Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package gtk-vnc for openSUSE:Factory checked in at 2025-12-17 17:32:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/gtk-vnc (Old) and /work/SRC/openSUSE:Factory/.gtk-vnc.new.1939 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "gtk-vnc" Wed Dec 17 17:32:11 2025 rev:59 rq:1323138 version:1.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/gtk-vnc/gtk-vnc.changes 2025-02-09 20:00:58.105251286 +0100 +++ /work/SRC/openSUSE:Factory/.gtk-vnc.new.1939/gtk-vnc.changes 2025-12-17 17:35:41.349936066 +0100 @@ -1,0 +2,14 @@ +Mon Nov 3 13:38:54 MST 2025 - [email protected] + +- 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 + +------------------------------------------------------------------- New: ---- 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 ----------(New B)---------- New: functionality, specifically for graphical console copy paste 001-src-introduce-a-vncclipboard.h-header-file.patch 002-Add-the-extended-clipboard-pseudo-encoding.patch New: 001-src-introduce-a-vncclipboard.h-header-file.patch 002-Add-the-extended-clipboard-pseudo-encoding.patch 003-Implement-extended-clipboard-capability-negotiation.patch New: 002-Add-the-extended-clipboard-pseudo-encoding.patch 003-Implement-extended-clipboard-capability-negotiation.patch 004-Implement-client-to-server-clipboard-update-notification.patch New: 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 New: 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 New: 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 New: 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 New: 007-Implement-handling-of-server-clipboard-NOTIFY-action.patch 008-Complete-server-to-client-data-sync-PROVIDE.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ gtk-vnc.spec ++++++ --- /var/tmp/diff_new_pack.N1fyyA/_old 2025-12-17 17:35:42.341977759 +0100 +++ /var/tmp/diff_new_pack.N1fyyA/_new 2025-12-17 17:35:42.345977926 +0100 @@ -29,6 +29,15 @@ 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 @@ %lang_package %prep -%autosetup +%autosetup -p1 # install gvncviewer sed -i '/install:/s/false/true/' examples/meson.build ++++++ 001-src-introduce-a-vncclipboard.h-header-file.patch ++++++ Subject: src: introduce a vncclipboard.h header file From: Lin Ma [email protected] 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 <[email protected]> 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 */ ++++++ 002-Add-the-extended-clipboard-pseudo-encoding.patch ++++++ Subject: Add the extended clipboard pseudo-encoding From: Lin Ma [email protected] Mon Jul 7 12:40:53 2025 +0800 Date: Sun Oct 12 16:55:56 2025 +0800: Git: e334dd438907251d7b42615824d9c14ae3452331 Signed-off-by: Lin Ma <[email protected]> --- 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; \ } \ } ++++++ 003-Implement-extended-clipboard-capability-negotiation.patch ++++++ Subject: Implement extended clipboard capability negotiation From: Lin Ma [email protected] 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 <[email protected]> --- 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; +} ++++++ 004-Implement-client-to-server-clipboard-update-notification.patch ++++++ Subject: Implement client-to-server clipboard update notification From: Lin Ma [email protected] 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 <[email protected]> --- 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); } ++++++ 005-Flush-pending-clipboard-on-focus-in-event.patch ++++++ Subject: Flush pending clipboard on focus-in event From: Lin Ma [email protected] 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 <[email protected]> --- 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); } ++++++ 006-Implement-response-to-server-clipboard-REQUEST-action.patch ++++++ Subject: Implement response to server clipboard REQUEST action From: Lin Ma [email protected] 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 <[email protected]> --- 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) ++++++ 007-Implement-handling-of-server-clipboard-NOTIFY-action.patch ++++++ Subject: Implement handling of server clipboard NOTIFY action From: Lin Ma [email protected] 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 <[email protected]> --- 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."); + } + } +} ++++++ 008-Complete-server-to-client-data-sync-PROVIDE.patch ++++++ Subject: Complete server-to-client data sync (PROVIDE) From: Lin Ma [email protected] 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 <[email protected]> --- 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); }
