2 Commits

Author SHA256 Message Date
48a978b07f Accepting request 1323138 from GNOME:Factory
Upstream patches to fix bsc#1251850. Adds copy and paste ability between host and guest when using VNC Display instead of Spice. (forwarded request 1321847 from charlesa)

OBS-URL: https://build.opensuse.org/request/show/1323138
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/gtk-vnc?expand=0&rev=59
2025-12-17 16:32:11 +00:00
9dc609fc34 Upstream patches to fix bsc#1251850. Adds copy and paste ability between host and guest when using VNC Display instead of Spice.
OBS-URL: https://build.opensuse.org/package/show/GNOME:Factory/gtk-vnc?expand=0&rev=95
2025-12-16 15:30:41 +00:00
10 changed files with 1353 additions and 1 deletions

View File

@@ -0,0 +1,69 @@
Subject: src: introduce a vncclipboard.h header file
From: Lin Ma lma@suse.de Mon Jul 7 12:39:46 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: 4806aa5889ea50508ef25347641c42acc601dd31
This header file defines the formats and the actions agreed upon by the
RFB extended clipboard protocol.
Signed-off-by: Lin Ma <lma@suse.de>
diff --git a/src/meson.build b/src/meson.build
index f5a11cf..941fe70 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -58,6 +58,7 @@ gvnc_headers = [
'vnccolormap.h',
'vncconnection.h',
'vncutil.h',
+ 'vncclipboard.h',
]
install_headers(gvnc_headers + [gvnc_version_header], subdir: 'gvnc-1.0')
diff --git a/src/vncclipboard.h b/src/vncclipboard.h
new file mode 100644
index 0000000..dd59bf4
--- /dev/null
+++ b/src/vncclipboard.h
@@ -0,0 +1,41 @@
+/*
+ * GTK VNC Widget
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef VNC_CLIPBOARD_H
+#define VNC_CLIPBOARD_H
+
+/* Max number of extended clipboard formats we might track(bits 0-15) */
+#define VNC_CLIPBOARD_MAX_FORMATS 16
+
+/* Extended Clipboard Formats */
+#define VNC_CLIPBOARD_FORMAT_UTF8 (1 << 0)
+#define VNC_CLIPBOARD_FORMAT_RTF (1 << 1)
+#define VNC_CLIPBOARD_FORMAT_HTML (1 << 2)
+#define VNC_CLIPBOARD_FORMAT_DIB (1 << 3)
+#define VNC_CLIPBOARD_FORMAT_FILES (1 << 4)
+
+/* Extended Clipboard Actions */
+#define VNC_CLIPBOARD_ACTION_CAPS (1 << 24)
+#define VNC_CLIPBOARD_ACTION_REQUEST (1 << 25)
+#define VNC_CLIPBOARD_ACTION_PEEK (1 << 26)
+#define VNC_CLIPBOARD_ACTION_NOTIFY (1 << 27)
+#define VNC_CLIPBOARD_ACTION_PROVIDE (1 << 28)
+
+#define VNC_CLIPBOARD_ACTION_MASK 0xff000000
+
+#endif /* VNC_CLIPBOARD_H */

View File

