From: macbookpro <macbookpro@macbookpros-macbook-pro.local>
To: qemu-devel@nongnu.org
Cc: pbonzini@redhat.com, marcandre.lureau@redhat.com
Subject: [PATCH] ui/sdl2: Add SDL clipboard support
Date: Tue, 5 Aug 2025 14:16:17 +0100 [thread overview]
Message-ID: <aJIEIS2jMQz_7skd@macbookpros-MacBook-Pro.local> (raw)
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
next reply other threads:[~2025-08-05 14:08 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-05 13:16 macbookpro [this message]
-- strict thread matches above, loose matches on Subject: below --
2025-08-05 11:53 [PATCH] ui/sdl2: Add SDL clipboard support startergo
2025-08-05 12:20 ` Marc-André Lureau
2025-07-31 17:42 startergo via
2025-08-05 14:27 ` Marc-André Lureau
2025-08-08 19:08 ` startergo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aJIEIS2jMQz_7skd@macbookpros-MacBook-Pro.local \
--to=macbookpro@macbookpros-macbook-pro.local \
--cc=marcandre.lureau@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).