From 11314b858b488ecb285c07bebb0c43e9f83a8a23 Mon Sep 17 00:00:00 2001 From: ninetailedtori Date: Thu, 2 Oct 2025 21:11:40 +0100 Subject: [PATCH 1/7] Initial commit for wayland host-to-client clipboard. See: https://github.com/ValveSoftware/gamescope/pull/1797 Signed-off-by: ninetailedtori --- src/Backends/WaylandBackend.cpp | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index 964132b2a4..c3b864d0a2 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -767,6 +767,13 @@ namespace gamescope void Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ); void Wayland_DataSource_Cancelled( struct wl_data_source *pSource ); static const wl_data_source_listener s_DataSourceListener; + + void Wayland_DataDevice_DataOffer( struct wl_data_device *pDevice, struct wl_data_offer *pOffer ); + void Wayland_DataDevice_Selection( wl_data_device *pDataDevice, wl_data_offer *pOffer ); + static const wl_data_device_listener s_DataDeviceListener; + + void Wayland_DataOffer_Offer( struct wl_data_offer *pOffer, const char *pMime ); + static const struct wl_data_offer_listener s_DataOfferListener; void Wayland_PrimarySelectionSource_Send( struct zwp_primary_selection_source_v1 *pSource, const char *pMime, int nFd ); void Wayland_PrimarySelectionSource_Cancelled( struct zwp_primary_selection_source_v1 *pSource ); @@ -913,6 +920,13 @@ namespace gamescope .send = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Send ), .cancelled = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Cancelled ), }; + const wl_data_device_listener CWaylandBackend::s_DataDeviceListener = { + .data_offer = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataDevice_DataOffer ), + .selection = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataDevice_Selection ), + }; + const wl_data_offer_listener CWaylandBackend::s_DataOfferListener = { + .offer = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataOffer_Offer ), + }; ////////////////// // CWaylandFb @@ -2076,6 +2090,16 @@ namespace gamescope xdg_log.errorf( "Failed to initialize input thread" ); return false; } + + // Set up the data device listener + if (m_pDataDeviceManager && !m_pDataDevice) { + m_pDataDevice = wl_data_device_manager_get_data_device(m_pDataDeviceManager, m_pSeat); + if (!m_pDataDevice) { + xdg_log.errorf("Failed to get wl_data_device"); + return false; + } + wl_data_device_add_listener(m_pDataDevice, &s_DataDeviceListener, this); + } xdg_log.infof( "Initted Wayland backend" ); @@ -2743,6 +2767,54 @@ namespace gamescope zwp_primary_selection_source_v1_destroy( pSource ); } + // Data Device + + void CWaylandBackend::Wayland_DataDevice_Selection(wl_data_device *pDataDevice, wl_data_offer *pOffer) { + // An application has set the clipboard contents + if (pOffer == nullptr) { + // Clipboard is empty + m_pClipboard = nullptr; + gamescope_set_selection(std::string{}, GAMESCOPE_SELECTION_CLIPBOARD); + return; + } + + int fds[2]; + if (pipe(fds) < 0) { + xdg_log.errorf("Failed to create pipe for clipboard data"); + return; + } + + wl_data_offer_receive(pOffer, "text/plain", fds[1]); + close(fds[1]); + + wl_display_roundtrip(m_pDisplay); + + // Read the clipboard contents and store it in a member variable. + std::string clipboardData; + char buf[1024]; + ssize_t n; + while ((n = read(fds[0], buf, sizeof(buf))) > 0) { + clipboardData.append(buf, n); + } + close(fds[0]); + + m_pClipboard = std::make_shared(clipboardData); + + char *pClipBoard = m_pClipboard->data(); + gamescope_set_selection( pClipBoard, GAMESCOPE_SELECTION_CLIPBOARD); + + wl_data_offer_destroy(pOffer); + } + void CWaylandBackend::Wayland_DataDevice_DataOffer(struct wl_data_device *pDevice, struct wl_data_offer *pOffer) { + wl_data_offer_add_listener(pOffer, &s_DataOfferListener, nullptr); + } + + // Data Offer + void CWaylandBackend::Wayland_DataOffer_Offer(struct wl_data_offer* pOffer, const char* pMime) + { + xdg_log.debugf("Clipboard supports MIME type: %s", pMime); + } + /////////////////////// // CWaylandInputThread /////////////////////// From f80d49c73ba8af7b0575948238cd7b6f1417d09f Mon Sep 17 00:00:00 2001 From: ninetailedtori Date: Fri, 3 Oct 2025 01:23:42 +0100 Subject: [PATCH 2/7] mime_type checks to wl_data_offer handling. Signed-off-by: ninetailedtori --- src/Backends/WaylandBackend.cpp | 55 ++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index c3b864d0a2..2871040760 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -60,6 +60,14 @@ static const char *GAMESCOPE_proxy_tag = "gamescope-proxy"; static const char *GAMESCOPE_plane_tag = "gamescope-plane"; static const char *GAMESCOPE_toplevel_tag = "gamescope-toplevel"; +const std::vector supportedMimeTypes = { + "text/plain;charset=utf-8", + "UTF8_STRING", + "text/plain", + "STRING", + "TEXT", +}; + template auto CallWithAllButLast(Func pFunc, Args&&... args) { @@ -810,6 +818,7 @@ namespace gamescope wl_data_device *m_pDataDevice = nullptr; std::shared_ptr m_pClipboard = nullptr; + std::vector m_CurrentOfferMimeTypes; zwp_primary_selection_device_manager_v1 *m_pPrimarySelectionDeviceManager = nullptr; zwp_primary_selection_device_v1 *m_pPrimarySelectionDevice = nullptr; std::shared_ptr m_pPrimarySelection = nullptr; @@ -1316,11 +1325,9 @@ namespace gamescope m_pBackend->m_pClipboard = szContents; wl_data_source *source = wl_data_device_manager_create_data_source( m_pBackend->m_pDataDeviceManager ); wl_data_source_add_listener( source, &m_pBackend->s_DataSourceListener, m_pBackend ); - wl_data_source_offer( source, "text/plain" ); - wl_data_source_offer( source, "text/plain;charset=utf-8" ); - wl_data_source_offer( source, "TEXT" ); - wl_data_source_offer( source, "STRING" ); - wl_data_source_offer( source, "UTF8_STRING" ); + for (const char *mime_type : supportedMimeTypes) { + wl_data_source_offer(source, mime_type); + } wl_data_device_set_selection( m_pBackend->m_pDataDevice, source, m_pBackend->m_uKeyboardEnterSerial ); } else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY && m_pBackend->m_pPrimarySelectionDevice ) @@ -1328,11 +1335,9 @@ namespace gamescope m_pBackend->m_pPrimarySelection = szContents; zwp_primary_selection_source_v1 *source = zwp_primary_selection_device_manager_v1_create_source( m_pBackend->m_pPrimarySelectionDeviceManager ); zwp_primary_selection_source_v1_add_listener( source, &m_pBackend->s_PrimarySelectionSourceListener, m_pBackend ); - zwp_primary_selection_source_v1_offer( source, "text/plain" ); - zwp_primary_selection_source_v1_offer( source, "text/plain;charset=utf-8" ); - zwp_primary_selection_source_v1_offer( source, "TEXT" ); - zwp_primary_selection_source_v1_offer( source, "STRING" ); - zwp_primary_selection_source_v1_offer( source, "UTF8_STRING" ); + for (const char *mime_type : supportedMimeTypes) { + zwp_primary_selection_source_v1_offer( source, mime_type ); + } zwp_primary_selection_device_v1_set_selection( m_pBackend->m_pPrimarySelectionDevice, source, m_pBackend->m_uPointerEnterSerial ); } } @@ -2773,14 +2778,40 @@ namespace gamescope // An application has set the clipboard contents if (pOffer == nullptr) { // Clipboard is empty + m_CurrentOfferMimeTypes.clear(); m_pClipboard = nullptr; gamescope_set_selection(std::string{}, GAMESCOPE_SELECTION_CLIPBOARD); return; } - + + wl_display_roundtrip(m_pDisplay); + + const std::vector &mimeTypes = m_CurrentOfferMimeTypes; + const char *selectedMimeType = nullptr; + + for (const char *supportedType : supportedMimeTypes) { + for (const auto &offeredType : mimeTypes) { + if (strcmp(offeredType == supportedType) == 0) { + selectedMimeType == supportedType; + return; + } + } + } + + if (!selectedMimeType) { + xdg_log.debugf("No supported clipboard MIME type found. Destroying data offer."); + wl_data_offer_destroy(pOffer); + m_CurrentOfferMimeTypes.clear(); + return; + } + + xdg_log.debugf("Accepting clipboard MIME type: %s", selectedMimeType); + int fds[2]; if (pipe(fds) < 0) { xdg_log.errorf("Failed to create pipe for clipboard data"); + wl_data_offer_destroy(pOffer); + m_CurrentOfferMimeTypes.clear(); return; } @@ -2806,12 +2837,14 @@ namespace gamescope wl_data_offer_destroy(pOffer); } void CWaylandBackend::Wayland_DataDevice_DataOffer(struct wl_data_device *pDevice, struct wl_data_offer *pOffer) { + m_CurrentOfferMimeTypes.clear(); wl_data_offer_add_listener(pOffer, &s_DataOfferListener, nullptr); } // Data Offer void CWaylandBackend::Wayland_DataOffer_Offer(struct wl_data_offer* pOffer, const char* pMime) { + m_CurrentOfferMimeTypes.emplace_back(pMime); xdg_log.debugf("Clipboard supports MIME type: %s", pMime); } From a8ebf5713519bc76aa7240803c5981aa1f70a7d4 Mon Sep 17 00:00:00 2001 From: ninetailedtori Date: Tue, 28 Oct 2025 20:00:44 +0000 Subject: [PATCH 3/7] Add .idea to ignore, typo fix strcmp(s1==s2) to strcmp(s1,s2) Signed-off-by: ninetailedtori --- .gitignore | 1 + src/Backends/WaylandBackend.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..485dee64bc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index 2871040760..1685a94bc1 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -775,7 +775,7 @@ namespace gamescope void Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ); void Wayland_DataSource_Cancelled( struct wl_data_source *pSource ); static const wl_data_source_listener s_DataSourceListener; - + void Wayland_DataDevice_DataOffer( struct wl_data_device *pDevice, struct wl_data_offer *pOffer ); void Wayland_DataDevice_Selection( wl_data_device *pDataDevice, wl_data_offer *pOffer ); static const wl_data_device_listener s_DataDeviceListener; @@ -2095,7 +2095,7 @@ namespace gamescope xdg_log.errorf( "Failed to initialize input thread" ); return false; } - + // Set up the data device listener if (m_pDataDeviceManager && !m_pDataDevice) { m_pDataDevice = wl_data_device_manager_get_data_device(m_pDataDeviceManager, m_pSeat); @@ -2791,7 +2791,7 @@ namespace gamescope for (const char *supportedType : supportedMimeTypes) { for (const auto &offeredType : mimeTypes) { - if (strcmp(offeredType == supportedType) == 0) { + if (strcmp(offeredType, supportedType) == 0) { selectedMimeType == supportedType; return; } @@ -2814,7 +2814,7 @@ namespace gamescope m_CurrentOfferMimeTypes.clear(); return; } - + wl_data_offer_receive(pOffer, "text/plain", fds[1]); close(fds[1]); From d9aea230ad77eb5f98bb114aac9a6f02e8c05fdd Mon Sep 17 00:00:00 2001 From: Toria Date: Thu, 15 Jan 2026 00:12:20 +0000 Subject: [PATCH 4/7] Fix some issues with clipboard. See if this corrects. Signed-off-by: Toria --- src/Backends/WaylandBackend.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index 1685a94bc1..7d802a84a8 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -2786,16 +2786,17 @@ namespace gamescope wl_display_roundtrip(m_pDisplay); - const std::vector &mimeTypes = m_CurrentOfferMimeTypes; const char *selectedMimeType = nullptr; for (const char *supportedType : supportedMimeTypes) { - for (const auto &offeredType : mimeTypes) { - if (strcmp(offeredType, supportedType) == 0) { - selectedMimeType == supportedType; - return; + for (const auto &offeredType : m_CurrentOfferMimeTypes) { + if (offeredType == supportedType) { + selectedMimeType = supportedType; + break; } } + if (selectedMimeType) + break; } if (!selectedMimeType) { @@ -2815,7 +2816,7 @@ namespace gamescope return; } - wl_data_offer_receive(pOffer, "text/plain", fds[1]); + wl_data_offer_receive(pOffer, selectedMimeType, fds[1]); close(fds[1]); wl_display_roundtrip(m_pDisplay); @@ -2831,14 +2832,14 @@ namespace gamescope m_pClipboard = std::make_shared(clipboardData); - char *pClipBoard = m_pClipboard->data(); - gamescope_set_selection( pClipBoard, GAMESCOPE_SELECTION_CLIPBOARD); + gamescope_set_selection( clipboardData, GAMESCOPE_SELECTION_CLIPBOARD); wl_data_offer_destroy(pOffer); + m_CurrentOfferMimeTypes.clear(); } void CWaylandBackend::Wayland_DataDevice_DataOffer(struct wl_data_device *pDevice, struct wl_data_offer *pOffer) { m_CurrentOfferMimeTypes.clear(); - wl_data_offer_add_listener(pOffer, &s_DataOfferListener, nullptr); + wl_data_offer_add_listener(pOffer, &s_DataOfferListener, this); } // Data Offer From 32a0135b67f15ef9962f476f728995cc2914a77f Mon Sep 17 00:00:00 2001 From: Toria Date: Thu, 15 Jan 2026 00:25:32 +0000 Subject: [PATCH 5/7] Initialise values properly to WAYLAND_NULL() where necessary, etc. --- src/Backends/WaylandBackend.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index 7d802a84a8..c40488fd76 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -929,13 +929,17 @@ namespace gamescope .send = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Send ), .cancelled = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Cancelled ), }; - const wl_data_device_listener CWaylandBackend::s_DataDeviceListener = { - .data_offer = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataDevice_DataOffer ), - .selection = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataDevice_Selection ), - }; - const wl_data_offer_listener CWaylandBackend::s_DataOfferListener = { - .offer = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataOffer_Offer ), - }; + const wl_data_device_listener CWaylandBackend::s_DataDeviceListener = { + .data_offer = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataDevice_DataOffer ), + .enter = WAYLAND_NULL(), + .leave = WAYLAND_NULL(), + .motion = WAYLAND_NULL(), + .drop = WAYLAND_NULL(), + .selection = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataDevice_Selection ), + }; + const wl_data_offer_listener CWaylandBackend::s_DataOfferListener = { + .offer = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataOffer_Offer ), + }; ////////////////// // CWaylandFb From 0b97d59f2f2585083744220e796534eec22ea408 Mon Sep 17 00:00:00 2001 From: Toria Date: Thu, 15 Jan 2026 00:31:07 +0000 Subject: [PATCH 6/7] Add null checks to send. Signed-off-by: Toria --- src/Backends/WaylandBackend.cpp | 34 +++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index c40488fd76..74adff7382 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -2750,12 +2750,17 @@ namespace gamescope // Data Source - void CWaylandBackend::Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ) - { - ssize_t len = m_pClipboard->length(); - if ( write( nFd, m_pClipboard->c_str(), len ) != len ) - xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); - close( nFd ); + void CWaylandBackend::Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ) + { + if ( !m_pClipboard ) + { + close( nFd ); + return; + } + ssize_t len = m_pClipboard->length(); + if ( write( nFd, m_pClipboard->c_str(), len ) != len ) + xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); + close( nFd ); } void CWaylandBackend::Wayland_DataSource_Cancelled( struct wl_data_source *pSource ) { @@ -2764,12 +2769,17 @@ namespace gamescope // Primary Selection Source - void CWaylandBackend::Wayland_PrimarySelectionSource_Send( struct zwp_primary_selection_source_v1 *pSource, const char *pMime, int nFd ) - { - ssize_t len = m_pPrimarySelection->length(); - if ( write( nFd, m_pPrimarySelection->c_str(), len ) != len ) - xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); - close( nFd ); + void CWaylandBackend::Wayland_PrimarySelectionSource_Send( struct zwp_primary_selection_source_v1 *pSource, const char *pMime, int nFd ) + { + if ( !m_pPrimarySelection ) + { + close( nFd ); + return; + } + ssize_t len = m_pPrimarySelection->length(); + if ( write( nFd, m_pPrimarySelection->c_str(), len ) != len ) + xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); + close( nFd ); } void CWaylandBackend::Wayland_PrimarySelectionSource_Cancelled( struct zwp_primary_selection_source_v1 *pSource) { From 07abe50ac3bab23e2f2e16677c4063b85514b04a Mon Sep 17 00:00:00 2001 From: Toria Date: Sun, 18 Jan 2026 22:25:25 +0000 Subject: [PATCH 7/7] Delete .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 485dee64bc..0000000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.idea