@@ -0,0 +1,36 @@
Subject: Add the extended clipboard pseudo-encoding
From: Lin Ma lma@suse.de Mon Jul 7 12:40:53 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: e334dd438907251d7b42615824d9c14ae3452331
Signed-off-by: Lin Ma <lma@suse.de>
--- a/src/vncconnection.h
+++ b/src/vncconnection.h
@@ -131,6 +131,7 @@ typedef enum {
VNC_CONNECTION_ENCODING_EXTENDED_DESKTOP_RESIZE = -308,
VNC_CONNECTION_ENCODING_XVP = -309,
VNC_CONNECTION_ENCODING_ALPHA_CURSOR = -314,
+ VNC_CONNECTION_ENCODING_EXTENDED_CLIPBOARD = 0xC0A1E5CE,
} VncConnectionEncoding;
/**
--- a/src/vncdisplay.c
+++ b/src/vncdisplay.c
@@ -1973,6 +1973,7 @@ static void on_initialized(VncConnection
VNC_CONNECTION_ENCODING_EXTENDED_DESKTOP_RESIZE,
VNC_CONNECTION_ENCODING_DESKTOP_RESIZE,
VNC_CONNECTION_ENCODING_DESKTOP_NAME,
+ VNC_CONNECTION_ENCODING_EXTENDED_CLIPBOARD,
VNC_CONNECTION_ENCODING_LAST_RECT,
VNC_CONNECTION_ENCODING_WMVi,
VNC_CONNECTION_ENCODING_AUDIO,
@@ -1996,7 +1997,7 @@ static void on_initialized(VncConnection
sizeof(gint32) * \
(n_encodings - (i + 1))); \
n_encodings--; \
- VNC_DEBUG("Removed encoding %d", e); \
+ VNC_DEBUG("Removed encoding %d", (int)e); \
break; \
} \
}

View File

@@ -0,0 +1,263 @@
Subject: Implement extended clipboard capability negotiation
From: Lin Ma lma@suse.de Mon Jul 7 12:41:10 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: 1c813530f80ddd58029b0c2dca5228f205c50a9f
This patch implements the initial capability negotiation for the RFB
Extended Clipboard protocol.
It modifies the vnc_connection_server_message to detect the extended
ServerCutText messages, which are identified by a negative length
value.
Upon receiving a message with the VNC_CLIPBOARD_ACTION_CAPS flag, The
vnc_connection_handle_clipboard_caps function is called. This function
parses the list of formats and their maximum sizes advertised by the
server and stores them in the private connection state.
After processing the server's capabilities, Client responds by sending
its own capabilities back. The vnc_connection_write_clipboard_caps
function constructs this response, announcing the client's supported
formats and actions.
New fields have been added to the VncConnectionPrivate struct to track
the negotiation state and the server's capabilities.
This commit establishes the foundational handshake required for all
subsequent extended clipboard operations.
Signed-off-by: Lin Ma <lma@suse.de>
--- a/src/vncconnection.c
+++ b/src/vncconnection.c
@@ -26,6 +26,7 @@
#include "vncmarshal.h"
#include "vncbaseframebuffer.h"
#include "vncutil.h"
+#include "vncclipboard.h"
#include <string.h>
#include <unistd.h>
@@ -58,6 +59,11 @@
#define GTK_VNC_ERROR g_quark_from_static_string("gtk-vnc")
+/* A reasonable upper limit for an extended clipboard message, which
+ * contains metadata, not the actual clipboard content.
+ */
+#define VNC_MAX_EXTENDED_CLIPBOARD_MSG_SIZE (256 * 1024)
+
struct wait_queue
{
gboolean waiting;
@@ -133,6 +139,15 @@ static void vnc_connection_set_error(Vnc
...) G_GNUC_PRINTF(2, 3);
static void vnc_connection_auth_failure(VncConnection *conn, const char *reason);
+static void vnc_connection_write_clipboard_caps(VncConnection *conn,
+ guint32 caps,
+ const guint32 *lengths);
+static void vnc_connection_handle_clipboard_caps(VncConnection *conn,
+ guint32 flags,
+ const guint32 *lengths);
+static void vnc_connection_handle_extended_clipboard(VncConnection *conn,
+ gint32 len);
+
/*
* A special GSource impl which allows us to wait on a certain
* condition to be satisfied. This is effectively a boolean test
@@ -257,6 +272,10 @@ struct _VncConnectionPrivate
VncAudio *audio;
VncAudioSample *audio_sample;
guint audio_timer;
+
+ gboolean has_extended_clipboard;
+ guint32 server_clipboard_flags;
+ guint32 server_clipboard_sizes[VNC_CLIPBOARD_MAX_FORMATS];
};
G_DEFINE_TYPE_WITH_PRIVATE(VncConnection, vnc_connection, G_TYPE_OBJECT);
@@ -3670,23 +3689,24 @@ static gboolean vnc_connection_server_me
break;
case VNC_CONNECTION_SERVER_MESSAGE_SERVER_CUT_TEXT: {
guint8 pad[3];
- guint32 n_text;
- char *data;
-
+ guint32 len;
vnc_connection_read(conn, pad, 3);
- n_text = vnc_connection_read_u32(conn);
- if (n_text > (32 << 20)) {
- vnc_connection_set_error(conn, "Cut text length %u longer than permitted %d",
- n_text, (32 << 20));
- break;
- }
-
- data = g_new(char, n_text + 1);
- vnc_connection_read(conn, data, n_text);
- data[n_text] = 0;
+ len = vnc_connection_read_u32(conn);
- vnc_connection_server_cut_text(conn, data, n_text);
- g_free(data);
+ if ((gint32)len < 0) {
+ vnc_connection_handle_extended_clipboard(conn, -(gint32)len);
+ } else {
+ if (len > (32 << 20)) {
+ vnc_connection_set_error(conn, "Cut text length %u longer than permitted %d",
+ len, (32 << 20));
+ break;
+ }
+ char *data = g_new(char, len + 1);
+ vnc_connection_read(conn, data, len);
+ data[len] = 0;
+ vnc_connection_server_cut_text(conn, data, len);
+ g_free(data);
+ }
} break;
case VNC_CONNECTION_SERVER_MESSAGE_XVP: {
guint8 pad[1];
@@ -5595,6 +5615,10 @@ static void vnc_connection_init(VncConne
priv->fd = -1;
priv->auth_type = VNC_CONNECTION_AUTH_INVALID;
priv->auth_subtype = VNC_CONNECTION_AUTH_INVALID;
+
+ priv->has_extended_clipboard = FALSE;
+ priv->server_clipboard_flags = 0;
+ memset(priv->server_clipboard_sizes, 0, sizeof(priv->server_clipboard_sizes));
}
@@ -6773,3 +6797,132 @@ VncConnectionResizeStatus vnc_connection
return !vnc_connection_has_error(conn);
}
+
+static void
+vnc_connection_handle_clipboard_caps(VncConnection *conn,
+ guint32 flags,
+ const guint32 *lengths)
+{
+ VncConnectionPrivate *priv = conn->priv;
+ int i, num = 0;
+ guint32 client_caps;
+ guint32 sizes[] = {0};
+
+ VNC_DEBUG("Got server clipboard capabilities (flags 0x%x)", flags);
+
+ priv->has_extended_clipboard = TRUE;
+ priv->server_clipboard_flags = flags;
+
+ memset(priv->server_clipboard_sizes, 0, sizeof(priv->server_clipboard_sizes));
+ for (i = 0; i < VNC_CLIPBOARD_MAX_FORMATS; i++) {
+ if (flags & (1 << i)) {
+ priv->server_clipboard_sizes[i] = lengths[num++];
+ }
+ }
+
+ // Announce our capabilities, Currently only implements UTF8 format handling.
+ client_caps = (VNC_CLIPBOARD_FORMAT_UTF8 |
+ VNC_CLIPBOARD_ACTION_REQUEST |
+ VNC_CLIPBOARD_ACTION_PEEK |
+ VNC_CLIPBOARD_ACTION_NOTIFY |
+ VNC_CLIPBOARD_ACTION_PROVIDE);
+
+ vnc_connection_write_clipboard_caps(conn, client_caps, sizes);
+}
+
+static void
+vnc_connection_write_clipboard_caps(VncConnection *conn,
+ guint32 caps,
+ const guint32 *lengths)
+{
+ int i, count = 0;
+ guint8 pad[3] = {0};
+
+ VNC_DEBUG("Sending client clipboard capabilities (caps 0x%x)", caps);
+
+ vnc_connection_buffered_write_u8(conn, VNC_CONNECTION_CLIENT_MESSAGE_CUT_TEXT);
+ vnc_connection_buffered_write(conn, pad, 3);
+
+ for (i = 0; i < VNC_CLIPBOARD_MAX_FORMATS; i++) {
+ if (caps & (1 << i))
+ count++;
+ }
+
+ // Payload is flags(4) + one size per format
+ vnc_connection_buffered_write_s32(conn, -(4 + 4 * count));
+
+ vnc_connection_buffered_write_u32(conn, caps | VNC_CLIPBOARD_ACTION_CAPS);
+
+ count = 0;
+ for (i = 0; i < VNC_CLIPBOARD_MAX_FORMATS; i++) {
+ if (caps & (1 << i))
+ vnc_connection_buffered_write_u32(conn, lengths[count++]);
+ }
+
+ vnc_connection_buffered_flush(conn);
+}
+
+static void
+vnc_connection_handle_extended_clipboard(VncConnection *conn, gint32 len)
+{
+ guint32 flags, action;
+
+ if (len < 4) {
+ vnc_connection_set_error(conn,
+ "Invalid extended clipboard message: length "
+ "%d is less than 4",
+ len);
+ return;
+ }
+ if (len > VNC_MAX_EXTENDED_CLIPBOARD_MSG_SIZE) {
+ vnc_connection_set_error(conn,
+ "Extended clipboard message too long (%d bytes).",
+ len);
+ return;
+ }
+
+ flags = vnc_connection_read_u32(conn);
+ if (vnc_connection_has_error(conn))
+ return;
+
+ action = flags & VNC_CLIPBOARD_ACTION_MASK;
+
+ if (action & VNC_CLIPBOARD_ACTION_CAPS) {
+ int i, format_count = 0;
+ guint32 lengths[VNC_CLIPBOARD_MAX_FORMATS] = {0};
+
+ for (i = 0; i < VNC_CLIPBOARD_MAX_FORMATS; i++) {
+ if (flags & (1 << i))
+ format_count++;
+ }
+
+ if (len < (4 + 4 * format_count)) {
+ vnc_connection_set_error(conn,
+ "Invalid clipboard caps message, length "
+ "%d is too small for %d formats",
+ len, format_count);
+ return;
+ }
+
+ for (i = 0; i < format_count; i++) {
+ lengths[i] = vnc_connection_read_u32(conn);
+ if (vnc_connection_has_error(conn))
+ return;
+ }
+
+ vnc_connection_handle_clipboard_caps(conn, flags, lengths);
+
+ } else if (action == VNC_CLIPBOARD_ACTION_PROVIDE) {
+ VNC_DEBUG("Received clipboard action 'provide', not implemented yet");
+ } else if (action == VNC_CLIPBOARD_ACTION_REQUEST) {
+ VNC_DEBUG("Received clipboard action 'request', not implemented yet");
+ } else if (action == VNC_CLIPBOARD_ACTION_PEEK) {
+ VNC_DEBUG("Received clipboard action 'peek', not implemented yet");
+ } else if (action == VNC_CLIPBOARD_ACTION_NOTIFY) {
+ VNC_DEBUG("Received clipboard action 'notify', not implemented yet");
+ } else {
+ VNC_DEBUG("Unknown extended clipboard action 0x%x", action);
+ }
+
+ return;
+}

View File

@@ -0,0 +1,207 @@
Subject: Implement client-to-server clipboard update notification
From: Lin Ma lma@suse.de Mon Jul 7 12:59:03 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: 348452c1007bbfd3575b94263d9e55b0e11b79db
This patch implements the mechanism for notifying the VNC server side
when the client's local clipboard content has changed.
On the VncDisplay (UI) layer:
- It detects local clipboard changes by listening to the 'owner-change'
signal on the GDK 'PRIMARY' selection.
- A 50ms timer is used to debounce events, filtering out transient,
invalid events caused by actions like double-clicks.
- When valid clipboard content is confirmed, it calls the underlying
VncConnection function.
On the VncConnection (protocol) layer:
- A new public function vnc_connection_handle_clipboard_change, is added
to serve as the entry point for triggering clipboard notifications from
the UI layer.
- The vnc_connection_write_clipboard_notify function is implemented to
construct and send an extended clipboard message with the NOTIFY action.
This patch completes the "announcement" phase of a client-side clipboard
update, laying the groundwork for the server to subsequently request the
data.
Signed-off-by: Lin Ma <lma@suse.de>
--- a/src/libgvnc_sym.version
+++ b/src/libgvnc_sym.version
@@ -95,6 +95,9 @@
vnc_connection_power_control;
vnc_connection_set_size;
+ vnc_connection_handle_clipboard_change;
+ vnc_connection_clear_pending_flag;
+
vnc_util_set_debug;
vnc_util_get_debug;
vnc_util_get_version;
--- a/src/vncconnection.c
+++ b/src/vncconnection.c
@@ -147,6 +147,10 @@ static void vnc_connection_handle_clipbo
const guint32 *lengths);
static void vnc_connection_handle_extended_clipboard(VncConnection *conn,
gint32 len);
+static void vnc_connection_write_clipboard_notify(VncConnection *conn,
+ guint32 flags);
+static void vnc_connection_announce_clipboard(VncConnection *conn,
+ gboolean available);
/*
* A special GSource impl which allows us to wait on a certain
@@ -276,6 +280,7 @@ struct _VncConnectionPrivate
gboolean has_extended_clipboard;
guint32 server_clipboard_flags;
guint32 server_clipboard_sizes[VNC_CLIPBOARD_MAX_FORMATS];
+ gboolean pendingClientClipboard;
};
G_DEFINE_TYPE_WITH_PRIVATE(VncConnection, vnc_connection, G_TYPE_OBJECT);
@@ -5619,6 +5624,7 @@ static void vnc_connection_init(VncConne
priv->has_extended_clipboard = FALSE;
priv->server_clipboard_flags = 0;
memset(priv->server_clipboard_sizes, 0, sizeof(priv->server_clipboard_sizes));
+ priv->pendingClientClipboard = FALSE;
}
@@ -6926,3 +6932,49 @@ vnc_connection_handle_extended_clipboard
return;
}
+
+static void vnc_connection_write_clipboard_notify(VncConnection *conn,
+ guint32 flags)
+{
+ guint8 pad[3] = {0};
+
+ vnc_connection_buffered_write_u8(conn, VNC_CONNECTION_CLIENT_MESSAGE_CUT_TEXT);
+ vnc_connection_buffered_write(conn, pad, 3);
+
+ /* Payload is flags(4). Negative length indicates extended message. */
+ vnc_connection_buffered_write_s32(conn, -4);
+
+ vnc_connection_buffered_write_u32(conn, flags | VNC_CLIPBOARD_ACTION_NOTIFY);
+
+ vnc_connection_buffered_flush(conn);
+}
+
+static void vnc_connection_announce_clipboard(VncConnection *conn,
+ gboolean available)
+{
+
+ if (!(conn->priv->server_clipboard_flags & VNC_CLIPBOARD_ACTION_NOTIFY)) {
+ vnc_connection_set_error(conn,
+ "Server does not support clipboard "
+ "'notify' action");
+ }
+
+ guint32 flags = available ? VNC_CLIPBOARD_FORMAT_UTF8 : 0;
+ vnc_connection_write_clipboard_notify(conn, flags);
+}
+
+void vnc_connection_handle_clipboard_change(VncConnection *conn)
+{
+ VncConnectionPrivate *priv = conn->priv;
+
+ priv->pendingClientClipboard = TRUE;
+
+ vnc_connection_announce_clipboard(conn, FALSE);
+}
+
+void vnc_connection_clear_pending_flag(VncConnection *conn)
+{
+ VncConnectionPrivate *priv = conn->priv;
+
+ priv->pendingClientClipboard = FALSE;
+}
--- a/src/vncconnection.h
+++ b/src/vncconnection.h
@@ -246,6 +246,9 @@ gboolean vnc_connection_has_error(VncCon
gboolean vnc_connection_set_framebuffer(VncConnection *conn,
VncFramebuffer *fb);
+void vnc_connection_handle_clipboard_change(VncConnection *conn);
+void vnc_connection_clear_pending_flag(VncConnection *conn);
+
const char *vnc_connection_get_name(VncConnection *conn);
int vnc_connection_get_width(VncConnection *conn);
int vnc_connection_get_height(VncConnection *conn);
--- a/src/vncdisplay.c
+++ b/src/vncdisplay.c
@@ -100,6 +100,8 @@ struct _VncDisplayPrivate
VncGrabSequence *vncgrabseq; /* the configured key sequence */
gboolean *vncactiveseq; /* the currently pressed keys */
+ guint primary_selection_timer_id;
+
#ifdef WIN32
HHOOK keyboard_hook;
#endif
@@ -1826,6 +1828,54 @@ static void on_auth_unsupported(VncConne
g_signal_emit(G_OBJECT(obj), signals[VNC_AUTH_UNSUPPORTED], 0, authType);
}
+static void handle_primary_received(GtkClipboard *clipboard G_GNUC_UNUSED,
+ const gchar *text,
+ gpointer opaque)
+{
+ VncDisplay *display = VNC_DISPLAY(opaque);
+ VncDisplayPrivate *priv = display->priv;
+
+ if (text != NULL && *text != '\0' && !priv->read_only) {
+ vnc_connection_handle_clipboard_change(priv->conn);
+ } else {
+ vnc_connection_clear_pending_flag(priv->conn);
+ }
+}
+
+static gboolean handle_primary_request(gpointer opaque)
+{
+ VncDisplay *display = VNC_DISPLAY(opaque);
+ VncDisplayPrivate *priv = display->priv;
+
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+ gtk_clipboard_request_text(clipboard, handle_primary_received, opaque);
+
+ /* Returns FALSE to indicate that this timer will be executed only once and
+ * then automatically destroyed. */
+ priv->primary_selection_timer_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void on_primary_owner_change(GtkClipboard *clipboard G_GNUC_UNUSED,
+ GdkEventOwnerChange *event G_GNUC_UNUSED,
+ gpointer opaque)
+{
+ VncDisplay *display = VNC_DISPLAY(opaque);
+ VncDisplayPrivate *priv = display->priv;
+
+ /* If a timer is already running, cancel it first. */
+ if (priv->primary_selection_timer_id != 0) {
+ g_source_remove(priv->primary_selection_timer_id);
+ }
+
+ /* Start a new timer and execute the real request after 50 milliseconds,
+ * This delay is enough to filter out intermediate events generated by
+ * double clicks */
+ priv->primary_selection_timer_id = g_timeout_add(50,
+ handle_primary_request,
+ opaque);
+}
+
static void on_server_cut_text(VncConnection *conn G_GNUC_UNUSED,
const gchar *text,
gpointer opaque)
@@ -3003,6 +3053,8 @@ static void vnc_display_init(VncDisplay
G_CALLBACK(on_power_control_init), display);
g_signal_connect(G_OBJECT(priv->conn), "vnc-power-control-failed",
G_CALLBACK(on_power_control_fail), display);
+ g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change",
+ G_CALLBACK(on_primary_owner_change), display);
priv->keycode_map = vnc_display_keymap_gdk2rfb_table(&priv->keycode_maplen);
}

