Commit ae1b4a8e45 for qemu.org

commit ae1b4a8e45d5ddfbfda7e1717d9c737aab2cbe98
Author: Lukasz Kornicki <lucas.kornicki@nutanix.com>
Date:   Fri Oct 30 15:14:42 2026 +0100

    ui/vnc: notify client of clipboard availability

    Adds support for vnc ClipboardAvailability pseudo-encoding.

    When a client requests this feature, the server will inform the client
    whether a guest clipboard peer is present. The server will repeat the
    notification each time availability of a guest clipboard peer changes.

    This change enables the VNC client to determine if a guest side
    clipboard peer is available for clipboard exchange and react to
    changes of its availability.

    Until now the VNC clipboard peer was only registered when
    VNC_FEATURE_CLIPBOARD_EXT was enabled. With this change the VNC peer
    can be registered just for the purpose of receiving peer status
    updates. This requires introducing a VNC_FEATURE_CLIPBOARD_EXT guard
    before the QEMU_CLIPBOARD_UPDATE_INFO handler which previously only ran
    if VNC_FEATURE_CLIPBOARD_EXT was enabled.

    Signed-off-by: Lukasz Kornicki <lucas.kornicki@nutanix.com>
    Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
    Message-ID: <20261030141442.264308-5-lucas.kornicki@nutanix.com>

diff --git a/tools/qemu-vnc/qemu-vnc.c b/tools/qemu-vnc/qemu-vnc.c
index 5c2ba3b7a5..c915589ca7 100644
--- a/tools/qemu-vnc/qemu-vnc.c
+++ b/tools/qemu-vnc/qemu-vnc.c
@@ -95,6 +95,7 @@ on_manager_ready(GObject *source_object,
     g_autoptr(GPtrArray) console_paths = NULL;
     bool found = false;
     Error *local_err = NULL;
+    VncDisplay *vd;

     manager = G_DBUS_OBJECT_MANAGER(
         g_dbus_object_manager_client_new_finish(res, &err));
@@ -145,13 +146,17 @@ on_manager_ready(GObject *source_object,
      * Create the VNC display now that consoles exist, so that the
      * display change listener is registered against a valid console.
      */
-    if (!vnc_display_new("default", &local_err)) {
+    vd = vnc_display_new("default", &local_err);
+    if (!vd) {
         error_report("Failed to create VNC display: %s",
                      error_get_pretty(local_err));
         g_assert_not_reached();
         return;
     }

+    /* guest clipboard peer presence is not exposed over D-Bus */
+    vd->clipboard_availability = false;
+
     vnc_dbus_setup(state->bus);

     clipboard_setup(manager, state->bus);
diff --git a/ui/trace-events b/ui/trace-events
index 917d116d90..f5f82c75db 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -45,6 +45,7 @@ vnc_msg_server_audio_end(void *state, void *ioc) "VNC server msg audio end state
 vnc_msg_server_audio_data(void *state, void *ioc, const void *buf, size_t len) "VNC server msg audio data state=%p ioc=%p buf=%p len=%zd"
 vnc_msg_server_desktop_resize(void *state, void *ioc, int width, int height) "VNC server msg ext resize state=%p ioc=%p size=%dx%d"
 vnc_msg_server_ext_desktop_resize(void *state, void *ioc, int width, int height, int reason) "VNC server msg ext resize state=%p ioc=%p size=%dx%d reason=%d"
+vnc_msg_server_clipboard_availability(void *state, void *ioc, bool available) "VNC server msg clipboard availability state=%p ioc=%p available=%d"
 vnc_msg_client_audio_enable(void *state, void *ioc) "VNC client msg audio enable state=%p ioc=%p"
 vnc_msg_client_audio_disable(void *state, void *ioc) "VNC client msg audio disable state=%p ioc=%p"
 vnc_msg_client_audio_format(void *state, void *ioc, int fmt, int channels, int freq) "VNC client msg audio format state=%p ioc=%p fmt=%d channels=%d freq=%d"
diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c
index 67f710f6ed..7c5bd94cb6 100644
--- a/ui/vnc-clipboard.c
+++ b/ui/vnc-clipboard.c
@@ -26,6 +26,7 @@
 #include "qemu/error-report.h"
 #include "vnc.h"
 #include "vnc-jobs.h"
+#include "trace.h"

 static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
 {
@@ -224,6 +225,26 @@ static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
     }
 }

+void vnc_clipboard_availability_send(VncState *vs, bool available)
+{
+    if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_AVAILABILITY)) {
+        return;
+    }
+
+    trace_vnc_msg_server_clipboard_availability(vs, vs->ioc, available);
+
+    vnc_lock_output(vs);
+    vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+    vnc_write_u8(vs, 0);
+    vnc_write_u16(vs, 1);
+    vnc_framebuffer_update(vs, 0, 0, 0, 0,
+                           VNC_ENCODING_CLIPBOARD_AVAILABILITY);
+    vnc_write_u8(vs, available ? VNC_CLIPBOARD_AVAILABLE
+                               : VNC_CLIPBOARD_UNAVAILABLE);
+    vnc_unlock_output(vs);
+    vnc_flush(vs);
+}
+
 static void vnc_clipboard_notify(Notifier *notifier, void *data)
 {
     VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
@@ -231,13 +252,24 @@ static void vnc_clipboard_notify(Notifier *notifier, void *data)

     switch (notify->type) {
     case QEMU_CLIPBOARD_UPDATE_INFO:
+        /* ignore the update if client does not want ext messages */
+        if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) {
+            return;
+        }
+
         vnc_clipboard_update_info(vs, notify->info);
         return;
     case QEMU_CLIPBOARD_RESET_SERIAL:
         /* ignore */
         return;
     case QEMU_CLIPBOARD_PEER_UPDATE:
-        /* ignore */
+        /* only guest side peers affect the status we relay to the client */
+        if (!notify->peer_update.peer->guest) {
+            return;
+        }
+
+        vnc_clipboard_availability_send(vs,
+            qemu_clipboard_guest_peer_present());
         return;
     }
 }
