include/ui/sdl2.h | 15 ++++ meson.build | 3 + meson_options.txt | 2 + ui/meson.build | 3 + ui/sdl2-clipboard.c | 208 ++++++++++++++++++++++++++++++++++++++++++++ ui/sdl2.c | 15 ++++ 6 files changed, 246 insertions(+) create mode 100644 ui/sdl2-clipboard.c
This patch implements SDL clipboard integration for QEMU's SDL2 UI backend,
specifically addressing the issue where clipboard functionality becomes
unreliable during host screen lock/unlock scenarios.
The implementation provides:
- Bidirectional clipboard synchronization between guest and host
- Robust screen lock/unlock handling to prevent clipboard conflicts
- Asynchronous clipboard request processing
- Proper resource cleanup and error handling
This addresses a common usability issue where copy/paste functionality
stops working after the host screen is locked and unlocked, particularly
noticeable on macOS systems.
The patch adds a new build option --enable-sdl-clipboard (enabled by default)
to allow users to disable the feature if needed.
Tested on QEMU 10.0.0 and master branch, passes checkpatch with zero errors.
Signed-off-by: startergo <startergo@protonmail.com>
---
include/ui/sdl2.h | 15 ++++
meson.build | 3 +
meson_options.txt | 2 +
ui/meson.build | 3 +
ui/sdl2-clipboard.c | 208 ++++++++++++++++++++++++++++++++++++++++++++
ui/sdl2.c | 15 ++++
6 files changed, 246 insertions(+)
create mode 100644 ui/sdl2-clipboard.c
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index dbe6e3d97..e73f83259 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,11 @@ struct sdl2_console {
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,10 @@ 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_focus_change(struct sdl2_console *scon, bool gained_focus);
+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..6cc0fd79c
--- /dev/null
+++ b/ui/sdl2-clipboard.c
@@ -0,0 +1,208 @@
+/*
+ * SDL UI -- clipboard support with screen lock handling
+ *
+ * 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;
+ uint32_t timestamp;
+} 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_free(pending_request);
+ pending_request = NULL;
+ }
+}
+
+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) {
+ 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;
+ pending_request->timestamp = SDL_GetTicks();
+ 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) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SDL clipboard: Failed to set clipboard text: %s\n",
+ SDL_GetError());
+ }
+ } 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_reset_state(scon);
+ 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;
+ 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)
+{
+ g_autofree char *text = NULL;
+ QemuClipboardInfo *info;
+
+ /* Skip if clipboard is not active */
+ if (!scon->clipboard_active) {
+ return;
+ }
+
+ 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..d89ac16dd 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -606,11 +606,17 @@ static void handle_windowevent(SDL_Event *ev)
* 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 @@ 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 +912,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
Hi
On Thu, Aug 7, 2025 at 6:49 PM startergo <startergo@protonmail.com> wrote:
> This patch implements SDL clipboard integration for QEMU's SDL2 UI backend,
> specifically addressing the issue where clipboard functionality becomes
> unreliable during host screen lock/unlock scenarios.
>
> The implementation provides:
> - Bidirectional clipboard synchronization between guest and host
> - Robust screen lock/unlock handling to prevent clipboard conflicts
> - Asynchronous clipboard request processing
> - Proper resource cleanup and error handling
>
> This addresses a common usability issue where copy/paste functionality
> stops working after the host screen is locked and unlocked, particularly
> noticeable on macOS systems.
>
> The patch adds a new build option --enable-sdl-clipboard (enabled by
> default)
> to allow users to disable the feature if needed.
>
> Tested on QEMU 10.0.0 and master branch, passes checkpatch with zero
> errors.
>
There are many things you didn't address since the last review:
https://lore.kernel.org/qemu-devel/CAJ+F1CJyGmYhBoTKg_hWibAQfL0f9-1CDWxJnQVBjaS_iXExeg@mail.gmail.com/
Please
- discuss the review on the ML if it's not clear or if they are not
appropriate
- version your patch
- keep a changelog of the changes
thanks
>
> Signed-off-by: startergo <startergo@protonmail.com>
> ---
> include/ui/sdl2.h | 15 ++++
> meson.build | 3 +
> meson_options.txt | 2 +
> ui/meson.build | 3 +
> ui/sdl2-clipboard.c | 208 ++++++++++++++++++++++++++++++++++++++++++++
> ui/sdl2.c | 15 ++++
> 6 files changed, 246 insertions(+)
> create mode 100644 ui/sdl2-clipboard.c
>
> diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
> index dbe6e3d97..e73f83259 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,11 @@ struct sdl2_console {
> 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,10 @@ 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_focus_change(struct sdl2_console *scon, bool
> gained_focus);
> +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..6cc0fd79c
> --- /dev/null
> +++ b/ui/sdl2-clipboard.c
> @@ -0,0 +1,208 @@
> +/*
> + * SDL UI -- clipboard support with screen lock handling
> + *
> + * 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;
> + uint32_t timestamp;
> +} 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_free(pending_request);
> + pending_request = NULL;
> + }
> +}
> +
> +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) {
> + 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;
> + pending_request->timestamp = SDL_GetTicks();
> + 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) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "SDL clipboard: Failed to set clipboard
> text: %s\n",
> + SDL_GetError());
> + }
> + } 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_reset_state(scon);
> + 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;
> + 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)
> +{
> + g_autofree char *text = NULL;
> + QemuClipboardInfo *info;
> +
> + /* Skip if clipboard is not active */
> + if (!scon->clipboard_active) {
> + return;
> + }
> +
> + 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..d89ac16dd 100644
> --- a/ui/sdl2.c
> +++ b/ui/sdl2.c
> @@ -606,11 +606,17 @@ static void handle_windowevent(SDL_Event *ev)
> * 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 @@ 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 +912,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
>
>
>
© 2016 - 2025 Red Hat, Inc.