View File

@@ -0,0 +1,93 @@
Subject: Flush pending clipboard on focus-in event
From: Lin Ma lma@suse.de Mon Jul 7 13:00:46 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: 6c8d86919878f08665e3c92915c58c64901af3ea
This patch introduces a mechanism to handle clipboard synchronization
when the VNC client window regains focus.
Previously, if a user copied content while the VNC window was inactive,
the clipboard change state 'pendingClientClipboard' was recorded but not
immediately notified to the server.
This commit addresses this by:
- Making VncDisplay listen for the 'focus-in-event' signal.
- When the widget gains focus, the vnc_connection_flush_pending_clipboard
function is called.
- This function checks for a pending clipboard update and, if one exists,
immediately sends a NOTIFY message to the server side, announcing that
clipboard content is available.
This "lazy synchronization" strategy ensures that the clipboard state is
updated only when the user re-engages with the VNC session.
Signed-off-by: Lin Ma <lma@suse.de>
--- a/src/libgvnc_sym.version
+++ b/src/libgvnc_sym.version
@@ -97,6 +97,7 @@
vnc_connection_handle_clipboard_change;
vnc_connection_clear_pending_flag;
+ vnc_connection_flush_pending_clipboard;
vnc_util_set_debug;
vnc_util_get_debug;
--- a/src/vncconnection.c
+++ b/src/vncconnection.c
@@ -6978,3 +6978,12 @@ void vnc_connection_clear_pending_flag(V
priv->pendingClientClipboard = FALSE;
}
+
+void vnc_connection_flush_pending_clipboard(VncConnection *conn)
+{
+ VncConnectionPrivate *priv = conn->priv;
+
+ if (priv->pendingClientClipboard)
+ vnc_connection_announce_clipboard(conn, TRUE);
+ priv->pendingClientClipboard = FALSE;
+}
--- a/src/vncconnection.h
+++ b/src/vncconnection.h
@@ -248,6 +248,7 @@ gboolean vnc_connection_set_framebuffer(
void vnc_connection_handle_clipboard_change(VncConnection *conn);
void vnc_connection_clear_pending_flag(VncConnection *conn);
+void vnc_connection_flush_pending_clipboard(VncConnection *conn);
const char *vnc_connection_get_name(VncConnection *conn);
int vnc_connection_get_width(VncConnection *conn);
--- a/src/vncdisplay.c
+++ b/src/vncdisplay.c
@@ -1876,6 +1876,21 @@ static void on_primary_owner_change(GtkC
opaque);
}
+static gboolean on_focus_in(GtkWidget *widget G_GNUC_UNUSED,
+ GdkEventFocus *event G_GNUC_UNUSED,
+ gpointer opaqueg)
+{
+ VncDisplay *display = VNC_DISPLAY(opaqueg);
+ VncDisplayPrivate *priv = display->priv;
+
+ VNC_DEBUG("VncDisplay gained focus.");
+
+ if (!priv->read_only)
+ vnc_connection_flush_pending_clipboard(priv->conn);
+
+ return FALSE;
+}
+
static void on_server_cut_text(VncConnection *conn G_GNUC_UNUSED,
const gchar *text,
gpointer opaque)
@@ -3055,6 +3070,8 @@ static void vnc_display_init(VncDisplay
G_CALLBACK(on_power_control_fail), display);
g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change",
G_CALLBACK(on_primary_owner_change), display);
+ g_signal_connect(display, "focus-in-event",
+ G_CALLBACK(on_focus_in), display);
priv->keycode_map = vnc_display_keymap_gdk2rfb_table(&priv->keycode_maplen);
}

