Hi
On Wed, Nov 8, 2023 at 7:56 PM Kamay Xutax <admin@xutaxkamay.com> wrote:
Signed-off-by: Kamay Xutax <admin@xutaxkamay.com>
---
include/ui/sdl2.h | 5 ++
meson.build | 1 +
ui/meson.build | 1 +
ui/sdl2-clipboard.c | 147 ++++++++++++++++++++++++++++++++++++++++++++
ui/sdl2.c | 8 +++
5 files changed, 162 insertions(+)
create mode 100644 ui/sdl2-clipboard.c
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index e3acc7c82a..120fe6f856 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -21,6 +21,7 @@
# include <SDL_image.h>
#endif
+#include "ui/clipboard.h"
#include "ui/kbd-state.h"
#ifdef CONFIG_OPENGL
# include "ui/egl-helpers.h"
@@ -51,6 +52,7 @@ struct sdl2_console {
bool y0_top;
bool scanout_mode;
#endif
+ QemuClipboardPeer cbpeer;
};
void sdl2_window_create(struct sdl2_console *scon);
@@ -70,6 +72,9 @@ void sdl2_2d_redraw(struct sdl2_console *scon);
bool sdl2_2d_check_format(DisplayChangeListener *dcl,
pixman_format_code_t format);
+void sdl2_clipboard_handle_request(struct sdl2_console *scon);
+void sdl2_clipboard_init(struct sdl2_console *scon);
+
void sdl2_gl_update(DisplayChangeListener *dcl,
int x, int y, int w, int h);
void sdl2_gl_switch(DisplayChangeListener *dcl,
diff --git a/meson.build b/meson.build
index 4848930680..1358d14b2e 100644
--- a/meson.build
+++ b/meson.build
@@ -2157,6 +2157,7 @@ config_host_data.set('CONFIG_RDMA', rdma.found())
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_CLIPBOARD', sdl.found())
'gtk_clipboard' option is there because it has some issues with the
glib loop - see https://gitlab.com/qemu-project/qemu/-/issues/1150.
Apparently this code could have similar kind of issues, since it's
reentering the main loop too. it might be worth to have a similar
option and disclaimer...
config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
config_host_data.set('CONFIG_SECCOMP', seccomp.found())
if seccomp.found()
diff --git a/ui/meson.build b/ui/meson.build
index 0ccb3387ee..0cadd1a18f 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -125,6 +125,7 @@ if sdl.found()
sdl_ss = ss.source_set()
sdl_ss.add(sdl, sdl_image, pixman, glib, files(
'sdl2-2d.c',
+ 'sdl2-clipboard.c',
'sdl2-input.c',
'sdl2.c',
))
diff --git a/ui/sdl2-clipboard.c b/ui/sdl2-clipboard.c
new file mode 100644
index 0000000000..405bb9ea8b
--- /dev/null
+++ b/ui/sdl2-clipboard.c
@@ -0,0 +1,147 @@
+/*
+ * SDL UI -- clipboard support
+ *
+ * Copyright (C) 2023 Kamay Xutax <admin@xutaxkamay.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "ui/sdl2.h"
+
+static void sdl2_clipboard_update(struct sdl2_console *scon,
+ QemuClipboardInfo *info)
+{
+ bool self_update = info->owner == &scon->cbpeer;
+ char *text;
+ size_t text_size;
+
+ /*
+ * In case of a self update,
+ * set again the text in SDL
+ *
+ * This is a workaround for hosts that have clipboard history
+ * or when they're copying again something,
+ * so that SDL can accept a new request from the host
+ * and make a new SDL_CLIPBOARDUPDATE event
+ */
+
+ if (self_update) {
+ text = SDL_GetClipboardText();
+ SDL_SetClipboardText(text);
+ SDL_free(text);
+ return;
+ }
Isn't this basically doing the work of a clipboard manager? it takes
the current clipboard data and makes qemu the new owner. It looks like
it could also run in a loop quite easily if it fights with a manager.
+
+ if (!info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ return;
+ }
+
+ info = qemu_clipboard_info_ref(info);
+ qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
+
+ while (info == qemu_clipboard_info(info->selection) &&
+ info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
+ info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
+ main_loop_wait(false);
Reentering the loop, that's annoying... same as gtk-clipboard.c..
Have you tried to defer the handling of the update? That will add
extra state & logic though.
+ }
+
+ /* clipboard info changed while waiting for data */
+ if (info != qemu_clipboard_info(info->selection)) {
+ qemu_clipboard_info_unref(info);
+ return;
+ }
+
+ /* text is not null terminated in cb info, so we need to copy it */
+ text_size = info->types[QEMU_CLIPBOARD_TYPE_TEXT].size;
hmm, I wonder why.. isn't it something we could fix from
qemu_clipboard_set_data() callers?
(gtk_selection_data_set_text() doc says it should be \0 terminated,
although it seems not required when len is given).
I am not sure if the spice/vdagent ensures \0 terminated strings, but
the qemu code could adjust it as necessary.
+ if (!text_size) {
+ qemu_clipboard_info_unref(info);
+ return;
+ }
+
+ text = malloc(text_size + 1);
We use g_malloc() and g_free() (even better with g_autofree).
+
+ if (!text) {
Then, no need to check for NULL results (it aborts on OOM).
+ qemu_clipboard_info_unref(info);
+ return;
+ }
+
+ text[text_size] = 0;
+ memcpy(text, info->types[QEMU_CLIPBOARD_TYPE_TEXT].data, text_size);
+ /* unref as soon we copied the text */
+ qemu_clipboard_info_unref(info);
+ SDL_SetClipboardText(text);
+
+ free(text);
+}
+
+static void sdl2_clipboard_notify(Notifier *notifier,
+ void *data)
+{
+ QemuClipboardNotify *notify = data;
+ struct sdl2_console *scon =
+ container_of(notifier, struct sdl2_console, cbpeer.notifier);
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ sdl2_clipboard_update(scon, notify->info);
+ break;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ break;
+ }
+}
+
+static void sdl2_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ struct sdl2_console *scon =
+ container_of(info->owner, struct sdl2_console, cbpeer);
+ char *text;
+
+ switch (type) {
+ case QEMU_CLIPBOARD_TYPE_TEXT:
+ if (!SDL_HasClipboardText()) {
+ return;
+ }
+
+ text = SDL_GetClipboardText();
+ qemu_clipboard_set_data(&scon->cbpeer, info, type,
+ strlen(text), text, true);
strlen() + 1 to have \0 then? (other backends would need similar fix)
+
+ SDL_free(text);
+ break;
+ default:
+ return;
+ }
+}
+
+void sdl2_clipboard_handle_request(struct sdl2_console *scon)
+{
+ g_autoptr(QemuClipboardInfo) info =
+ qemu_clipboard_info_new(&scon->cbpeer,
+ QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+
+ sdl2_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
+}
+
+void sdl2_clipboard_init(struct sdl2_console *scon)
+{
+ scon->cbpeer.name = "sdl2";
+ scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
+ /* requests will be handled from the SDL event loop */
+ qemu_clipboard_peer_register(&scon->cbpeer);
+}
diff --git a/ui/sdl2.c b/ui/sdl2.c
index fbfdb64e90..d674add7c5 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -702,6 +702,11 @@ void sdl2_poll_events(struct sdl2_console *scon)
case SDL_WINDOWEVENT:
handle_windowevent(ev);
break;
+#if defined(CONFIG_SDL_CLIPBOARD)
+ case SDL_CLIPBOARDUPDATE:
+ sdl2_clipboard_handle_request(scon);
+ break;
+#endif
default:
break;
}
@@ -922,6 +927,9 @@ static void sdl2_display_init(DisplayState *ds,
DisplayOptions *o)
qemu_console_set_window_id(con, info.info.x11.window);
#endif
}
+#endif
+#if defined(CONFIG_SDL_CLIPBOARD)
+ sdl2_clipboard_init(&sdl2_console[i]);
#endif
}
--
2.41.0