* [PATCH] ui/sdl2: Add SDL clipboard support
@ 2025-07-31 17:42 startergo via
2025-08-05 14:27 ` Marc-André Lureau
0 siblings, 1 reply; 6+ messages in thread
From: startergo via @ 2025-07-31 17:42 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé, Marc-André Lureau,
Philippe Mathieu-Daudé, startergo, Kamay Xutax
Implement bidirectional clipboard integration between QEMU and host
system when using the SDL display backend. This allows seamless
copy-paste operations between the guest and host environments.
Features:
- Bidirectional clipboard sync (guest ↔ host)
- Async clipboard request handling to prevent blocking
- Self-update detection to avoid clipboard manager conflicts
- Configurable via --enable-sdl-clipboard build option
- Text-only clipboard support (following existing QEMU patterns)
The implementation follows the same patterns used by the existing
GTK and VNC clipboard implementations, integrating with QEMU's
clipboard subsystem through QemuClipboardPeer.
Tested on macOS with successful build and runtime clipboard
functionality verification.
Co-authored-by: Kamay Xutax <admin@xutaxkamay.com>
Signed-off-by: startergo <startergo@protonmail.com>
---
include/ui/sdl2.h | 12 ++++
meson.build | 3 +
meson_options.txt | 2 +
ui/meson.build | 3 +
ui/sdl2-clipboard.c | 154 ++++++++++++++++++++++++++++++++++++++++++++
ui/sdl2.c | 9 +++
6 files changed, 183 insertions(+)
create mode 100644 ui/sdl2-clipboard.c
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index dbe6e3d973..0cadbe8c1c 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -21,6 +21,10 @@
# include <SDL_image.h>
#endif
+#ifdef CONFIG_SDL_CLIPBOARD
+#include "ui/clipboard.h"
+#endif
+
#include "ui/kbd-state.h"
#ifdef CONFIG_OPENGL
# include "ui/egl-helpers.h"
@@ -45,6 +49,9 @@ struct sdl2_console {
bool gui_keysym;
SDL_GLContext winctx;
QKbdState *kbd;
+#ifdef CONFIG_SDL_CLIPBOARD
+ QemuClipboardPeer cbpeer;
+#endif
#ifdef CONFIG_OPENGL
QemuGLShader *gls;
egl_fb guest_fb;
@@ -97,4 +104,9 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
+#ifdef CONFIG_SDL_CLIPBOARD
+void sdl2_clipboard_init(struct sdl2_console *scon);
+void sdl2_clipboard_handle_request(struct sdl2_console *scon);
+#endif
+
#endif /* SDL2_H */
diff --git a/meson.build b/meson.build
index 41f68d3806..4a37df9669 100644
--- a/meson.build
+++ b/meson.build
@@ -1596,6 +1596,8 @@ else
sdl_image = not_found
endif
+have_sdl_clipboard = sdl.found() and get_option('sdl_clipboard')
+
rbd = not_found
if not get_option('rbd').auto() or have_block
librados = cc.find_library('rados', required: get_option('rbd'))
@@ -2511,6 +2513,7 @@ config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
config_host_data.set('CONFIG_SDL', sdl.found())
config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
+config_host_data.set('CONFIG_SDL_CLIPBOARD', have_sdl_clipboard)
config_host_data.set('CONFIG_SECCOMP', seccomp.found())
if seccomp.found()
config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
diff --git a/meson_options.txt b/meson_options.txt
index 59d973bca0..be2cba3a30 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -212,6 +212,8 @@ option('sdl', type : 'feature', value : 'auto',
description: 'SDL user interface')
option('sdl_image', type : 'feature', value : 'auto',
description: 'SDL Image support for icons')
+option('sdl_clipboard', type : 'boolean', value : true,
+ description: 'SDL clipboard support')
option('seccomp', type : 'feature', value : 'auto',
description: 'seccomp support')
option('smartcard', type : 'feature', value : 'auto',
diff --git a/ui/meson.build b/ui/meson.build
index 35fb04cadf..6d1bf3477e 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -126,6 +126,9 @@ if sdl.found()
'sdl2-input.c',
'sdl2.c',
))
+ if have_sdl_clipboard
+ sdl_ss.add(files('sdl2-clipboard.c'))
+ endif
sdl_ss.add(when: opengl, if_true: files('sdl2-gl.c'))
sdl_ss.add(when: x11, if_true: files('x_keymap.c'))
ui_modules += {'sdl' : sdl_ss}
diff --git a/ui/sdl2-clipboard.c b/ui/sdl2-clipboard.c
new file mode 100644
index 0000000000..e50ff11d5a
--- /dev/null
+++ b/ui/sdl2-clipboard.c
@@ -0,0 +1,154 @@
+/*
+ * SDL UI -- clipboard support (improved async version)
+ *
+ * Copyright (C) 2023 Kamay Xutax <admin@xutaxkamay.com>
+ * Copyright (C) 2025 startergo <startergo@protonmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "ui/clipboard.h"
+#include "ui/sdl2.h"
+#include "qemu/log.h"
+
+#ifdef CONFIG_SDL_CLIPBOARD
+
+/* Track pending clipboard requests to handle async data */
+typedef struct {
+ struct sdl2_console *scon;
+ QemuClipboardInfo *info;
+ QemuClipboardType type;
+} SDLClipboardRequest;
+
+static SDLClipboardRequest *pending_request = NULL;
+
+static void sdl2_clipboard_clear_pending(void)
+{
+ if (pending_request) {
+ if (pending_request->info) {
+ qemu_clipboard_info_unref(pending_request->info);
+ }
+ g_free(pending_request);
+ pending_request = NULL;
+ }
+}
+
+static void sdl2_clipboard_notify(Notifier *notifier, void *data)
+{
+ QemuClipboardNotify *notify = data;
+ struct sdl2_console *scon =
+ container_of(notifier, struct sdl2_console, cbpeer.notifier);
+ bool self_update = notify->info->owner == &scon->cbpeer;
+ const char *text_data;
+ size_t text_size;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ {
+ /* Skip self-updates to avoid clipboard manager conflicts */
+ if (self_update) {
+ return;
+ }
+
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ return;
+ }
+
+ /* Check if this is completion of our pending request */
+ if (pending_request && pending_request->info == notify->info &&
+ pending_request->type == QEMU_CLIPBOARD_TYPE_TEXT) {
+ sdl2_clipboard_clear_pending();
+ }
+
+ /* Check if data is available, request asynchronously if not */
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
+ if (!pending_request) {
+ pending_request = g_new0(SDLClipboardRequest, 1);
+ pending_request->scon = scon;
+ pending_request->info = qemu_clipboard_info_ref(notify->info);
+ pending_request->type = QEMU_CLIPBOARD_TYPE_TEXT;
+ qemu_clipboard_request(notify->info, QEMU_CLIPBOARD_TYPE_TEXT);
+ }
+ return;
+ }
+
+ /* Process available data */
+ text_size = notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].size;
+ if (text_size == 0) {
+ return;
+ }
+
+ text_data = (const char *)notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data;
+
+ /* Ensure null termination for SDL clipboard */
+ g_autofree char *text = g_strndup(text_data, text_size);
+ if (text && text[0] != '\0') {
+ SDL_SetClipboardText(text);
+ } else if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to allocate memory for clipboard text\n");
+ }
+ break;
+ }
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ sdl2_clipboard_clear_pending();
+ break;
+ }
+}
+
+static void sdl2_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ g_autofree char *text = NULL;
+
+ if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
+ return;
+ }
+
+ text = SDL_GetClipboardText();
+ if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to get clipboard text: %s\n",
+ SDL_GetError());
+ return;
+ }
+
+ qemu_clipboard_set_data(info->owner, info, type,
+ strlen(text), text, true);
+}
+
+void sdl2_clipboard_init(struct sdl2_console *scon)
+{
+ scon->cbpeer.name = "sdl2-clipboard";
+ scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
+ scon->cbpeer.request = sdl2_clipboard_request;
+
+ qemu_clipboard_peer_register(&scon->cbpeer);
+}
+
+void sdl2_clipboard_handle_request(struct sdl2_console *scon)
+{
+ g_autofree char *text = NULL;
+ QemuClipboardInfo *info;
+
+ text = SDL_GetClipboardText();
+ if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to get clipboard text: %s\n",
+ SDL_GetError());
+ return;
+ }
+
+ if (text[0] == '\0') {
+ return; /* Ignore empty clipboard */
+ }
+
+ info = qemu_clipboard_info_new(&scon->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+ qemu_clipboard_set_data(&scon->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
+ strlen(text), text, true);
+ qemu_clipboard_info_unref(info);
+}
+
+#endif /* CONFIG_SDL_CLIPBOARD */
diff --git a/ui/sdl2.c b/ui/sdl2.c
index cda4293a53..00a17b68a7 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -691,6 +691,11 @@ void sdl2_poll_events(struct sdl2_console *scon)
case SDL_WINDOWEVENT:
handle_windowevent(ev);
break;
+#ifdef CONFIG_SDL_CLIPBOARD
+ case SDL_CLIPBOARDUPDATE:
+ sdl2_clipboard_handle_request(scon);
+ break;
+#endif
default:
break;
}
@@ -901,6 +906,10 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
}
register_displaychangelistener(&sdl2_console[i].dcl);
+#ifdef CONFIG_SDL_CLIPBOARD
+ sdl2_clipboard_init(&sdl2_console[i]);
+#endif
+
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH] ui/sdl2: Add SDL clipboard support
@ 2025-08-05 11:53 startergo
2025-08-05 12:20 ` Marc-André Lureau
0 siblings, 1 reply; 6+ messages in thread
From: startergo @ 2025-08-05 11:53 UTC (permalink / raw)
To: qemu-devel@nongnu.org; +Cc: pbonzini@redhat.com, marcandre.lureau@redhat.com
[-- Attachment #1: Type: text/plain, Size: 9152 bytes --]
From 89affd7e5b1ac9fcf9f10e483d9e4e63328a42fa Mon Sep 17 00:00:00 2001
From: startergo <startergo@protonmail.com>
Date: Thu, 31 Jul 2025 19:36:07 +0300
Subject: [PATCH 1/1] ui/sdl2: Add SDL clipboard support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
To: qemu-devel@nongnu.org
Cc: pbonzini@redhat.com,
marcandre.lureau@redhat.com
Implement bidirectional clipboard integration between QEMU and host
system when using the SDL display backend. This allows seamless
copy-paste operations between the guest and host environments.
Features:
- Bidirectional clipboard sync (guest ↔ host)
- Async clipboard request handling to prevent blocking
- Self-update detection to avoid clipboard manager conflicts
- Configurable via --enable-sdl-clipboard build option
- Text-only clipboard support (following existing QEMU patterns)
The implementation follows the same patterns used by the existing
GTK and VNC clipboard implementations, integrating with QEMU's
clipboard subsystem through QemuClipboardPeer.
Tested on macOS with successful build and runtime clipboard
functionality verification.
Co-authored-by: Kamay Xutax <admin@xutaxkamay.com>
Signed-off-by: startergo <startergo@protonmail.com>
---
include/ui/sdl2.h | 12 ++++
meson.build | 3 +
meson_options.txt | 2 +
ui/meson.build | 3 +
ui/sdl2-clipboard.c | 154 ++++++++++++++++++++++++++++++++++++++++++++
ui/sdl2.c | 9 +++
6 files changed, 183 insertions(+)
create mode 100644 ui/sdl2-clipboard.c
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index dbe6e3d973..0cadbe8c1c 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -21,6 +21,10 @@
# include <SDL_image.h>
#endif
+#ifdef CONFIG_SDL_CLIPBOARD
+#include "ui/clipboard.h"
+#endif
+
#include "ui/kbd-state.h"
#ifdef CONFIG_OPENGL
# include "ui/egl-helpers.h"
@@ -45,6 +49,9 @@ struct sdl2_console {
bool gui_keysym;
SDL_GLContext winctx;
QKbdState *kbd;
+#ifdef CONFIG_SDL_CLIPBOARD
+ QemuClipboardPeer cbpeer;
+#endif
#ifdef CONFIG_OPENGL
QemuGLShader *gls;
egl_fb guest_fb;
@@ -97,4 +104,9 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
+#ifdef CONFIG_SDL_CLIPBOARD
+void sdl2_clipboard_init(struct sdl2_console *scon);
+void sdl2_clipboard_handle_request(struct sdl2_console *scon);
+#endif
+
#endif /* SDL2_H */
diff --git a/meson.build b/meson.build
index 41f68d3806..4a37df9669 100644
--- a/meson.build
+++ b/meson.build
@@ -1596,6 +1596,8 @@ else
sdl_image = not_found
endif
+have_sdl_clipboard = sdl.found() and get_option('sdl_clipboard')
+
rbd = not_found
if not get_option('rbd').auto() or have_block
librados = cc.find_library('rados', required: get_option('rbd'))
@@ -2511,6 +2513,7 @@ config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
config_host_data.set('CONFIG_SDL', sdl.found())
config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
+config_host_data.set('CONFIG_SDL_CLIPBOARD', have_sdl_clipboard)
config_host_data.set('CONFIG_SECCOMP', seccomp.found())
if seccomp.found()
config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
diff --git a/meson_options.txt b/meson_options.txt
index 59d973bca0..be2cba3a30 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -212,6 +212,8 @@ option('sdl', type : 'feature', value : 'auto',
description: 'SDL user interface')
option('sdl_image', type : 'feature', value : 'auto',
description: 'SDL Image support for icons')
+option('sdl_clipboard', type : 'boolean', value : true,
+ description: 'SDL clipboard support')
option('seccomp', type : 'feature', value : 'auto',
description: 'seccomp support')
option('smartcard', type : 'feature', value : 'auto',
diff --git a/ui/meson.build b/ui/meson.build
index 35fb04cadf..6d1bf3477e 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -126,6 +126,9 @@ if sdl.found()
'sdl2-input.c',
'sdl2.c',
))
+ if have_sdl_clipboard
+ sdl_ss.add(files('sdl2-clipboard.c'))
+ endif
sdl_ss.add(when: opengl, if_true: files('sdl2-gl.c'))
sdl_ss.add(when: x11, if_true: files('x_keymap.c'))
ui_modules += {'sdl' : sdl_ss}
diff --git a/ui/sdl2-clipboard.c b/ui/sdl2-clipboard.c
new file mode 100644
index 0000000000..e50ff11d5a
--- /dev/null
+++ b/ui/sdl2-clipboard.c
@@ -0,0 +1,154 @@
+/*
+ * SDL UI -- clipboard support (improved async version)
+ *
+ * Copyright (C) 2023 Kamay Xutax <admin@xutaxkamay.com>
+ * Copyright (C) 2025 startergo <startergo@protonmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "ui/clipboard.h"
+#include "ui/sdl2.h"
+#include "qemu/log.h"
+
+#ifdef CONFIG_SDL_CLIPBOARD
+
+/* Track pending clipboard requests to handle async data */
+typedef struct {
+ struct sdl2_console *scon;
+ QemuClipboardInfo *info;
+ QemuClipboardType type;
+} SDLClipboardRequest;
+
+static SDLClipboardRequest *pending_request = NULL;
+
+static void sdl2_clipboard_clear_pending(void)
+{
+ if (pending_request) {
+ if (pending_request->info) {
+ qemu_clipboard_info_unref(pending_request->info);
+ }
+ g_free(pending_request);
+ pending_request = NULL;
+ }
+}
+
+static void sdl2_clipboard_notify(Notifier *notifier, void *data)
+{
+ QemuClipboardNotify *notify = data;
+ struct sdl2_console *scon =
+ container_of(notifier, struct sdl2_console, cbpeer.notifier);
+ bool self_update = notify->info->owner == &scon->cbpeer;
+ const char *text_data;
+ size_t text_size;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ {
+ /* Skip self-updates to avoid clipboard manager conflicts */
+ if (self_update) {
+ return;
+ }
+
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ return;
+ }
+
+ /* Check if this is completion of our pending request */
+ if (pending_request && pending_request->info == notify->info &&
+ pending_request->type == QEMU_CLIPBOARD_TYPE_TEXT) {
+ sdl2_clipboard_clear_pending();
+ }
+
+ /* Check if data is available, request asynchronously if not */
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
+ if (!pending_request) {
+ pending_request = g_new0(SDLClipboardRequest, 1);
+ pending_request->scon = scon;
+ pending_request->info = qemu_clipboard_info_ref(notify->info);
+ pending_request->type = QEMU_CLIPBOARD_TYPE_TEXT;
+ qemu_clipboard_request(notify->info, QEMU_CLIPBOARD_TYPE_TEXT);
+ }
+ return;
+ }
+
+ /* Process available data */
+ text_size = notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].size;
+ if (text_size == 0) {
+ return;
+ }
+
+ text_data = (const char *)notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data;
+
+ /* Ensure null termination for SDL clipboard */
+ g_autofree char *text = g_strndup(text_data, text_size);
+ if (text && text[0] != '\0') {
+ SDL_SetClipboardText(text);
+ } else if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to allocate memory for clipboard text\n");
+ }
+ break;
+ }
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ sdl2_clipboard_clear_pending();
+ break;
+ }
+}
+
+static void sdl2_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ g_autofree char *text = NULL;
+
+ if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
+ return;
+ }
+
+ text = SDL_GetClipboardText();
+ if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to get clipboard text: %s\n",
+ SDL_GetError());
+ return;
+ }
+
+ qemu_clipboard_set_data(info->owner, info, type,
+ strlen(text), text, true);
+}
+
+void sdl2_clipboard_init(struct sdl2_console *scon)
+{
+ scon->cbpeer.name = "sdl2-clipboard";
+ scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
+ scon->cbpeer.request = sdl2_clipboard_request;
+
+ qemu_clipboard_peer_register(&scon->cbpeer);
+}
+
+void sdl2_clipboard_handle_request(struct sdl2_console *scon)
+{
+ g_autofree char *text = NULL;
+ QemuClipboardInfo *info;
+
+ text = SDL_GetClipboardText();
+ if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to get clipboard text: %s\n",
+ SDL_GetError());
+ return;
+ }
+
+ if (text[0] == '\0') {
+ return; /* Ignore empty clipboard */
+ }
+
+ info = qemu_clipboard_info_new(&scon->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+ qemu_clipboard_set_data(&scon->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
+ strlen(text), text, true);
+ qemu_clipboard_info_unref(info);
+}
+
+#endif /* CONFIG_SDL_CLIPBOARD */
diff --git a/ui/sdl2.c b/ui/sdl2.c
index cda4293a53..00a17b68a7 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -691,6 +691,11 @@ void sdl2_poll_events(struct sdl2_console *scon)
case SDL_WINDOWEVENT:
handle_windowevent(ev);
break;
+#ifdef CONFIG_SDL_CLIPBOARD
+ case SDL_CLIPBOARDUPDATE:
+ sdl2_clipboard_handle_request(scon);
+ break;
+#endif
default:
break;
}
@@ -901,6 +906,10 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
}
register_displaychangelistener(&sdl2_console[i].dcl);
+#ifdef CONFIG_SDL_CLIPBOARD
+ sdl2_clipboard_init(&sdl2_console[i]);
+#endif
+
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
--
2.50.1
[-- Attachment #2: Type: text/html, Size: 90492 bytes --]
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH] ui/sdl2: Add SDL clipboard support
2025-08-05 11:53 startergo
@ 2025-08-05 12:20 ` Marc-André Lureau
0 siblings, 0 replies; 6+ messages in thread
From: Marc-André Lureau @ 2025-08-05 12:20 UTC (permalink / raw)
To: startergo; +Cc: qemu-devel@nongnu.org, pbonzini@redhat.com
Hi
This is still not a correctly formatted git patch:
$ git am \[PATCH\]\ ui_sdl2_\ Add\ SDL\ clipboard\ support.eml
warning: quoted CRLF detected
Applying: ui/sdl2: Add SDL clipboard support
error: corrupt patch at line 16
Patch failed at 0001 ui/sdl2: Add SDL clipboard support
Please make sure you follow:
https://www.qemu.org/docs/master/devel/submitting-a-patch.html
thanks
On Tue, Aug 5, 2025 at 3:54 PM startergo <startergo@protonmail.com> wrote:
>
> From 89affd7e5b1ac9fcf9f10e483d9e4e63328a42fa Mon Sep 17 00:00:00 2001
> From: startergo <startergo@protonmail.com>
> Date: Thu, 31 Jul 2025 19:36:07 +0300
> Subject: [PATCH 1/1] ui/sdl2: Add SDL clipboard support
> MIME-Version: 1.0
> Content-Type: text/plain; charset=UTF-8
> Content-Transfer-Encoding: 8bit
> To: qemu-devel@nongnu.org
> Cc: pbonzini@redhat.com,
> marcandre.lureau@redhat.com
>
> Implement bidirectional clipboard integration between QEMU and host
> system when using the SDL display backend. This allows seamless
> copy-paste operations between the guest and host environments.
>
> Features:
> - Bidirectional clipboard sync (guest ↔ host)
> - Async clipboard request handling to prevent blocking
> - Self-update detection to avoid clipboard manager conflicts
> - Configurable via --enable-sdl-clipboard build option
> - Text-only clipboard support (following existing QEMU patterns)
>
> The implementation follows the same patterns used by the existing
> GTK and VNC clipboard implementations, integrating with QEMU's
> clipboard subsystem through QemuClipboardPeer.
>
> Tested on macOS with successful build and runtime clipboard
> functionality verification.
>
> Co-authored-by: Kamay Xutax <admin@xutaxkamay.com>
> Signed-off-by: startergo <startergo@protonmail.com>
> ---
> include/ui/sdl2.h | 12 ++++
> meson.build | 3 +
> meson_options.txt | 2 +
> ui/meson.build | 3 +
> ui/sdl2-clipboard.c | 154 ++++++++++++++++++++++++++++++++++++++++++++
> ui/sdl2.c | 9 +++
> 6 files changed, 183 insertions(+)
> create mode 100644 ui/sdl2-clipboard.c
>
> diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
> index dbe6e3d973..0cadbe8c1c 100644
> --- a/include/ui/sdl2.h
> +++ b/include/ui/sdl2.h
> @@ -21,6 +21,10 @@
> # include <SDL_image.h>
> #endif
>
> +#ifdef CONFIG_SDL_CLIPBOARD
> +#include "ui/clipboard.h"
> +#endif
> +
> #include "ui/kbd-state.h"
> #ifdef CONFIG_OPENGL
> # include "ui/egl-helpers.h"
> @@ -45,6 +49,9 @@ struct sdl2_console {
> bool gui_keysym;
> SDL_GLContext winctx;
> QKbdState *kbd;
> +#ifdef CONFIG_SDL_CLIPBOARD
> + QemuClipboardPeer cbpeer;
> +#endif
> #ifdef CONFIG_OPENGL
> QemuGLShader *gls;
> egl_fb guest_fb;
> @@ -97,4 +104,9 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
> void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
> uint32_t x, uint32_t y, uint32_t w, uint32_t h);
>
> +#ifdef CONFIG_SDL_CLIPBOARD
> +void sdl2_clipboard_init(struct sdl2_console *scon);
> +void sdl2_clipboard_handle_request(struct sdl2_console *scon);
> +#endif
> +
> #endif /* SDL2_H */
> diff --git a/meson.build b/meson.build
> index 41f68d3806..4a37df9669 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -1596,6 +1596,8 @@ else
> sdl_image = not_found
> endif
>
> +have_sdl_clipboard = sdl.found() and get_option('sdl_clipboard')
> +
> rbd = not_found
> if not get_option('rbd').auto() or have_block
> librados = cc.find_library('rados', required: get_option('rbd'))
> @@ -2511,6 +2513,7 @@ config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
> config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
> config_host_data.set('CONFIG_SDL', sdl.found())
> config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
> +config_host_data.set('CONFIG_SDL_CLIPBOARD', have_sdl_clipboard)
> config_host_data.set('CONFIG_SECCOMP', seccomp.found())
> if seccomp.found()
> config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
> diff --git a/meson_options.txt b/meson_options.txt
> index 59d973bca0..be2cba3a30 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -212,6 +212,8 @@ option('sdl', type : 'feature', value : 'auto',
> description: 'SDL user interface')
> option('sdl_image', type : 'feature', value : 'auto',
> description: 'SDL Image support for icons')
> +option('sdl_clipboard', type : 'boolean', value : true,
> + description: 'SDL clipboard support')
> option('seccomp', type : 'feature', value : 'auto',
> description: 'seccomp support')
> option('smartcard', type : 'feature', value : 'auto',
> diff --git a/ui/meson.build b/ui/meson.build
> index 35fb04cadf..6d1bf3477e 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -126,6 +126,9 @@ if sdl.found()
> 'sdl2-input.c',
> 'sdl2.c',
> ))
> + if have_sdl_clipboard
> + sdl_ss.add(files('sdl2-clipboard.c'))
> + endif
> sdl_ss.add(when: opengl, if_true: files('sdl2-gl.c'))
> sdl_ss.add(when: x11, if_true: files('x_keymap.c'))
> ui_modules += {'sdl' : sdl_ss}
> diff --git a/ui/sdl2-clipboard.c b/ui/sdl2-clipboard.c
> new file mode 100644
> index 0000000000..e50ff11d5a
> --- /dev/null
> +++ b/ui/sdl2-clipboard.c
> @@ -0,0 +1,154 @@
> +/*
> + * SDL UI -- clipboard support (improved async version)
> + *
> + * Copyright (C) 2023 Kamay Xutax <admin@xutaxkamay.com>
> + * Copyright (C) 2025 startergo <startergo@protonmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "ui/console.h"
> +#include "ui/clipboard.h"
> +#include "ui/sdl2.h"
> +#include "qemu/log.h"
> +
> +#ifdef CONFIG_SDL_CLIPBOARD
> +
> +/* Track pending clipboard requests to handle async data */
> +typedef struct {
> + struct sdl2_console *scon;
> + QemuClipboardInfo *info;
> + QemuClipboardType type;
> +} SDLClipboardRequest;
> +
> +static SDLClipboardRequest *pending_request = NULL;
> +
> +static void sdl2_clipboard_clear_pending(void)
> +{
> + if (pending_request) {
> + if (pending_request->info) {
> + qemu_clipboard_info_unref(pending_request->info);
> + }
> + g_free(pending_request);
> + pending_request = NULL;
> + }
> +}
> +
> +static void sdl2_clipboard_notify(Notifier *notifier, void *data)
> +{
> + QemuClipboardNotify *notify = data;
> + struct sdl2_console *scon =
> + container_of(notifier, struct sdl2_console, cbpeer.notifier);
> + bool self_update = notify->info->owner == &scon->cbpeer;
> + const char *text_data;
> + size_t text_size;
> +
> + switch (notify->type) {
> + case QEMU_CLIPBOARD_UPDATE_INFO:
> + {
> + /* Skip self-updates to avoid clipboard manager conflicts */
> + if (self_update) {
> + return;
> + }
> +
> + if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
> + return;
> + }
> +
> + /* Check if this is completion of our pending request */
> + if (pending_request && pending_request->info == notify->info &&
> + pending_request->type == QEMU_CLIPBOARD_TYPE_TEXT) {
> + sdl2_clipboard_clear_pending();
> + }
> +
> + /* Check if data is available, request asynchronously if not */
> + if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
> + if (!pending_request) {
> + pending_request = g_new0(SDLClipboardRequest, 1);
> + pending_request->scon = scon;
> + pending_request->info = qemu_clipboard_info_ref(notify->info);
> + pending_request->type = QEMU_CLIPBOARD_TYPE_TEXT;
> + qemu_clipboard_request(notify->info, QEMU_CLIPBOARD_TYPE_TEXT);
> + }
> + return;
> + }
> +
> + /* Process available data */
> + text_size = notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].size;
> + if (text_size == 0) {
> + return;
> + }
> +
> + text_data = (const char *)notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data;
> +
> + /* Ensure null termination for SDL clipboard */
> + g_autofree char *text = g_strndup(text_data, text_size);
> + if (text && text[0] != '\0') {
> + SDL_SetClipboardText(text);
> + } else if (!text) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "SDL clipboard: Failed to allocate memory for clipboard text\n");
> + }
> + break;
> + }
> + case QEMU_CLIPBOARD_RESET_SERIAL:
> + sdl2_clipboard_clear_pending();
> + break;
> + }
> +}
> +
> +static void sdl2_clipboard_request(QemuClipboardInfo *info,
> + QemuClipboardType type)
> +{
> + g_autofree char *text = NULL;
> +
> + if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
> + return;
> + }
> +
> + text = SDL_GetClipboardText();
> + if (!text) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "SDL clipboard: Failed to get clipboard text: %s\n",
> + SDL_GetError());
> + return;
> + }
> +
> + qemu_clipboard_set_data(info->owner, info, type,
> + strlen(text), text, true);
> +}
> +
> +void sdl2_clipboard_init(struct sdl2_console *scon)
> +{
> + scon->cbpeer.name = "sdl2-clipboard";
> + scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
> + scon->cbpeer.request = sdl2_clipboard_request;
> +
> + qemu_clipboard_peer_register(&scon->cbpeer);
> +}
> +
> +void sdl2_clipboard_handle_request(struct sdl2_console *scon)
> +{
> + g_autofree char *text = NULL;
> + QemuClipboardInfo *info;
> +
> + text = SDL_GetClipboardText();
> + if (!text) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "SDL clipboard: Failed to get clipboard text: %s\n",
> + SDL_GetError());
> + return;
> + }
> +
> + if (text[0] == '\0') {
> + return; /* Ignore empty clipboard */
> + }
> +
> + info = qemu_clipboard_info_new(&scon->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
> + qemu_clipboard_set_data(&scon->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
> + strlen(text), text, true);
> + qemu_clipboard_info_unref(info);
> +}
> +
> +#endif /* CONFIG_SDL_CLIPBOARD */
> diff --git a/ui/sdl2.c b/ui/sdl2.c
> index cda4293a53..00a17b68a7 100644
> --- a/ui/sdl2.c
> +++ b/ui/sdl2.c
> @@ -691,6 +691,11 @@ void sdl2_poll_events(struct sdl2_console *scon)
> case SDL_WINDOWEVENT:
> handle_windowevent(ev);
> break;
> +#ifdef CONFIG_SDL_CLIPBOARD
> + case SDL_CLIPBOARDUPDATE:
> + sdl2_clipboard_handle_request(scon);
> + break;
> +#endif
> default:
> break;
> }
> @@ -901,6 +906,10 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
> }
> register_displaychangelistener(&sdl2_console[i].dcl);
>
> +#ifdef CONFIG_SDL_CLIPBOARD
> + sdl2_clipboard_init(&sdl2_console[i]);
> +#endif
> +
> #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
> if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
> #if defined(SDL_VIDEO_DRIVER_WINDOWS)
> --
> 2.50.1
>
>
--
Marc-André Lureau
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH] ui/sdl2: Add SDL clipboard support
@ 2025-08-05 13:16 macbookpro
0 siblings, 0 replies; 6+ messages in thread
From: macbookpro @ 2025-08-05 13:16 UTC (permalink / raw)
To: qemu-devel; +Cc: pbonzini, marcandre.lureau
From ef834d229fda71f1773f483f4ef6ef7c50e485d0 Mon Sep 17 00:00:00 2001
From: Test User <7897244+startergo@users.noreply.github.com>
Date: Tue, 5 Aug 2025 13:53:00 +0100
Subject: [PATCH] ui/sdl2: Add SDL clipboard support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Implement bidirectional clipboard integration between QEMU and host
system when using the SDL display backend. This allows seamless
copy-paste operations between the guest and host environments.
Features:
- Bidirectional clipboard sync (guest <-> host)
- Async clipboard request handling to prevent blocking
- Self-update detection to avoid clipboard manager conflicts
- Configurable via --enable-sdl-clipboard build option
- Text-only clipboard support (following existing QEMU patterns)
The implementation follows the same patterns used by the existing
GTK and VNC clipboard implementations, integrating with QEMU's
clipboard subsystem through QemuClipboardPeer.
Tested on macOS with successful build and runtime clipboard
functionality verification.
Co-authored-by: Kamay Xutax <admin@xutaxkamay.com>
Signed-off-by: startergo <startergo@protonmail.com>
---
include/ui/sdl2.h | 12 ++++
meson.build | 3 +
meson_options.txt | 2 +
ui/meson.build | 3 +
ui/sdl2-clipboard.c | 154 ++++++++++++++++++++++++++++++++++++++++++++
ui/sdl2.c | 9 +++
6 files changed, 183 insertions(+)
create mode 100644 ui/sdl2-clipboard.c
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index dbe6e3d97..0cadbe8c1 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -21,6 +21,10 @@
# include <SDL_image.h>
#endif
+#ifdef CONFIG_SDL_CLIPBOARD
+#include "ui/clipboard.h"
+#endif
+
#include "ui/kbd-state.h"
#ifdef CONFIG_OPENGL
# include "ui/egl-helpers.h"
@@ -45,6 +49,9 @@ struct sdl2_console {
bool gui_keysym;
SDL_GLContext winctx;
QKbdState *kbd;
+#ifdef CONFIG_SDL_CLIPBOARD
+ QemuClipboardPeer cbpeer;
+#endif
#ifdef CONFIG_OPENGL
QemuGLShader *gls;
egl_fb guest_fb;
@@ -97,4 +104,9 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
+#ifdef CONFIG_SDL_CLIPBOARD
+void sdl2_clipboard_init(struct sdl2_console *scon);
+void sdl2_clipboard_handle_request(struct sdl2_console *scon);
+#endif
+
#endif /* SDL2_H */
diff --git a/meson.build b/meson.build
index 41f68d380..4a37df966 100644
--- a/meson.build
+++ b/meson.build
@@ -1596,6 +1596,8 @@ else
sdl_image = not_found
endif
+have_sdl_clipboard = sdl.found() and get_option('sdl_clipboard')
+
rbd = not_found
if not get_option('rbd').auto() or have_block
librados = cc.find_library('rados', required: get_option('rbd'))
@@ -2511,6 +2513,7 @@ config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
config_host_data.set('CONFIG_SDL', sdl.found())
config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
+config_host_data.set('CONFIG_SDL_CLIPBOARD', have_sdl_clipboard)
config_host_data.set('CONFIG_SECCOMP', seccomp.found())
if seccomp.found()
config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
diff --git a/meson_options.txt b/meson_options.txt
index 59d973bca..be2cba3a3 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -212,6 +212,8 @@ option('sdl', type : 'feature', value : 'auto',
description: 'SDL user interface')
option('sdl_image', type : 'feature', value : 'auto',
description: 'SDL Image support for icons')
+option('sdl_clipboard', type : 'boolean', value : true,
+ description: 'SDL clipboard support')
option('seccomp', type : 'feature', value : 'auto',
description: 'seccomp support')
option('smartcard', type : 'feature', value : 'auto',
diff --git a/ui/meson.build b/ui/meson.build
index 35fb04cad..6d1bf3477 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -126,6 +126,9 @@ if sdl.found()
'sdl2-input.c',
'sdl2.c',
))
+ if have_sdl_clipboard
+ sdl_ss.add(files('sdl2-clipboard.c'))
+ endif
sdl_ss.add(when: opengl, if_true: files('sdl2-gl.c'))
sdl_ss.add(when: x11, if_true: files('x_keymap.c'))
ui_modules += {'sdl' : sdl_ss}
diff --git a/ui/sdl2-clipboard.c b/ui/sdl2-clipboard.c
new file mode 100644
index 000000000..15e68af46
--- /dev/null
+++ b/ui/sdl2-clipboard.c
@@ -0,0 +1,154 @@
+/*
+ * SDL UI -- clipboard support (improved async version)
+ *
+ * Copyright (C) 2023 Kamay Xutax <admin@xutaxkamay.com>
+ * Copyright (C) 2025 startergo <startergo@protonmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "ui/clipboard.h"
+#include "ui/sdl2.h"
+#include "qemu/log.h"
+
+#ifdef CONFIG_SDL_CLIPBOARD
+
+/* Track pending clipboard requests to handle async data */
+typedef struct {
+ struct sdl2_console *scon;
+ QemuClipboardInfo *info;
+ QemuClipboardType type;
+} SDLClipboardRequest;
+
+static SDLClipboardRequest *pending_request = NULL;
+
+static void sdl2_clipboard_clear_pending(void)
+{
+ if (pending_request) {
+ if (pending_request->info) {
+ qemu_clipboard_info_unref(pending_request->info);
+ }
+ g_free(pending_request);
+ pending_request = NULL;
+ }
+}
+
+static void sdl2_clipboard_notify(Notifier *notifier, void *data)
+{
+ QemuClipboardNotify *notify = data;
+ struct sdl2_console *scon =
+ container_of(notifier, struct sdl2_console, cbpeer.notifier);
+ bool self_update = notify->info->owner == &scon->cbpeer;
+ const char *text_data;
+ size_t text_size;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ {
+ /* Skip self-updates to avoid clipboard manager conflicts */
+ if (self_update) {
+ return;
+ }
+
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ return;
+ }
+
+ /* Check if this is completion of our pending request */
+ if (pending_request && pending_request->info == notify->info &&
+ pending_request->type == QEMU_CLIPBOARD_TYPE_TEXT) {
+ sdl2_clipboard_clear_pending();
+ }
+
+ /* Check if data is available, request asynchronously if not */
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
+ if (!pending_request) {
+ pending_request = g_new0(SDLClipboardRequest, 1);
+ pending_request->scon = scon;
+ pending_request->info = qemu_clipboard_info_ref(notify->info);
+ pending_request->type = QEMU_CLIPBOARD_TYPE_TEXT;
+ qemu_clipboard_request(notify->info, QEMU_CLIPBOARD_TYPE_TEXT);
+ }
+ return;
+ }
+
+ /* Process available data */
+ text_size = notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].size;
+ if (text_size == 0) {
+ return;
+ }
+
+ text_data = (const char *)notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data;
+
+ /* Ensure null termination for SDL clipboard */
+ g_autofree char *text = g_strndup(text_data, text_size);
+ if (text && text[0] != '\0') {
+ SDL_SetClipboardText(text);
+ } else if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to allocate memory for clipboard text\n");
+ }
+ break;
+ }
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ sdl2_clipboard_clear_pending();
+ break;
+ }
+}
+
+static void sdl2_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ g_autofree char *text = NULL;
+
+ if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
+ return;
+ }
+
+ text = SDL_GetClipboardText();
+ if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to get clipboard text: %s\n",
+ SDL_GetError());
+ return;
+ }
+
+ qemu_clipboard_set_data(info->owner, info, type,
+ strlen(text), text, true);
+}
+
+void sdl2_clipboard_init(struct sdl2_console *scon)
+{
+ scon->cbpeer.name = "sdl2-clipboard";
+ scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
+ scon->cbpeer.request = sdl2_clipboard_request;
+
+ qemu_clipboard_peer_register(&scon->cbpeer);
+}
+
+void sdl2_clipboard_handle_request(struct sdl2_console *scon)
+{
+ g_autofree char *text = NULL;
+ QemuClipboardInfo *info;
+
+ text = SDL_GetClipboardText();
+ if (!text) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to get clipboard text: %s\n",
+ SDL_GetError());
+ return;
+ }
+
+ if (text[0] == '\0') {
+ return; /* Ignore empty clipboard */
+ }
+
+ info = qemu_clipboard_info_new(&scon->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+ qemu_clipboard_set_data(&scon->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
+ strlen(text), text, true);
+ qemu_clipboard_info_unref(info);
+}
+
+#endif /* CONFIG_SDL_CLIPBOARD */
diff --git a/ui/sdl2.c b/ui/sdl2.c
index cda4293a5..00a17b68a 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -691,6 +691,11 @@ void sdl2_poll_events(struct sdl2_console *scon)
case SDL_WINDOWEVENT:
handle_windowevent(ev);
break;
+#ifdef CONFIG_SDL_CLIPBOARD
+ case SDL_CLIPBOARDUPDATE:
+ sdl2_clipboard_handle_request(scon);
+ break;
+#endif
default:
break;
}
@@ -901,6 +906,10 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
}
register_displaychangelistener(&sdl2_console[i].dcl);
+#ifdef CONFIG_SDL_CLIPBOARD
+ sdl2_clipboard_init(&sdl2_console[i]);
+#endif
+
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH] ui/sdl2: Add SDL clipboard support
2025-07-31 17:42 [PATCH] ui/sdl2: Add SDL clipboard support startergo via
@ 2025-08-05 14:27 ` Marc-André Lureau
2025-08-08 19:08 ` startergo
0 siblings, 1 reply; 6+ messages in thread
From: Marc-André Lureau @ 2025-08-05 14:27 UTC (permalink / raw)
To: startergo
Cc: qemu-devel, Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Kamay Xutax
Hi
On Tue, Aug 5, 2025 at 5:33 PM startergo via <qemu-devel@nongnu.org> wrote:
>
> Implement bidirectional clipboard integration between QEMU and host
> system when using the SDL display backend. This allows seamless
> copy-paste operations between the guest and host environments.
>
> Features:
> - Bidirectional clipboard sync (guest ↔ host)
> - Async clipboard request handling to prevent blocking
> - Self-update detection to avoid clipboard manager conflicts
> - Configurable via --enable-sdl-clipboard build option
> - Text-only clipboard support (following existing QEMU patterns)
>
> The implementation follows the same patterns used by the existing
> GTK and VNC clipboard implementations, integrating with QEMU's
> clipboard subsystem through QemuClipboardPeer.
>
> Tested on macOS with successful build and runtime clipboard
> functionality verification.
>
> Co-authored-by: Kamay Xutax <admin@xutaxkamay.com>
> Signed-off-by: startergo <startergo@protonmail.com>
Thanks for sending a patch that can be applied with git am !
Next time, make sure it passes checkpatch too:
https://www.qemu.org/docs/master/devel/submitting-a-patch.html#writing-your-patches
> ---
> include/ui/sdl2.h | 12 ++++
> meson.build | 3 +
> meson_options.txt | 2 +
> ui/meson.build | 3 +
> ui/sdl2-clipboard.c | 154 ++++++++++++++++++++++++++++++++++++++++++++
> ui/sdl2.c | 9 +++
> 6 files changed, 183 insertions(+)
> create mode 100644 ui/sdl2-clipboard.c
>
> diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
> index dbe6e3d973..0cadbe8c1c 100644
> --- a/include/ui/sdl2.h
> +++ b/include/ui/sdl2.h
> @@ -21,6 +21,10 @@
> # include <SDL_image.h>
> #endif
>
> +#ifdef CONFIG_SDL_CLIPBOARD
> +#include "ui/clipboard.h"
> +#endif
> +
> #include "ui/kbd-state.h"
> #ifdef CONFIG_OPENGL
> # include "ui/egl-helpers.h"
> @@ -45,6 +49,9 @@ struct sdl2_console {
> bool gui_keysym;
> SDL_GLContext winctx;
> QKbdState *kbd;
> +#ifdef CONFIG_SDL_CLIPBOARD
> + QemuClipboardPeer cbpeer;
> +#endif
> #ifdef CONFIG_OPENGL
> QemuGLShader *gls;
> egl_fb guest_fb;
> @@ -97,4 +104,9 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
> void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
> uint32_t x, uint32_t y, uint32_t w, uint32_t h);
>
> +#ifdef CONFIG_SDL_CLIPBOARD
> +void sdl2_clipboard_init(struct sdl2_console *scon);
> +void sdl2_clipboard_handle_request(struct sdl2_console *scon);
> +#endif
> +
> #endif /* SDL2_H */
> diff --git a/meson.build b/meson.build
> index 41f68d3806..4a37df9669 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -1596,6 +1596,8 @@ else
> sdl_image = not_found
> endif
>
> +have_sdl_clipboard = sdl.found() and get_option('sdl_clipboard')
you should handle the option the same way as gtk_clipboard: fail if
requested but sdl not enabled, make it a feature, disabled by default
? etc
> +
> rbd = not_found
> if not get_option('rbd').auto() or have_block
> librados = cc.find_library('rados', required: get_option('rbd'))
> @@ -2511,6 +2513,7 @@ config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
> config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
> config_host_data.set('CONFIG_SDL', sdl.found())
> config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
> +config_host_data.set('CONFIG_SDL_CLIPBOARD', have_sdl_clipboard)
> config_host_data.set('CONFIG_SECCOMP', seccomp.found())
> if seccomp.found()
> config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
> diff --git a/meson_options.txt b/meson_options.txt
> index 59d973bca0..be2cba3a30 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -212,6 +212,8 @@ option('sdl', type : 'feature', value : 'auto',
> description: 'SDL user interface')
> option('sdl_image', type : 'feature', value : 'auto',
> description: 'SDL Image support for icons')
> +option('sdl_clipboard', type : 'boolean', value : true,
> + description: 'SDL clipboard support')
> option('seccomp', type : 'feature', value : 'auto',
> description: 'seccomp support')
> option('smartcard', type : 'feature', value : 'auto',
> diff --git a/ui/meson.build b/ui/meson.build
> index 35fb04cadf..6d1bf3477e 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -126,6 +126,9 @@ if sdl.found()
> 'sdl2-input.c',
> 'sdl2.c',
> ))
> + if have_sdl_clipboard
> + sdl_ss.add(files('sdl2-clipboard.c'))
> + endif
> sdl_ss.add(when: opengl, if_true: files('sdl2-gl.c'))
> sdl_ss.add(when: x11, if_true: files('x_keymap.c'))
> ui_modules += {'sdl' : sdl_ss}
> diff --git a/ui/sdl2-clipboard.c b/ui/sdl2-clipboard.c
> new file mode 100644
> index 0000000000..e50ff11d5a
> --- /dev/null
> +++ b/ui/sdl2-clipboard.c
> @@ -0,0 +1,154 @@
> +/*
> + * SDL UI -- clipboard support (improved async version)
> + *
drop the "(improved async version)"
> + * Copyright (C) 2023 Kamay Xutax <admin@xutaxkamay.com>
> + * Copyright (C) 2025 startergo <startergo@protonmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "ui/console.h"
> +#include "ui/clipboard.h"
> +#include "ui/sdl2.h"
> +#include "qemu/log.h"
> +
> +#ifdef CONFIG_SDL_CLIPBOARD
this condition is unnecessary if the unit is already filtered out by meson
> +
> +/* Track pending clipboard requests to handle async data */
> +typedef struct {
> + struct sdl2_console *scon;
> + QemuClipboardInfo *info;
> + QemuClipboardType type;
> +} SDLClipboardRequest;
> +
> +static SDLClipboardRequest *pending_request = NULL;
> +
> +static void sdl2_clipboard_clear_pending(void)
> +{
> + if (pending_request) {
> + if (pending_request->info) {
> + qemu_clipboard_info_unref(pending_request->info);
> + }
> + g_free(pending_request);
> + pending_request = NULL;
or g_clear_pointer(&pending_request, g_free)
> + }
> +}
> +
> +static void sdl2_clipboard_notify(Notifier *notifier, void *data)
> +{
> + QemuClipboardNotify *notify = data;
> + struct sdl2_console *scon =
> + container_of(notifier, struct sdl2_console, cbpeer.notifier);
> + bool self_update = notify->info->owner == &scon->cbpeer;
> + const char *text_data;
> + size_t text_size;
> +
> + switch (notify->type) {
> + case QEMU_CLIPBOARD_UPDATE_INFO:
> + {
> + /* Skip self-updates to avoid clipboard manager conflicts */
> + if (self_update) {
> + return;
> + }
> +
> + if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
> + return;
> + }
> +
> + /* Check if this is completion of our pending request */
> + if (pending_request && pending_request->info == notify->info &&
> + pending_request->type == QEMU_CLIPBOARD_TYPE_TEXT) {
> + sdl2_clipboard_clear_pending();
> + }
> +
> + /* Check if data is available, request asynchronously if not */
> + if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
> + if (!pending_request) {
> + pending_request = g_new0(SDLClipboardRequest, 1);
> + pending_request->scon = scon;
> + pending_request->info = qemu_clipboard_info_ref(notify->info);
> + pending_request->type = QEMU_CLIPBOARD_TYPE_TEXT;
> + qemu_clipboard_request(notify->info, QEMU_CLIPBOARD_TYPE_TEXT);
There is a risk of requesting clipboard text, getting nothing back,
and going in a loop. It should handle that.
> + }
> + return;
> + }
> +
> + /* Process available data */
> + text_size = notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].size;
> + if (text_size == 0) {
> + return;
> + }
> +
> + text_data = (const char *)notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data;
> +
> + /* Ensure null termination for SDL clipboard */
> + g_autofree char *text = g_strndup(text_data, text_size);
> + if (text && text[0] != '\0') {
> + SDL_SetClipboardText(text);
> + } else if (!text) {
At this point text_data != NULL and text_size != 0, in this case
g_strndup() will never return NULL. If OOM, the process will abort()
before that.
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "SDL clipboard: Failed to allocate memory for clipboard text\n");
you will drop this then
> + }
> + break;
> + }
> + case QEMU_CLIPBOARD_RESET_SERIAL:
> + sdl2_clipboard_clear_pending();
you can ignore this, just like gtk-clipboard
> + break;
> + }
> +}
> +
> +static void sdl2_clipboard_request(QemuClipboardInfo *info,
> + QemuClipboardType type)
> +{
> + g_autofree char *text = NULL;
> +
> + if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
> + return;
> + }
> +
> + text = SDL_GetClipboardText();
> + if (!text) {
SDL_GetClipboardText doc says: Returns the clipboard text on success
or an empty string on failure..Caller must call SDL_free() on the
returned pointer when done with it (even if there was an error).
if (!text || !text[0])
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "SDL clipboard: Failed to get clipboard text: %s\n",
> + SDL_GetError());
use warn_report() && call SDL_free(text)
> + return;
> + }
> +
> + qemu_clipboard_set_data(info->owner, info, type,
> + strlen(text), text, true);
> +}
> +
> +void sdl2_clipboard_init(struct sdl2_console *scon)
> +{
> + scon->cbpeer.name = "sdl2-clipboard";
> + scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
> + scon->cbpeer.request = sdl2_clipboard_request;
> +
> + qemu_clipboard_peer_register(&scon->cbpeer);
> +}
> +
> +void sdl2_clipboard_handle_request(struct sdl2_console *scon)
The function should be renamed, see below.
> +{
> + g_autofree char *text = NULL;
> + QemuClipboardInfo *info;
> +
> + text = SDL_GetClipboardText();
instead of requesting the content immediately here, we should wait for
an actual guest/peer request. This will remove the duplication of code
to actually get the content.
> + if (!text) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "SDL clipboard: Failed to get clipboard text: %s\n",
> + SDL_GetError());
> + return;
> + }
> +
> + if (text[0] == '\0') {
> + return; /* Ignore empty clipboard */
> + }
> +
> + info = qemu_clipboard_info_new(&scon->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
> + qemu_clipboard_set_data(&scon->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
> + strlen(text), text, true);
This will simply be qemu_clipboard_update(info)
> + qemu_clipboard_info_unref(info);
> +}
> +
> +#endif /* CONFIG_SDL_CLIPBOARD */
> diff --git a/ui/sdl2.c b/ui/sdl2.c
> index cda4293a53..00a17b68a7 100644
> --- a/ui/sdl2.c
> +++ b/ui/sdl2.c
> @@ -691,6 +691,11 @@ void sdl2_poll_events(struct sdl2_console *scon)
> case SDL_WINDOWEVENT:
> handle_windowevent(ev);
> break;
> +#ifdef CONFIG_SDL_CLIPBOARD
> + case SDL_CLIPBOARDUPDATE:
> + sdl2_clipboard_handle_request(scon);
It's not a request, it's an update. Please rename the function accordingly.
> + break;
> +#endif
> default:
> break;
> }
> @@ -901,6 +906,10 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
> }
> register_displaychangelistener(&sdl2_console[i].dcl);
>
> +#ifdef CONFIG_SDL_CLIPBOARD
> + sdl2_clipboard_init(&sdl2_console[i]);
> +#endif
> +
> #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
> if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
> #if defined(SDL_VIDEO_DRIVER_WINDOWS)
> --
> 2.50.1
>
>
--
Marc-André Lureau
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH] ui/sdl2: Add SDL clipboard support
2025-08-05 14:27 ` Marc-André Lureau
@ 2025-08-08 19:08 ` startergo
0 siblings, 0 replies; 6+ messages in thread
From: startergo @ 2025-08-08 19:08 UTC (permalink / raw)
To: marcandre.lureau; +Cc: qemu-devel, pbonzini, berrange, philmd, admin, startergo
Implement bidirectional clipboard integration between QEMU and host
system when using the SDL display backend. This allows seamless
copy-paste operations between the guest and host environments.
Features:
- Bidirectional clipboard sync (guest ↔ host)
- Async clipboard request handling to prevent blocking
- Self-update detection to avoid clipboard manager conflicts
- Screen lock/unlock handling for improved reliability
- Configurable via --enable-sdl-clipboard build option
- Text-only clipboard support (following existing QEMU patterns)
The implementation follows the same patterns used by the existing
GTK and VNC clipboard implementations, integrating with QEMU's
clipboard subsystem through QemuClipboardPeer.
Changes in v2:
- Fixed meson option to use 'feature' type instead of 'boolean'
- Improved error handling with warn_report() and proper SDL_free()
- Added protection against async request loops
- Simplified clipboard data handling
- Fixed line length violations for checkpatch compliance
- Maintained compatibility with existing screen lock handling
Co-authored-by: Kamay Xutax <admin@xutaxkamay.com>
Signed-off-by: startergo <startergo@protonmail.com>
---
include/ui/sdl2.h | 15 ++++
meson.build | 1 +
meson_options.txt | 2 +
ui/meson.build | 6 ++
ui/sdl2-clipboard.c | 148 ++++++++++++++++++++++++++++++++++++++++++++
ui/sdl2.c | 30 +++++++++
6 files changed, 202 insertions(+)
create mode 100644 ui/sdl2-clipboard.c
--- /tmp/qemu-pristine/qemu-10.0.0/include/ui/sdl2.h 2025-04-22 16:26:11
+++ qemu-clean-workspace/include/ui/sdl2.h 2025-08-07 14:01:28
@@ -21,6 +21,10 @@
# include <SDL_image.h>
#endif
+#ifdef CONFIG_SDL_CLIPBOARD
+#include "ui/clipboard.h"
+#endif
+
#include "ui/kbd-state.h"
#ifdef CONFIG_OPENGL
# include "ui/egl-helpers.h"
@@ -45,6 +49,11 @@
bool gui_keysym;
SDL_GLContext winctx;
QKbdState *kbd;
+#ifdef CONFIG_SDL_CLIPBOARD
+ QemuClipboardPeer cbpeer;
+ bool clipboard_active;
+ uint32_t last_focus_time;
+#endif
#ifdef CONFIG_OPENGL
QemuGLShader *gls;
egl_fb guest_fb;
@@ -97,4 +106,11 @@
void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
+#ifdef CONFIG_SDL_CLIPBOARD
+void sdl2_clipboard_init(struct sdl2_console *scon);
+void sdl2_clipboard_handle_focus_change(struct sdl2_console *scon,
+ bool gained_focus);
+void sdl2_clipboard_handle_request(struct sdl2_console *scon);
+#endif
+
#endif /* SDL2_H */
--- /tmp/qemu-pristine/qemu-10.0.0/meson.build 2025-04-22 16:26:11
+++ qemu-clean-workspace/meson.build 2025-08-07 14:01:28
@@ -1594,6 +1594,11 @@
get_option('sdl').disabled() ? 'disabled' : 'not found'))
endif
sdl_image = not_found
+endif
+
+have_sdl_clipboard = false
+if sdl.found()
+ have_sdl_clipboard = get_option('sdl_clipboard').require(sdl.found()).allowed()
endif
rbd = not_found
@@ -2511,6 +2516,7 @@
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
config_host_data.set('CONFIG_SDL', sdl.found())
config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
+config_host_data.set('CONFIG_SDL_CLIPBOARD', have_sdl_clipboard)
config_host_data.set('CONFIG_SECCOMP', seccomp.found())
if seccomp.found()
config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
--- /tmp/qemu-pristine/qemu-10.0.0/meson_options.txt 2025-04-22 16:26:11
+++ qemu-clean-workspace/meson_options.txt 2025-08-07 14:01:28
@@ -212,6 +212,8 @@
description: 'SDL user interface')
option('sdl_image', type : 'feature', value : 'auto',
description: 'SDL Image support for icons')
+option('sdl_clipboard', type : 'feature', value : 'auto',
+ description: 'SDL clipboard support')
option('seccomp', type : 'feature', value : 'auto',
description: 'seccomp support')
option('smartcard', type : 'feature', value : 'auto',
--- /tmp/qemu-pristine/qemu-10.0.0/ui/meson.build 2025-04-22 16:26:11
+++ qemu-clean-workspace/ui/meson.build 2025-08-07 14:01:28
@@ -126,6 +126,9 @@
'sdl2-input.c',
'sdl2.c',
))
+ if have_sdl_clipboard
+ sdl_ss.add(files('sdl2-clipboard.c'))
+ endif
sdl_ss.add(when: opengl, if_true: files('sdl2-gl.c'))
sdl_ss.add(when: x11, if_true: files('x_keymap.c'))
ui_modules += {'sdl' : sdl_ss}
--- /dev/null 2025-08-07 14:08:08
+++ qemu-clean-workspace/ui/sdl2-clipboard.c 2025-08-07 14:01:28
@@ -0,0 +1,206 @@
+/*
+ * SDL UI -- clipboard support
+ *
+ * Copyright (C) 2023 Kamay Xutax <admin@xutaxkamay.com>
+ * Copyright (C) 2025 startergo <startergo@protonmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "ui/console.h"
+#include "ui/clipboard.h"
+#include "ui/sdl2.h"
+
+/* Track pending clipboard requests to handle async data */
+typedef struct {
+ struct sdl2_console *scon;
+ QemuClipboardInfo *info;
+ QemuClipboardType type;
+ bool request_sent;
+} SDLClipboardRequest;
+
+static SDLClipboardRequest *pending_request;
+
+static void sdl2_clipboard_clear_pending(void)
+{
+ if (pending_request) {
+ if (pending_request->info) {
+ qemu_clipboard_info_unref(pending_request->info);
+ }
+ g_clear_pointer(&pending_request, g_free);
+ }
+}
+
+static void sdl2_clipboard_reset_state(struct sdl2_console *scon)
+{
+ /* Clear any pending requests when clipboard state is reset */
+ sdl2_clipboard_clear_pending();
+
+ /* Force a fresh clipboard check after reconnection */
+ if (scon->clipboard_active) {
+ scon->last_focus_time = SDL_GetTicks();
+ }
+}
+
+static void sdl2_clipboard_notify(Notifier *notifier, void *data)
+{
+ QemuClipboardNotify *notify = data;
+ struct sdl2_console *scon =
+ container_of(notifier, struct sdl2_console, cbpeer.notifier);
+ bool self_update = notify->info->owner == &scon->cbpeer;
+ const char *text_data;
+ size_t text_size;
+
+ /* Skip processing if clipboard is not active (e.g., during screen lock) */
+ if (!scon->clipboard_active) {
+ return;
+ }
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ {
+ /* Skip self-updates to avoid clipboard manager conflicts */
+ if (self_update) {
+ return;
+ }
+
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ return;
+ }
+
+ /* Check if this is completion of our pending request */
+ if (pending_request && pending_request->info == notify->info &&
+ pending_request->type == QEMU_CLIPBOARD_TYPE_TEXT) {
+ sdl2_clipboard_clear_pending();
+ }
+
+ /* Check if data is available, request asynchronously if not */
+ if (!notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
+ /*
+ * Protect against request loops by checking if we already
+ * sent a request
+ */
+ if (!pending_request || !pending_request->request_sent) {
+ sdl2_clipboard_clear_pending();
+ pending_request = g_new0(SDLClipboardRequest, 1);
+ pending_request->scon = scon;
+ pending_request->info =
+ qemu_clipboard_info_ref(notify->info);
+ pending_request->type = QEMU_CLIPBOARD_TYPE_TEXT;
+ pending_request->request_sent = true;
+ qemu_clipboard_request(notify->info,
+ QEMU_CLIPBOARD_TYPE_TEXT);
+ }
+ return;
+ }
+
+ /* Process available data */
+ text_size = notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].size;
+ if (text_size == 0) {
+ return;
+ }
+
+ text_data = (const char *)
+ notify->info->types[QEMU_CLIPBOARD_TYPE_TEXT].data;
+
+ /* Ensure null termination for SDL clipboard */
+ g_autofree char *text = g_strndup(text_data, text_size);
+ if (text && text[0] != '\0') {
+ if (SDL_SetClipboardText(text) < 0) {
+ warn_report("SDL clipboard: Failed to set clipboard "
+ "text: %s", SDL_GetError());
+ }
+ }
+ break;
+ }
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ sdl2_clipboard_reset_state(scon);
+ break;
+ }
+}
+
+static void sdl2_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ char *text = NULL;
+
+ if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
+ return;
+ }
+
+ text = SDL_GetClipboardText();
+ if (!text || !text[0]) {
+ if (text) {
+ warn_report("SDL clipboard: Failed to get clipboard text: %s",
+ SDL_GetError());
+ SDL_free(text);
+ }
+ return;
+ }
+
+ qemu_clipboard_set_data(info->owner, info, type,
+ strlen(text), text, true);
+ SDL_free(text);
+}
+
+void sdl2_clipboard_init(struct sdl2_console *scon)
+{
+ scon->cbpeer.name = "sdl2-clipboard";
+ scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
+ scon->cbpeer.request = sdl2_clipboard_request;
+ scon->clipboard_active = true;
+ scon->last_focus_time = SDL_GetTicks();
+
+ qemu_clipboard_peer_register(&scon->cbpeer);
+}
+
+void sdl2_clipboard_handle_focus_change(struct sdl2_console *scon,
+ bool gained_focus)
+{
+ uint32_t current_time = SDL_GetTicks();
+
+ if (gained_focus) {
+ /* Reactivate clipboard after regaining focus */
+ scon->clipboard_active = true;
+ scon->last_focus_time = current_time;
+
+ /* Clear any stale pending requests */
+ sdl2_clipboard_clear_pending();
+
+ /* Force a fresh clipboard sync after focus is regained */
+ sdl2_clipboard_handle_request(scon);
+ } else {
+ /* Deactivate clipboard when losing focus to prevent conflicts */
+ scon->clipboard_active = false;
+ sdl2_clipboard_clear_pending();
+ }
+}
+
+void sdl2_clipboard_handle_request(struct sdl2_console *scon)
+{
+ char *text = NULL;
+ QemuClipboardInfo *info;
+
+ /* Skip if clipboard is not active */
+ if (!scon->clipboard_active) {
+ return;
+ }
+
+ text = SDL_GetClipboardText();
+ if (!text || !text[0]) {
+ if (text) {
+ SDL_free(text);
+ }
+ return; /* Ignore empty clipboard */
+ }
+
+ info = qemu_clipboard_info_new(&scon->cbpeer,
+ QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+ qemu_clipboard_set_data(&scon->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
+ strlen(text), text, true);
+ qemu_clipboard_update(info);
+ qemu_clipboard_info_unref(info);
+ SDL_free(text);
+}
--- /tmp/qemu-pristine/qemu-10.0.0/ui/sdl2.c 2025-04-22 16:26:11
+++ qemu-clean-workspace/ui/sdl2.c 2025-08-07 14:01:28
@@ -606,11 +606,17 @@
* key is released.
*/
scon->ignore_hotkeys = get_mod_state();
+#ifdef CONFIG_SDL_CLIPBOARD
+ sdl2_clipboard_handle_focus_change(scon, true);
+#endif
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (gui_grab && !gui_fullscreen) {
sdl_grab_end(scon);
}
+#ifdef CONFIG_SDL_CLIPBOARD
+ sdl2_clipboard_handle_focus_change(scon, false);
+#endif
break;
case SDL_WINDOWEVENT_RESTORED:
update_displaychangelistener(&scon->dcl, GUI_REFRESH_INTERVAL_DEFAULT);
@@ -691,6 +697,11 @@
case SDL_WINDOWEVENT:
handle_windowevent(ev);
break;
+#ifdef CONFIG_SDL_CLIPBOARD
+ case SDL_CLIPBOARDUPDATE:
+ sdl2_clipboard_handle_request(scon);
+ break;
+#endif
default:
break;
}
@@ -901,6 +912,10 @@
}
register_displaychangelistener(&sdl2_console[i].dcl);
+#ifdef CONFIG_SDL_CLIPBOARD
+ sdl2_clipboard_init(&sdl2_console[i]);
+#endif
+
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-08-08 19:09 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-31 17:42 [PATCH] ui/sdl2: Add SDL clipboard support startergo via
2025-08-05 14:27 ` Marc-André Lureau
2025-08-08 19:08 ` startergo
-- strict thread matches above, loose matches on Subject: below --
2025-08-05 11:53 startergo
2025-08-05 12:20 ` Marc-André Lureau
2025-08-05 13:16 macbookpro
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).