View File

@@ -0,0 +1,272 @@
Subject: Implement response to server clipboard REQUEST action
From: Lin Ma lma@suse.de Mon Jul 7 13:05:22 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: e5ecf1243b7b7ac274681567ed9bb1b596967b28
This patch implements the full logic for the client to handle a server's
VNC_CLIPBOARD_ACTION_REQUEST and send back the clipboard data.
When the VncConnection receives a data request from the server, it now:
1. Emits a new GObject signal, 'clipboard-request', to propagate the
protocol event to the upper VncDisplay layer.
2. VncDisplay, upon catching this signal, asynchronously requests the
text content of the 'PRIMARY' selection from GTK.
3. After successfully fetching the text, it passes the data back to the
VncConnection via the public API vnc_connection_send_clipboard_data.
4. The VncConnection then compresses the data using zlib and sends it
to the server side.
This implementation handles the decoupling of the protocol and UI layers,
manages GTK's asynchronous clipboard operations, and completes the
request-response loop for client-to-server data synchronization.
Signed-off-by: Lin Ma <lma@suse.de>
--- a/src/libgvnc_sym.version
+++ b/src/libgvnc_sym.version
@@ -98,6 +98,7 @@
vnc_connection_handle_clipboard_change;
vnc_connection_clear_pending_flag;
vnc_connection_flush_pending_clipboard;
+ vnc_connection_send_clipboard_data;
vnc_util_set_debug;
vnc_util_get_debug;
--- a/src/vncconnection.c
+++ b/src/vncconnection.c
@@ -151,6 +151,10 @@ static void vnc_connection_write_clipboa
guint32 flags);
static void vnc_connection_announce_clipboard(VncConnection *conn,
gboolean available);
+static void vnc_connection_write_clipboard_provide(VncConnection *conn,
+ guint32 flags,
+ const gchar *text);
+static void vnc_connection_handle_clipboard_request(VncConnection *conn);
/*
* A special GSource impl which allows us to wait on a certain
@@ -311,6 +315,8 @@ enum {
VNC_DISCONNECTED,
VNC_ERROR,
+ VNC_CLIPBOARD_REQUEST, /* 20 */
+
VNC_LAST_SIGNAL,
};
@@ -5606,6 +5612,15 @@ static void vnc_connection_class_init(Vn
G_TYPE_NONE,
1,
G_TYPE_STRING);
+ signals[VNC_CLIPBOARD_REQUEST] =
+ g_signal_new ("clipboard-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (VncConnectionClass, vnc_clipboard_request),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
}
@@ -6921,7 +6936,7 @@ vnc_connection_handle_extended_clipboard
} else if (action == VNC_CLIPBOARD_ACTION_PROVIDE) {
VNC_DEBUG("Received clipboard action 'provide', not implemented yet");
} else if (action == VNC_CLIPBOARD_ACTION_REQUEST) {
- VNC_DEBUG("Received clipboard action 'request', not implemented yet");
+ vnc_connection_handle_clipboard_request(conn);
} else if (action == VNC_CLIPBOARD_ACTION_PEEK) {
VNC_DEBUG("Received clipboard action 'peek', not implemented yet");
} else if (action == VNC_CLIPBOARD_ACTION_NOTIFY) {
@@ -6987,3 +7002,92 @@ void vnc_connection_flush_pending_clipbo
vnc_connection_announce_clipboard(conn, TRUE);
priv->pendingClientClipboard = FALSE;
}
+
+static void
+vnc_connection_write_clipboard_provide(VncConnection *conn,
+ guint32 flags,
+ const gchar *text)
+{
+ guint8 pad[3] = {0};
+ z_stream zst;
+ GByteArray *uncompressed_payload = NULL;
+ guchar *compressed_buf = NULL;
+ gsize compressed_size = 0;
+ gulong text_len = strlen(text) + 1;
+ guint32 text_len_net;
+ gulong dest_len;
+
+ uncompressed_payload = g_byte_array_new();
+
+ text_len_net = g_htonl(text_len);
+ g_byte_array_append(uncompressed_payload,
+ (const guint8 *)&text_len_net,
+ sizeof(text_len_net));
+
+ g_byte_array_append(uncompressed_payload, (const guint8 *)text, text_len);
+
+ dest_len = compressBound(uncompressed_payload->len);
+ compressed_buf = g_malloc(dest_len);
+
+ zst.zalloc = Z_NULL;
+ zst.zfree = Z_NULL;
+ zst.opaque = Z_NULL;
+
+ if (deflateInit(&zst, Z_DEFAULT_COMPRESSION) != Z_OK) {
+ vnc_connection_set_error(conn,
+ "Failed to initialize zlib for compression");
+ g_free(compressed_buf);
+ g_byte_array_free(uncompressed_payload, TRUE);
+ return;
+ }
+
+ zst.avail_in = uncompressed_payload->len;
+ zst.next_in = uncompressed_payload->data;
+ zst.avail_out = dest_len;
+ zst.next_out = compressed_buf;
+
+ if (deflate(&zst, Z_FINISH) != Z_STREAM_END) {
+ vnc_connection_set_error(conn, "%s", "Zlib compression failed");
+ goto cleanup;
+ }
+ compressed_size = zst.total_out;
+
+ VNC_DEBUG("Sending clipboard provide, payload size %u, compressed size %lu",
+ uncompressed_payload->len, (gulong)compressed_size);
+
+ vnc_connection_buffered_write_u8(conn, VNC_CONNECTION_CLIENT_MESSAGE_CUT_TEXT);
+ vnc_connection_buffered_write(conn, pad, 3);
+
+ vnc_connection_buffered_write_s32(conn, -(4 + compressed_size));
+ vnc_connection_buffered_write_u32(conn, flags | VNC_CLIPBOARD_ACTION_PROVIDE);
+
+ vnc_connection_buffered_write(conn, compressed_buf, compressed_size);
+
+ vnc_connection_buffered_flush(conn);
+
+cleanup:
+ deflateEnd(&zst);
+ g_free(compressed_buf);
+ g_byte_array_free(uncompressed_payload, TRUE);
+}
+
+static void vnc_connection_handle_clipboard_request(VncConnection *conn)
+{
+ g_signal_emit(conn, signals[VNC_CLIPBOARD_REQUEST], 0);
+}
+
+void vnc_connection_send_clipboard_data(VncConnection *conn,
+ const gchar *text)
+{
+ VncConnectionPrivate *priv = conn->priv;
+
+ if (priv->server_clipboard_flags & VNC_CLIPBOARD_ACTION_PROVIDE) {
+ vnc_connection_write_clipboard_provide(conn,
+ VNC_CLIPBOARD_FORMAT_UTF8,
+ text);
+ } else {
+ vnc_connection_set_error(conn,
+ "Server does not support clipboard 'provide'"
+ "action, can not send clipboard data.");
+ }
+}
--- a/src/vncconnection.h
+++ b/src/vncconnection.h
@@ -83,6 +83,7 @@ struct _VncConnectionClass
void (*vnc_power_control_initialized)(VncConnection *conn);
void (*vnc_power_control_failed)(VncConnection *conn);
void (*vnc_desktop_rename)(VncConnection *conn, const char *name);
+ void (*vnc_clipboard_request) (VncConnection *conn);
/*
* If adding fields to this struct, remove corresponding
@@ -249,6 +250,7 @@ gboolean vnc_connection_set_framebuffer(
void vnc_connection_handle_clipboard_change(VncConnection *conn);
void vnc_connection_clear_pending_flag(VncConnection *conn);
void vnc_connection_flush_pending_clipboard(VncConnection *conn);
+void vnc_connection_send_clipboard_data(VncConnection *conn, const gchar *text);
const char *vnc_connection_get_name(VncConnection *conn);
int vnc_connection_get_width(VncConnection *conn);
--- a/src/vncdisplay.c
+++ b/src/vncdisplay.c
@@ -356,6 +356,11 @@ static GdkCursor *create_default_cursor(
return cursor;
}
+static void on_clipboard_request(VncConnection *conn, VncDisplay *display);
+static void send_primary_selection(GtkClipboard *clipboard G_GNUC_UNUSED,
+ const gchar *text,
+ gpointer user_data);
+
#ifdef G_OS_WIN32
static HWND win32_window = NULL;
@@ -1347,6 +1352,11 @@ static void realize_event(GtkWidget *wid
gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(obj)),
priv->remote_cursor ? priv->remote_cursor : priv->null_cursor);
+
+ if (priv->conn) {
+ g_signal_connect(priv->conn, "clipboard-request",
+ G_CALLBACK(on_clipboard_request), obj);
+ }
}
static void get_preferred_width(GtkWidget *widget,
@@ -1501,6 +1511,53 @@ static void on_framebuffer_update(VncCon
}
+/**
+ * on_clipboard_request:
+ *
+ * This function is called when the VncConnection receives a clipboard
+ * request from the server. Its job is to start the *asynchronous*
+ * process of getting the local PRIMARY selection text from GTK.
+ */
+static void
+on_clipboard_request(VncConnection *conn G_GNUC_UNUSED, VncDisplay *display)
+{
+ GtkClipboard *primary_clipboard;
+
+ VNC_DEBUG("Handling 'clipboard-request' signal, asking GTK for PRIMARY selection.");
+
+ primary_clipboard = gtk_widget_get_clipboard(GTK_WIDGET(display),
+ GDK_SELECTION_PRIMARY);
+
+ /*
+ * This is an asynchronous operation. GTK will fetch the text
+ * and call send_primary_selection() when it's done.
+ */
+ gtk_clipboard_request_text(primary_clipboard,
+ send_primary_selection,
+ display);
+}
+
+/**
+ * send_primary_selection:
+ *
+ * GTK calls this function after it has successfully fetched the PRIMARY
+ * selection text. We now have the data and can send it to the server.
+ */
+static void
+send_primary_selection(GtkClipboard *clipboard G_GNUC_UNUSED,
+ const gchar *text,
+ gpointer user_data)
+{
+ VncDisplay *display = VNC_DISPLAY(user_data);
+ VncConnection *conn = vnc_display_get_connection(display);
+
+ if (conn && text != NULL && *text != '\0') {
+ VNC_DEBUG("Got PRIMARY selection asynchronously, sending to server.");
+ vnc_connection_send_clipboard_data(conn, text);
+ }
+}
+
+
static void do_framebuffer_init(VncDisplay *obj,
const VncPixelFormat *remoteFormat,
int width, int height, gboolean quiet)

