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);
 }

Reply via email to