diff --git a/ui/vnc.c b/ui/vnc.c
index 656768f9c9..8b1d03ef24 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -2191,6 +2191,14 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
             vnc_set_feature(vs, VNC_FEATURE_CLIPBOARD_EXT);
             vnc_server_cut_text_caps(vs);
             break;
+        case VNC_ENCODING_CLIPBOARD_AVAILABILITY:
+            if (vs->vd->clipboard_availability) {
+                vnc_set_feature(vs, VNC_FEATURE_CLIPBOARD_AVAILABILITY);
+                vnc_clipboard_peer_register(vs);
+                vnc_clipboard_availability_send(vs,
+                    qemu_clipboard_guest_peer_present());
+            }
+            break;
         case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9:
             vc->worker.tight.compression = (enc & 0x0F);
             break;
@@ -3459,6 +3467,7 @@ VncDisplay *vnc_display_new(const char *id, Error **errp)

     vd->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
     vd->connections_limit = 32;
+    vd->clipboard_availability = true;

     vnc_start_worker_thread(vd);

diff --git a/ui/vnc.h b/ui/vnc.h
index d8b0070396..8eae550c96 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -171,6 +171,7 @@ struct VncDisplay
     bool lossy;
     bool non_adaptive;
     bool power_control;
+    bool clipboard_availability;
     QCryptoTLSCreds *tlscreds;
     char *tlsauthzid;
 #ifdef CONFIG_VNC_SASL
@@ -422,6 +423,7 @@ enum {
 #define VNC_ENCODING_DESKTOP_RESIZE_EXT   0XFFFFFECC /* -308 */
 #define VNC_ENCODING_XVP                  0XFFFFFECB /* -309 */
 #define VNC_ENCODING_ALPHA_CURSOR         0XFFFFFEC6 /* -314 */
+#define VNC_ENCODING_CLIPBOARD_AVAILABILITY  0XFFFFFEC2 /* -318 */
 #define VNC_ENCODING_WMVi                 0x574D5669
 #define VNC_ENCODING_CLIPBOARD_EXT        0xc0a1e5ce

@@ -466,6 +468,7 @@ enum VncFeatures {
     VNC_FEATURE_LED_STATE,
     VNC_FEATURE_XVP,
     VNC_FEATURE_CLIPBOARD_EXT,
+    VNC_FEATURE_CLIPBOARD_AVAILABILITY,
     VNC_FEATURE_AUDIO,
 };

@@ -541,6 +544,10 @@ enum VncFeatures {
 #define VNC_CLIPBOARD_NOTIFY   (1 << 27)
 #define VNC_CLIPBOARD_PROVIDE  (1 << 28)

+/* ClipboardAvailability pseudo-encoding values */
+#define VNC_CLIPBOARD_UNAVAILABLE  0
+#define VNC_CLIPBOARD_AVAILABLE    1
+
 VncDisplay *vnc_display_new(const char *id, Error **errp);
 void vnc_display_free(VncDisplay *vd);

@@ -637,6 +644,7 @@ void vnc_zrle_clear(VncWorker *worker);

 /* vnc-clipboard.c */
 void vnc_clipboard_peer_register(VncState *vs);
+void vnc_clipboard_availability_send(VncState *vs, bool available);
 void vnc_server_cut_text_caps(VncState *vs);
 void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text);
 void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data);