View File

@@ -0,0 +1,96 @@
Subject: Implement handling of server clipboard NOTIFY action
From: Lin Ma lma@suse.de Mon Jul 7 13:11:06 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: b9150485968c771bdb32dc8a95560f6baa3c91eb
This patch implements the first step in the server-to-client clipboard
synchronization flow: the client's response to a server update
notification.
The specific changes are as follows:
- When VncConnection receives a message from the server with the
'VNC_CLIPBOARD_ACTION_NOTIFY' flag, the new
'vnc_connection_handle_clipboard_notify' function is called.
- Within this function, the client checks if the server supports the
'REQUEST' action. If so, the client immediately sends back a 'REQUEST'
message, asking the server to provide its clipboard data.
- Additionally, upon receiving a notification from the server, any
pending client-to-server clipboard update ('pendingClientClipboard')
is cancelled. This ensures correct clipboard state synchronization.
This commit establishes the "Notify -> Request" interaction, laying the
groundwork for the client to receive clipboard data from the server.
Signed-off-by: Lin Ma <lma@suse.de>
--- a/src/vncconnection.c
+++ b/src/vncconnection.c
@@ -155,6 +155,10 @@ static void vnc_connection_write_clipboa
guint32 flags,
const gchar *text);
static void vnc_connection_handle_clipboard_request(VncConnection *conn);
+static void vnc_connection_write_clipboard_request(VncConnection *conn,
+ guint32 flags);
+static void vnc_connection_handle_clipboard_notify(VncConnection *conn,
+ guint32 flags);
/*
* A special GSource impl which allows us to wait on a certain
@@ -6940,7 +6944,7 @@ vnc_connection_handle_extended_clipboard
} else if (action == VNC_CLIPBOARD_ACTION_PEEK) {
VNC_DEBUG("Received clipboard action 'peek', not implemented yet");
} else if (action == VNC_CLIPBOARD_ACTION_NOTIFY) {
- VNC_DEBUG("Received clipboard action 'notify', not implemented yet");
+ vnc_connection_handle_clipboard_notify(conn, flags);
} else {
VNC_DEBUG("Unknown extended clipboard action 0x%x", action);
}
@@ -7091,3 +7095,48 @@ void vnc_connection_send_clipboard_data(
"action, can not send clipboard data.");
}
}
+
+static void
+vnc_connection_write_clipboard_request(VncConnection *conn, guint32 flags)
+{
+ VncConnectionPrivate *priv = conn->priv;
+ guint8 pad[3] = {0};
+
+ if (!(priv->server_clipboard_flags & VNC_CLIPBOARD_ACTION_REQUEST)) {
+ vnc_connection_set_error(conn,
+ "Server does not support clipboard 'request'"
+ "action.");
+ return;
+ }
+
+ VNC_DEBUG("Sending clipboard request to server for format flags 0x%x", flags);
+
+ vnc_connection_buffered_write_u8(conn, VNC_CONNECTION_CLIENT_MESSAGE_CUT_TEXT);
+ vnc_connection_buffered_write(conn, pad, 3);
+
+ vnc_connection_buffered_write_s32(conn, -4);
+ vnc_connection_buffered_write_u32(conn, flags | VNC_CLIPBOARD_ACTION_REQUEST);
+
+ vnc_connection_buffered_flush(conn);
+}
+
+static void
+vnc_connection_handle_clipboard_notify(VncConnection *conn, guint32 flags)
+{
+ VncConnectionPrivate *priv = conn->priv;
+
+ if (flags & VNC_CLIPBOARD_FORMAT_UTF8) {
+ priv->pendingClientClipboard = FALSE;
+
+ VNC_DEBUG("Received notification of new clipboard data on server.");
+
+ if (priv->server_clipboard_flags & VNC_CLIPBOARD_ACTION_REQUEST) {
+ vnc_connection_write_clipboard_request(conn, VNC_CLIPBOARD_FORMAT_UTF8);
+ } else {
+ VNC_DEBUG("Server does not support clipboard request, not requesting data.");
+ vnc_connection_set_error(conn,
+ "Server does not support clipboard "
+ "'notify' action.");
+ }
+ }
+}

View File

@@ -0,0 +1,293 @@
Subject: Complete server-to-client data sync (PROVIDE)
From: Lin Ma lma@suse.de Mon Jul 7 13:14:40 2025 +0800
Date: Sun Oct 12 16:55:56 2025 +0800:
Git: 6e6d5df86cb5b6fe032573e9fd4defe88f04a733
This patch implements the final stage of the server-to-client extended
clipboard synchronization, enabling the client to receive and process
the actual data sent by the server via 'VNC_CLIPBOARD_ACTION_PROVIDE'.
The main workflow is as follows:
1. Upon receiving a 'PROVIDE' message, VncConnection reads and
decompresses the data payload using zlib.
2. It parses the decompressed data, extracts the text content, and
handles any potential Unicode escape sequences.
3. After validating the data as legal UTF-8, it emits a new GObject
signal, 'vnc-clipboard-data-received', passing the text as an argument.
4. VncDisplay listens for this signal and, in its callback, sets the
received text to the local 'CLIPBOARD' and 'PRIMARY' selections.
Signed-off-by: Lin Ma <lma@suse.de>
--- a/src/vncconnection.c
+++ b/src/vncconnection.c
@@ -159,6 +159,9 @@ static void vnc_connection_write_clipboa
guint32 flags);
static void vnc_connection_handle_clipboard_notify(VncConnection *conn,
guint32 flags);
+static void vnc_connection_handle_clipboard_provide(VncConnection *conn,
+ guint32 flags,
+ gint32 len);
/*
* A special GSource impl which allows us to wait on a certain
@@ -320,6 +323,7 @@ enum {
VNC_ERROR,
VNC_CLIPBOARD_REQUEST, /* 20 */
+ VNC_CLIPBOARD_DATA_RECEIVED,
VNC_LAST_SIGNAL,
};
@@ -5625,6 +5629,16 @@ static void vnc_connection_class_init(Vn
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
+ signals[VNC_CLIPBOARD_DATA_RECEIVED] =
+ g_signal_new("vnc-clipboard-data-received",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
}
@@ -6899,12 +6913,6 @@ vnc_connection_handle_extended_clipboard
len);
return;
}
- if (len > VNC_MAX_EXTENDED_CLIPBOARD_MSG_SIZE) {
- vnc_connection_set_error(conn,
- "Extended clipboard message too long (%d bytes).",
- len);
- return;
- }
flags = vnc_connection_read_u32(conn);
if (vnc_connection_has_error(conn))
@@ -6938,7 +6946,7 @@ vnc_connection_handle_extended_clipboard
vnc_connection_handle_clipboard_caps(conn, flags, lengths);
} else if (action == VNC_CLIPBOARD_ACTION_PROVIDE) {
- VNC_DEBUG("Received clipboard action 'provide', not implemented yet");
+ vnc_connection_handle_clipboard_provide(conn, flags, len);
} else if (action == VNC_CLIPBOARD_ACTION_REQUEST) {
vnc_connection_handle_clipboard_request(conn);
} else if (action == VNC_CLIPBOARD_ACTION_PEEK) {
@@ -7140,3 +7148,167 @@ vnc_connection_handle_clipboard_notify(V
}
}
}
+
+/**
+ * unescape_unicode_string:
+ * @escaped_text: A string that may contain \uXXXX escape sequences.
+ *
+ * This function parses a string containing C-style Unicode escapes
+ * (e.g., "\u4f60\u597d") and converts it back to a proper UTF-8
+ * encoded string (e.g., "你好").
+ *
+ * Returns: A new, g_malloc'd string with the unescaped UTF-8 content.
+ * The caller is responsible for freeing the returned string.
+ * Returns NULL on failure.
+ */
+static gchar*
+unescape_unicode_string(const gchar *escaped_text)
+{
+ if (!escaped_text)
+ return NULL;
+
+ GString *result = g_string_new("");
+ const gchar *p = escaped_text;
+
+ while (*p) {
+ if (p[0] == '\\' && p[1] == 'u' &&
+ g_ascii_isxdigit(p[2]) && g_ascii_isxdigit(p[3]) &&
+ g_ascii_isxdigit(p[4]) && g_ascii_isxdigit(p[5]))
+ {
+ gchar hex[5];
+ gunichar uc;
+
+ strncpy(hex, p + 2, 4);
+ hex[4] = '\0';
+ uc = g_ascii_strtoull(hex, NULL, 16);
+
+ g_string_append_unichar(result, uc);
+ p += 6;
+ } else {
+ g_string_append_c(result, *p);
+ p++;
+ }
+ }
+
+ return g_string_free(result, FALSE);
+}
+
+/*
+ * Handles the clipboard 'provide' action. It reads the compressed
+ * data from the connection, decompresses it, parses the content,
+ * and emits the "vnc-clipboard-data-received" signal if successful.
+ */
+static void
+vnc_connection_handle_clipboard_provide(VncConnection *conn, guint32 flags, gint32 len)
+{
+ guint8 *zlib_data = NULL;
+ guint8 *uncompressed_data = NULL;
+ GByteArray *out_array = NULL;
+ gchar *escaped_text = NULL;
+ gchar *text = NULL;
+ z_stream zst;
+ gboolean zst_initialized = FALSE;
+
+ gsize uncompressed_size = 0;
+ guint32 text_len = 0;
+
+ if (!(flags & VNC_CLIPBOARD_FORMAT_UTF8)) {
+ vnc_connection_set_error(conn, "%s", "Unsupported format UTF8.");
+ goto cleanup;
+ }
+
+ len -= 4;
+ // read the entire compressed data block from the network connection.
+ zlib_data = g_malloc(len);
+ if (!vnc_connection_read(conn, zlib_data, len)) {
+ vnc_connection_set_error(conn, "Failed to read clipboard provide data");
+ goto cleanup;
+ }
+
+ // Decompress the entire block of data.
+ zst.zalloc = Z_NULL;
+ zst.zfree = Z_NULL;
+ zst.opaque = Z_NULL;
+ zst.avail_in = len;
+ zst.next_in = zlib_data;
+
+ if (inflateInit(&zst) != Z_OK) {
+ vnc_connection_set_error(conn, "%s", "Failed to initialize zlib for decompression");
+ goto cleanup;
+ }
+ zst_initialized = TRUE;
+
+ out_array = g_byte_array_new();
+ guint8 temp_buf[4096];
+ int ret;
+
+ do {
+ zst.avail_out = sizeof(temp_buf);
+ zst.next_out = temp_buf;
+ ret = inflate(&zst, Z_NO_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END) {
+ vnc_connection_set_error(conn,
+ "Zlib decompression failed with code %d",
+ ret);
+ goto cleanup;
+ }
+ g_byte_array_append(out_array, temp_buf, sizeof(temp_buf) - zst.avail_out);
+ } while (zst.avail_out == 0 && ret != Z_STREAM_END);
+
+ uncompressed_data = g_byte_array_free(out_array, FALSE);
+ out_array = NULL;
+ uncompressed_size = zst.total_out;
+ inflateEnd(&zst);
+ zst_initialized = FALSE;
+
+ // parse [length][data] from the decompressed data.
+ if (uncompressed_size < 4) {
+ vnc_connection_set_error(conn,
+ "Invalid decompressed data: too short for "
+ "length field");
+ goto cleanup;
+ }
+
+ text_len = g_ntohl(*(guint32 *)uncompressed_data);
+
+ if (text_len > uncompressed_size - 4) {
+ vnc_connection_set_error(conn, "Invalid decompressed data: length mismatch");
+ goto cleanup;
+ }
+
+ if (text_len > VNC_MAX_EXTENDED_CLIPBOARD_MSG_SIZE) {
+ VNC_DEBUG("Clipboard data size (%u) exceeds maximum size (%u), ignoring.",
+ text_len,
+ VNC_MAX_EXTENDED_CLIPBOARD_MSG_SIZE);
+ goto cleanup;
+ }
+
+ escaped_text = g_strndup((const gchar *)(uncompressed_data + 4), text_len);
+ text = unescape_unicode_string(escaped_text);
+
+ if (!g_utf8_validate(text, -1, NULL)) {
+ VNC_DEBUG("Invalid UTF-8 sequence in clipboard data");
+ goto cleanup;
+ }
+
+ VNC_DEBUG("Successfully decoded clipboard text (len=%u), "
+ "emitting signal to UI layer.",
+ text_len);
+ g_signal_emit(conn, signals[VNC_CLIPBOARD_DATA_RECEIVED], 0, text);
+
+cleanup:
+ g_free(zlib_data);
+ g_free(uncompressed_data);
+ g_free(escaped_text);
+ g_free(text);
+
+ if (out_array) {
+ g_byte_array_free(out_array, TRUE);
+ }
+
+ if (zst_initialized) {
+ inflateEnd(&zst);
+ }
+
+ return;
+}
--- a/src/vncdisplay.c
+++ b/src/vncdisplay.c
@@ -360,6 +360,9 @@ static void on_clipboard_request(VncConn
static void send_primary_selection(GtkClipboard *clipboard G_GNUC_UNUSED,
const gchar *text,
gpointer user_data);
+static void on_clipboard_data_received(VncConnection *conn G_GNUC_UNUSED,
+ const gchar *text,
+ gpointer user_data);
#ifdef G_OS_WIN32
static HWND win32_window = NULL;
@@ -1557,6 +1560,23 @@ send_primary_selection(GtkClipboard *cli
}
}
+static void
+on_clipboard_data_received(VncConnection *conn G_GNUC_UNUSED,
+ const gchar *text,
+ gpointer user_data)
+{
+ VncDisplay *display = VNC_DISPLAY(user_data);
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_widget_get_clipboard(GTK_WIDGET(display),
+ gdk_atom_intern("CLIPBOARD", FALSE));
+ gtk_clipboard_set_text(clipboard, text, -1);
+
+ clipboard = gtk_widget_get_clipboard(GTK_WIDGET(display),
+ GDK_SELECTION_PRIMARY);
+ gtk_clipboard_set_text(clipboard, text, -1);
+}
+
static void do_framebuffer_init(VncDisplay *obj,
const VncPixelFormat *remoteFormat,
@@ -3129,6 +3149,8 @@ static void vnc_display_init(VncDisplay
G_CALLBACK(on_primary_owner_change), display);
g_signal_connect(display, "focus-in-event",
G_CALLBACK(on_focus_in), display);
+ g_signal_connect(G_OBJECT(priv->conn), "vnc-clipboard-data-received",
+ G_CALLBACK(on_clipboard_data_received), display);
priv->keycode_map = vnc_display_keymap_gdk2rfb_table(&priv->keycode_maplen);
}

