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