View File

@@ -1,3 +1,17 @@
-------------------------------------------------------------------
Mon Nov 3 13:38:54 MST 2025 - carnold@suse.com
- bsc#1251850 - removal of spice leads to regression in
functionality, specifically for graphical console copy paste
001-src-introduce-a-vncclipboard.h-header-file.patch
002-Add-the-extended-clipboard-pseudo-encoding.patch
003-Implement-extended-clipboard-capability-negotiation.patch
004-Implement-client-to-server-clipboard-update-notification.patch
005-Flush-pending-clipboard-on-focus-in-event.patch
006-Implement-response-to-server-clipboard-REQUEST-action.patch
007-Implement-handling-of-server-clipboard-NOTIFY-action.patch
008-Complete-server-to-client-data-sync-PROVIDE.patch
-------------------------------------------------------------------
Fri Feb 7 09:58:09 UTC 2025 - Bjørn Lie <bjorn.lie@gmail.com>

View File

@@ -29,6 +29,15 @@ Group: Development/Libraries/X11
URL: https://wiki.gnome.org/Projects/gtk-vnc
Source0: https://download.gnome.org/sources/gtk-vnc/1.5/%{name}-%{version}.tar.xz
Patch1: 001-src-introduce-a-vncclipboard.h-header-file.patch
Patch2: 002-Add-the-extended-clipboard-pseudo-encoding.patch
Patch3: 003-Implement-extended-clipboard-capability-negotiation.patch
Patch4: 004-Implement-client-to-server-clipboard-update-notification.patch
Patch5: 005-Flush-pending-clipboard-on-focus-in-event.patch
Patch6: 006-Implement-response-to-server-clipboard-REQUEST-action.patch
Patch7: 007-Implement-handling-of-server-clipboard-NOTIFY-action.patch
Patch8: 008-Complete-server-to-client-data-sync-PROVIDE.patch
BuildRequires: cyrus-sasl-devel
BuildRequires: gobject-introspection-devel >= 0.9.4
BuildRequires: intltool
@@ -157,7 +166,7 @@ threaded.
%lang_package
%prep
%autosetup
%autosetup -p1
# install gvncviewer
sed -i '/install:/s/false/true/' examples/meson.build