qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/7] ui: add vdagent implementation and clipboard support.
@ 2021-03-18  9:16 Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 1/7] ui: add clipboard infrastructure Gerd Hoffmann
                   ` (7 more replies)
  0 siblings, 8 replies; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

Fist sketch of cut+paste support for vnc.  On the guest side we are
going to reuse the spice vdagent, so things should work out-of-the-box
with guests in the wild.  So this patch set brings a qemu implemenation
of the vdagent protocol.

Beside that there is the clipboard infrastructure of course.  For now
only text support is there.  The design allows adding more data types,
so we can add image support and maybe more later on.  So far vdagent,
vnc server and gtk ui are hooked up.

Usage: qemu \
  -chardev vdagent,id=vdagent,clipboard=on \
  -device virtio-serial-pci \
  -device virtserialport,chardev=vdagent,name=com.redhat.spice.0

v2:
 - add a bunch of sanity checks.
 - add proper chunking.
 - use autofree.

Gerd Hoffmann (7):
  ui: add clipboard infrastructure
  ui/vdagent: core infrastructure
  ui/vdagent: add mouse support
  ui/vdagent: add clipboard support
  ui/vnc: clipboard support
  ui/gtk: move struct GtkDisplayState to ui/gtk.h
  ui/gtk: add clipboard support

 include/ui/clipboard.h |  68 +++++
 include/ui/gtk.h       |  67 ++++
 ui/vnc.h               |  24 ++
 chardev/char.c         |   6 +
 ui/clipboard.c         |  92 ++++++
 ui/gtk-clipboard.c     | 189 ++++++++++++
 ui/gtk.c               |  56 +---
 ui/vdagent.c           | 679 +++++++++++++++++++++++++++++++++++++++++
 ui/vnc-clipboard.c     | 323 ++++++++++++++++++++
 ui/vnc.c               |  20 +-
 qapi/char.json         |  17 ++
 ui/meson.build         |   5 +-
 ui/trace-events        |  10 +
 13 files changed, 1494 insertions(+), 62 deletions(-)
 create mode 100644 include/ui/clipboard.h
 create mode 100644 ui/clipboard.c
 create mode 100644 ui/gtk-clipboard.c
 create mode 100644 ui/vdagent.c
 create mode 100644 ui/vnc-clipboard.c

-- 
2.30.2




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 1/7] ui: add clipboard infrastructure
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
@ 2021-03-18  9:16 ` Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 2/7] ui/vdagent: core infrastructure Gerd Hoffmann
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

Add some infrastructure to manage the clipboard in qemu.

TODO: Add API docs.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 include/ui/clipboard.h | 68 +++++++++++++++++++++++++++++++
 ui/clipboard.c         | 92 ++++++++++++++++++++++++++++++++++++++++++
 ui/meson.build         |  1 +
 3 files changed, 161 insertions(+)
 create mode 100644 include/ui/clipboard.h
 create mode 100644 ui/clipboard.c

diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
new file mode 100644
index 000000000000..00ff559425ee
--- /dev/null
+++ b/include/ui/clipboard.h
@@ -0,0 +1,68 @@
+#ifndef QEMU_CLIPBOARD_H
+#define QEMU_CLIPBOARD_H
+
+#include "qemu/notify.h"
+
+typedef enum QemuClipboardType QemuClipboardType;
+typedef enum QemuClipboardSelection QemuClipboardSelection;
+typedef struct QemuClipboardPeer QemuClipboardPeer;
+typedef struct QemuClipboardInfo QemuClipboardInfo;
+
+enum QemuClipboardType {
+    QEMU_CLIPBOARD_TYPE_TEXT,  /* text/plain; charset=utf-8 */
+    QEMU_CLIPBOARD_TYPE__COUNT,
+};
+
+/* same as VD_AGENT_CLIPBOARD_SELECTION_* */
+enum QemuClipboardSelection {
+    QEMU_CLIPBOARD_SELECTION_CLIPBOARD,
+    QEMU_CLIPBOARD_SELECTION_PRIMARY,
+    QEMU_CLIPBOARD_SELECTION_SECONDARY,
+    QEMU_CLIPBOARD_SELECTION__COUNT,
+};
+
+struct QemuClipboardPeer {
+    const char *name;
+    Notifier update;
+    void (*request)(QemuClipboardInfo *info,
+                    QemuClipboardType type);
+};
+
+struct QemuClipboardInfo {
+    uint32_t refcount;
+    QemuClipboardPeer *owner;
+    QemuClipboardSelection selection;
+    struct {
+        bool available;
+        bool requested;
+        size_t size;
+        void *data;
+    } types[QEMU_CLIPBOARD_TYPE__COUNT];
+};
+
+struct QemuClipboardData {
+    uint32_t refcount;
+    QemuClipboardInfo *info;
+    QemuClipboardType type;
+};
+
+void qemu_clipboard_peer_register(QemuClipboardPeer *peer);
+void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer);
+
+QemuClipboardInfo *qemu_clipboard_info_new(QemuClipboardPeer *owner,
+                                           QemuClipboardSelection selection);
+QemuClipboardInfo *qemu_clipboard_info_get(QemuClipboardInfo *info);
+void qemu_clipboard_info_put(QemuClipboardInfo *info);
+
+void qemu_clipboard_update(QemuClipboardInfo *info);
+void qemu_clipboard_request(QemuClipboardInfo *info,
+                            QemuClipboardType type);
+
+void qemu_clipboard_set_data(QemuClipboardPeer *peer,
+                             QemuClipboardInfo *info,
+                             QemuClipboardType type,
+                             uint32_t size,
+                             void *data,
+                             bool update);
+
+#endif /* QEMU_CLIPBOARD_H */
diff --git a/ui/clipboard.c b/ui/clipboard.c
new file mode 100644
index 000000000000..556531c578a1
--- /dev/null
+++ b/ui/clipboard.c
@@ -0,0 +1,92 @@
+#include "qemu/osdep.h"
+#include "ui/clipboard.h"
+
+static NotifierList clipboard_notifiers =
+    NOTIFIER_LIST_INITIALIZER(clipboard_notifiers);
+
+void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
+{
+    notifier_list_add(&clipboard_notifiers, &peer->update);
+}
+
+void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
+{
+    notifier_remove(&peer->update);
+}
+
+void qemu_clipboard_update(QemuClipboardInfo *info)
+{
+    notifier_list_notify(&clipboard_notifiers, info);
+}
+
+QemuClipboardInfo *qemu_clipboard_info_new(QemuClipboardPeer *owner,
+                                           QemuClipboardSelection selection)
+{
+    QemuClipboardInfo *info = g_new0(QemuClipboardInfo, 1);
+
+    info->owner = owner;
+    info->selection = selection;
+    info->refcount = 1;
+
+    return info;
+}
+
+QemuClipboardInfo *qemu_clipboard_info_get(QemuClipboardInfo *info)
+{
+    info->refcount++;
+    return info;
+}
+
+void qemu_clipboard_info_put(QemuClipboardInfo *info)
+{
+    uint32_t type;
+
+    if (!info) {
+        return;
+    }
+
+    info->refcount--;
+    if (info->refcount > 0) {
+        return;
+    }
+
+    for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
+        g_free(info->types[type].data);
+    }
+    g_free(info);
+}
+
+void qemu_clipboard_request(QemuClipboardInfo *info,
+                            QemuClipboardType type)
+{
+    if (info->types[type].data ||
+        info->types[type].requested ||
+        !info->types[type].available ||
+        !info->owner)
+        return;
+
+    info->types[type].requested = true;
+    info->owner->request(info, type);
+}
+
+void qemu_clipboard_set_data(QemuClipboardPeer *peer,
+                             QemuClipboardInfo *info,
+                             QemuClipboardType type,
+                             uint32_t size,
+                             void *data,
+                             bool update)
+{
+    if (!info ||
+        info->owner != peer) {
+        return;
+    }
+
+    g_free(info->types[type].data);
+    info->types[type].data = g_memdup(data, size);
+    info->types[type].size = size;
+    info->types[type].available = true;
+
+    if (update) {
+        qemu_clipboard_update(info);
+    }
+}
diff --git a/ui/meson.build b/ui/meson.build
index e8d3ff41b905..fc4fb75c2869 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -2,6 +2,7 @@ softmmu_ss.add(pixman)
 specific_ss.add(when: ['CONFIG_SOFTMMU'], if_true: pixman)   # for the include path
 
 softmmu_ss.add(files(
+  'clipboard.c',
   'console.c',
   'cursor.c',
   'input-keymap.c',
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 2/7] ui/vdagent: core infrastructure
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 1/7] ui: add clipboard infrastructure Gerd Hoffmann
@ 2021-03-18  9:16 ` Gerd Hoffmann
  2021-03-18 10:32   ` Marc-André Lureau
  2021-03-18  9:16 ` [PATCH v2 3/7] ui/vdagent: add mouse support Gerd Hoffmann
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

The vdagent protocol allows the guest agent (spice-vdagent) and the
spice client exchange messages to implement features which require
guest cooperation, for example clipboard support.

This is a qemu implementation of the spice client side.  This allows
the spice guest agent talk to qemu directly when not using the spice
protocol.

usage: qemu \
  -chardev vdagent,id=vdagent \
  -device virtserialport,chardev=vdagent,name=com.redhat.spice.0

This patch adds just the protocol basics: initial handshake and
capability negotiation.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 ui/vdagent.c    | 279 ++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/char.json  |  13 +++
 ui/meson.build  |   1 +
 ui/trace-events |   8 ++
 4 files changed, 301 insertions(+)
 create mode 100644 ui/vdagent.c

diff --git a/ui/vdagent.c b/ui/vdagent.c
new file mode 100644
index 000000000000..146ddb8e31b4
--- /dev/null
+++ b/ui/vdagent.c
@@ -0,0 +1,279 @@
+#include "qemu/osdep.h"
+#include "include/qemu-common.h"
+#include "chardev/char.h"
+#include "trace.h"
+
+#include "qapi/qapi-types-char.h"
+
+#include "spice/vd_agent.h"
+
+struct VDAgentChardev {
+    Chardev parent;
+
+    /* guest vdagent */
+    uint32_t caps;
+    VDIChunkHeader chunk;
+    uint32_t chunksize;
+    uint8_t *msgbuf;
+    uint32_t msgsize;
+};
+typedef struct VDAgentChardev VDAgentChardev;
+
+#define TYPE_CHARDEV_VDAGENT "chardev-vdagent"
+
+DECLARE_INSTANCE_CHECKER(VDAgentChardev, VDAGENT_CHARDEV,
+                         TYPE_CHARDEV_VDAGENT);
+
+/* ------------------------------------------------------------------ */
+/* names, for debug logging                                           */
+
+static const char *cap_name[] = {
+    [VD_AGENT_CAP_MOUSE_STATE]                    = "mouse-state",
+    [VD_AGENT_CAP_MONITORS_CONFIG]                = "monitors-config",
+    [VD_AGENT_CAP_REPLY]                          = "reply",
+    [VD_AGENT_CAP_CLIPBOARD]                      = "clipboard",
+    [VD_AGENT_CAP_DISPLAY_CONFIG]                 = "display-config",
+    [VD_AGENT_CAP_CLIPBOARD_BY_DEMAND]            = "clipboard-by-demand",
+    [VD_AGENT_CAP_CLIPBOARD_SELECTION]            = "clipboard-selection",
+    [VD_AGENT_CAP_SPARSE_MONITORS_CONFIG]         = "sparse-monitors-config",
+    [VD_AGENT_CAP_GUEST_LINEEND_LF]               = "guest-lineend-lf",
+    [VD_AGENT_CAP_GUEST_LINEEND_CRLF]             = "guest-lineend-crlf",
+    [VD_AGENT_CAP_MAX_CLIPBOARD]                  = "max-clipboard",
+    [VD_AGENT_CAP_AUDIO_VOLUME_SYNC]              = "audio-volume-sync",
+    [VD_AGENT_CAP_MONITORS_CONFIG_POSITION]       = "monitors-config-position",
+    [VD_AGENT_CAP_FILE_XFER_DISABLED]             = "file-xfer-disabled",
+    [VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS]      = "file-xfer-detailed-errors",
+#if 0
+    [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO]           = "graphics-device-info",
+    [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
+    [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL]          = "clipboard-grab-serial",
+#endif
+};
+
+static const char *msg_name[] = {
+    [VD_AGENT_MOUSE_STATE]           = "mouse-state",
+    [VD_AGENT_MONITORS_CONFIG]       = "monitors-config",
+    [VD_AGENT_REPLY]                 = "reply",
+    [VD_AGENT_CLIPBOARD]             = "clipboard",
+    [VD_AGENT_DISPLAY_CONFIG]        = "display-config",
+    [VD_AGENT_ANNOUNCE_CAPABILITIES] = "announce-capabilities",
+    [VD_AGENT_CLIPBOARD_GRAB]        = "clipboard-grab",
+    [VD_AGENT_CLIPBOARD_REQUEST]     = "clipboard-request",
+    [VD_AGENT_CLIPBOARD_RELEASE]     = "clipboard-release",
+    [VD_AGENT_FILE_XFER_START]       = "file-xfer-start",
+    [VD_AGENT_FILE_XFER_STATUS]      = "file-xfer-status",
+    [VD_AGENT_FILE_XFER_DATA]        = "file-xfer-data",
+    [VD_AGENT_CLIENT_DISCONNECTED]   = "client-disconnected",
+    [VD_AGENT_MAX_CLIPBOARD]         = "max-clipboard",
+    [VD_AGENT_AUDIO_VOLUME_SYNC]     = "audio-volume-sync",
+#if 0
+    [VD_AGENT_GRAPHICS_DEVICE_INFO]  = "graphics-device-info",
+#endif
+};
+
+#define GET_NAME(_m, _v) \
+    (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???")
+
+/* ------------------------------------------------------------------ */
+/* send messages                                                      */
+
+static void vdagent_send_buf(VDAgentChardev *vd, void *ptr, uint32_t msgsize)
+{
+    uint8_t *msgbuf = ptr;
+    uint32_t len, pos = 0;
+
+    while (pos < msgsize) {
+        len = qemu_chr_be_can_write(CHARDEV(vd));
+        if (len > msgsize - pos) {
+            len = msgsize - pos;
+        }
+        qemu_chr_be_write(CHARDEV(vd), msgbuf + pos, len);
+        pos += len;
+    }
+}
+
+static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+    uint8_t *msgbuf = (void *)msg;
+    uint32_t msgsize = sizeof(VDAgentMessage) + msg->size;
+    uint32_t msgoff = 0;
+    VDIChunkHeader chunk;
+
+    trace_vdagent_send(GET_NAME(msg_name, msg->type));
+
+    msg->protocol = VD_AGENT_PROTOCOL;
+
+    while (msgoff < msgsize) {
+        chunk.port = VDP_CLIENT_PORT;
+        chunk.size = msgsize - msgoff;
+        if (chunk.size > 1024) {
+            chunk.size = 1024;
+        }
+        vdagent_send_buf(vd, &chunk, sizeof(chunk));
+        vdagent_send_buf(vd, msgbuf + msgoff, chunk.size);
+        msgoff += chunk.size;
+    }
+}
+
+static void vdagent_send_caps(VDAgentChardev *vd)
+{
+    g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+                                               sizeof(VDAgentAnnounceCapabilities) +
+                                               sizeof(uint32_t));
+
+    msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
+    msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t);
+
+    vdagent_send_msg(vd, msg);
+}
+
+/* ------------------------------------------------------------------ */
+/* chardev backend                                                    */
+
+static void vdagent_chr_open(Chardev *chr,
+                             ChardevBackend *backend,
+                             bool *be_opened,
+                             Error **errp)
+{
+    *be_opened = true;
+}
+
+static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+    VDAgentAnnounceCapabilities *caps = (void *)msg->data;
+    int i;
+
+    if (msg->size < (sizeof(VDAgentAnnounceCapabilities) +
+                     sizeof(uint32_t))) {
+        return;
+    }
+
+    for (i = 0; i < ARRAY_SIZE(cap_name); i++) {
+        if (caps->caps[0] & (1 << i)) {
+            trace_vdagent_peer_cap(GET_NAME(cap_name, i));
+        }
+    }
+
+    vd->caps = caps->caps[0];
+    if (caps->request) {
+        vdagent_send_caps(vd);
+    }
+}
+
+static void vdagent_chr_recv(VDAgentChardev *vd)
+{
+    VDAgentMessage *msg = (void *)vd->msgbuf;
+
+    if (vd->msgsize < sizeof(*msg)) {
+        fprintf(stderr, "%s: message too small: %d < %zd\n", __func__,
+                vd->msgsize, sizeof(*msg));
+        return;
+    }
+    if (vd->msgsize != msg->size + sizeof(*msg)) {
+        /* FIXME: handle parse messages splitted into multiple chunks */
+        fprintf(stderr, "%s: size mismatch: chunk %d, msg %d (+%zd)\n",
+                __func__, vd->msgsize, msg->size, sizeof(*msg));
+        return;
+    }
+
+    trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type));
+
+    switch (msg->type) {
+    case VD_AGENT_ANNOUNCE_CAPABILITIES:
+        vdagent_chr_recv_caps(vd, msg);
+        break;
+    default:
+        break;
+    }
+}
+
+static void vdagent_reset_bufs(VDAgentChardev *vd)
+{
+    memset(&vd->chunk, 0, sizeof(vd->chunk));
+    vd->chunksize = 0;
+    g_free(vd->msgbuf);
+    vd->msgbuf = NULL;
+    vd->msgsize = 0;
+}
+
+static int vdagent_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+    VDAgentChardev *vd = VDAGENT_CHARDEV(chr);
+    uint32_t copy, ret = len;
+
+    while (len) {
+        if (vd->chunksize < sizeof(vd->chunk)) {
+            copy = sizeof(vd->chunk) - vd->chunksize;
+            if (copy > len) {
+                copy = len;
+            }
+            memcpy((void *)(&vd->chunk) + vd->chunksize, buf, copy);
+            vd->chunksize += copy;
+            buf += copy;
+            len -= copy;
+            if (vd->chunksize < sizeof(vd->chunk)) {
+                break;
+            }
+
+            assert(vd->msgbuf == NULL);
+            vd->msgbuf = g_malloc0(vd->chunk.size);
+        }
+
+        copy = vd->chunk.size - vd->msgsize;
+        if (copy > len) {
+            copy = len;
+        }
+        memcpy(vd->msgbuf + vd->msgsize, buf, copy);
+        vd->msgsize += copy;
+        buf += copy;
+        len -= copy;
+
+        if (vd->msgsize == vd->chunk.size) {
+            trace_vdagent_recv_chunk(vd->chunk.size);
+            vdagent_chr_recv(vd);
+            vdagent_reset_bufs(vd);
+        }
+    }
+
+    return ret;
+}
+
+static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
+{
+    VDAgentChardev *vd = VDAGENT_CHARDEV(chr);
+
+    if (!fe_open) {
+        trace_vdagent_close();
+        /* reset state */
+        vdagent_reset_bufs(vd);
+        vd->caps = 0;
+        return;
+    }
+
+    trace_vdagent_open();
+}
+
+/* ------------------------------------------------------------------ */
+
+static void vdagent_chr_class_init(ObjectClass *oc, void *data)
+{
+    ChardevClass *cc = CHARDEV_CLASS(oc);
+
+    cc->open             = vdagent_chr_open;
+    cc->chr_write        = vdagent_chr_write;
+    cc->chr_set_fe_open  = vdagent_chr_set_fe_open;
+}
+
+static const TypeInfo vdagent_chr_type_info = {
+    .name = TYPE_CHARDEV_VDAGENT,
+    .parent = TYPE_CHARDEV,
+    .instance_size = sizeof(VDAgentChardev),
+    .class_init = vdagent_chr_class_init,
+};
+
+static void register_types(void)
+{
+    type_register_static(&vdagent_chr_type_info);
+}
+
+type_init(register_types);
diff --git a/qapi/char.json b/qapi/char.json
index 6413970fa73b..6e565ce42753 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -390,6 +390,17 @@
   'data': { '*size': 'int' },
   'base': 'ChardevCommon' }
 
+##
+# @ChardevVDAgent:
+#
+# Configuration info for vdagent.
+#
+# Since: 6.0
+##
+{ 'struct': 'ChardevVDAgent',
+  'data': { },
+  'base': 'ChardevCommon' }
+
 ##
 # @ChardevBackend:
 #
@@ -417,6 +428,8 @@
                           'if': 'defined(CONFIG_SPICE)' },
             'spiceport': { 'type': 'ChardevSpicePort',
                            'if': 'defined(CONFIG_SPICE)' },
+            'vdagent': { 'type': 'ChardevVDAgent',
+                         'if': 'defined(CONFIG_SPICE)' },
             'vc': 'ChardevVC',
             'ringbuf': 'ChardevRingbuf',
             # next one is just for compatibility
diff --git a/ui/meson.build b/ui/meson.build
index fc4fb75c2869..3d382b3b9019 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -14,6 +14,7 @@ softmmu_ss.add(files(
   'qemu-pixman.c',
 ))
 softmmu_ss.add([spice_headers, files('spice-module.c')])
+softmmu_ss.add(when: spice_headers, if_true: files('vdagent.c'))
 
 softmmu_ss.add(when: 'CONFIG_LINUX', if_true: files('input-linux.c'))
 softmmu_ss.add(when: cocoa, if_true: files('cocoa.m'))
diff --git a/ui/trace-events b/ui/trace-events
index 5d1da6f23668..c286065f1a94 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -124,3 +124,11 @@ xkeymap_extension(const char *name) "extension '%s'"
 xkeymap_vendor(const char *name) "vendor '%s'"
 xkeymap_keycodes(const char *name) "keycodes '%s'"
 xkeymap_keymap(const char *name) "keymap '%s'"
+
+# vdagent.c
+vdagent_open(void) ""
+vdagent_close(void) ""
+vdagent_send(const char *name) "msg %s"
+vdagent_recv_chunk(uint32_t size) "size %d"
+vdagent_recv_msg(const char *name) "msg %s"
+vdagent_peer_cap(const char *name) "cap %s"
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 3/7] ui/vdagent: add mouse support
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 1/7] ui: add clipboard infrastructure Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 2/7] ui/vdagent: core infrastructure Gerd Hoffmann
@ 2021-03-18  9:16 ` Gerd Hoffmann
  2021-03-18 10:24   ` Marc-André Lureau
  2021-03-18  9:16 ` [PATCH v2 4/7] ui/vdagent: add clipboard support Gerd Hoffmann
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

This patch adds support for mouse messages to the vdagent
implementation.  This can be enabled/disabled using the new
'mouse' parameter for the vdagent chardev.  Default is on.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 chardev/char.c |   3 ++
 ui/vdagent.c   | 141 +++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/char.json |   4 +-
 3 files changed, 147 insertions(+), 1 deletion(-)

diff --git a/chardev/char.c b/chardev/char.c
index 97cafd68494a..fd4d86d0dd3f 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -927,6 +927,9 @@ QemuOptsList qemu_chardev_opts = {
         },{
             .name = "logappend",
             .type = QEMU_OPT_BOOL,
+        },{
+            .name = "mouse",
+            .type = QEMU_OPT_BOOL,
 #ifdef CONFIG_LINUX
         },{
             .name = "tight",
diff --git a/ui/vdagent.c b/ui/vdagent.c
index 146ddb8e31b4..61c12b02b573 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -1,21 +1,38 @@
 #include "qemu/osdep.h"
 #include "include/qemu-common.h"
 #include "chardev/char.h"
+#include "hw/qdev-core.h"
+#include "qemu/option.h"
+#include "ui/console.h"
+#include "ui/input.h"
 #include "trace.h"
 
 #include "qapi/qapi-types-char.h"
+#include "qapi/qapi-types-ui.h"
 
 #include "spice/vd_agent.h"
 
+#define VDAGENT_MOUSE_DEFAULT true
+
 struct VDAgentChardev {
     Chardev parent;
 
+    /* config */
+    bool mouse;
+
     /* guest vdagent */
     uint32_t caps;
     VDIChunkHeader chunk;
     uint32_t chunksize;
     uint8_t *msgbuf;
     uint32_t msgsize;
+
+    /* mouse */
+    DeviceState mouse_dev;
+    uint32_t mouse_x;
+    uint32_t mouse_y;
+    uint32_t mouse_btn;
+    QemuInputHandlerState *mouse_hs;
 };
 typedef struct VDAgentChardev VDAgentChardev;
 
@@ -120,13 +137,105 @@ static void vdagent_send_caps(VDAgentChardev *vd)
     g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
                                                sizeof(VDAgentAnnounceCapabilities) +
                                                sizeof(uint32_t));
+    VDAgentAnnounceCapabilities *caps = (void *)msg->data;
 
     msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
     msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t);
+    if (vd->mouse) {
+        caps->caps[0] |= (1 << VD_AGENT_CAP_MOUSE_STATE);
+    }
 
     vdagent_send_msg(vd, msg);
 }
 
+/* ------------------------------------------------------------------ */
+/* mouse events                                                       */
+
+static void vdagent_send_mouse(VDAgentChardev *vd)
+{
+    g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+                                               sizeof(VDAgentMouseState));
+    VDAgentMouseState *mouse = (void *)msg->data;
+
+    msg->type = VD_AGENT_MOUSE_STATE;
+    msg->size = sizeof(VDAgentMouseState);
+
+    mouse->x       = vd->mouse_x;
+    mouse->y       = vd->mouse_y;
+    mouse->buttons = vd->mouse_btn;
+
+    vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_pointer_event(DeviceState *dev, QemuConsole *src,
+                                  InputEvent *evt)
+{
+    static const int bmap[INPUT_BUTTON__MAX] = {
+        [INPUT_BUTTON_LEFT]        = VD_AGENT_LBUTTON_MASK,
+        [INPUT_BUTTON_RIGHT]       = VD_AGENT_RBUTTON_MASK,
+        [INPUT_BUTTON_MIDDLE]      = VD_AGENT_MBUTTON_MASK,
+        [INPUT_BUTTON_WHEEL_UP]    = VD_AGENT_UBUTTON_MASK,
+        [INPUT_BUTTON_WHEEL_DOWN]  = VD_AGENT_DBUTTON_MASK,
+#if 0
+        [INPUT_BUTTON_SIDE]        = VD_AGENT_SBUTTON_MASK,
+        [INPUT_BUTTON_EXTRA]       = VD_AGENT_EBUTTON_MASK,
+#endif
+    };
+
+    VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev);
+    InputMoveEvent *move;
+    InputBtnEvent *btn;
+    uint32_t xres, yres;
+
+    switch (evt->type) {
+    case INPUT_EVENT_KIND_ABS:
+        move = evt->u.abs.data;
+        xres = qemu_console_get_width(src, 1024);
+        yres = qemu_console_get_height(src, 768);
+        if (move->axis == INPUT_AXIS_X) {
+            vd->mouse_x = qemu_input_scale_axis(move->value,
+                                                INPUT_EVENT_ABS_MIN,
+                                                INPUT_EVENT_ABS_MAX,
+                                                0, xres);
+        } else if (move->axis == INPUT_AXIS_Y) {
+            vd->mouse_y = qemu_input_scale_axis(move->value,
+                                                INPUT_EVENT_ABS_MIN,
+                                                INPUT_EVENT_ABS_MAX,
+                                                0, yres);
+        }
+        break;
+
+    case INPUT_EVENT_KIND_BTN:
+        btn = evt->u.btn.data;
+        if (btn->down) {
+            vd->mouse_btn |= bmap[btn->button];
+        } else {
+            vd->mouse_btn &= ~bmap[btn->button];
+        }
+        break;
+
+    default:
+        /* keep gcc happy */
+        break;
+    }
+}
+
+static void vdagent_pointer_sync(DeviceState *dev)
+{
+    VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev);
+
+    if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)) {
+        vdagent_send_mouse(vd);
+    }
+}
+
+static QemuInputHandler vdagent_mouse_handler = {
+    .name  = "vdagent mouse",
+    .mask  = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+    .event = vdagent_pointer_event,
+    .sync  = vdagent_pointer_sync,
+};
+
 /* ------------------------------------------------------------------ */
 /* chardev backend                                                    */
 
@@ -135,6 +244,19 @@ static void vdagent_chr_open(Chardev *chr,
                              bool *be_opened,
                              Error **errp)
 {
+    VDAgentChardev *vd = VDAGENT_CHARDEV(chr);
+    ChardevVDAgent *cfg = backend->u.vdagent.data;
+
+    vd->mouse = VDAGENT_MOUSE_DEFAULT;
+    if (cfg->has_mouse) {
+        vd->mouse = cfg->mouse;
+    }
+
+    if (vd->mouse) {
+        vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev,
+                                                   &vdagent_mouse_handler);
+    }
+
     *be_opened = true;
 }
 
@@ -158,6 +280,9 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
     if (caps->request) {
         vdagent_send_caps(vd);
     }
+    if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE) && vd->mouse_hs) {
+        qemu_input_handler_activate(vd->mouse_hs);
+    }
 }
 
 static void vdagent_chr_recv(VDAgentChardev *vd)
@@ -247,18 +372,34 @@ static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
         /* reset state */
         vdagent_reset_bufs(vd);
         vd->caps = 0;
+        if (vd->mouse_hs) {
+            qemu_input_handler_deactivate(vd->mouse_hs);
+        }
         return;
     }
 
     trace_vdagent_open();
 }
 
+static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend,
+                              Error **errp)
+{
+    ChardevVDAgent *cfg;
+
+    backend->type = CHARDEV_BACKEND_KIND_VDAGENT;
+    cfg = backend->u.vdagent.data = g_new0(ChardevVDAgent, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevVDAgent_base(cfg));
+    cfg->has_mouse = true;
+    cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT);
+}
+
 /* ------------------------------------------------------------------ */
 
 static void vdagent_chr_class_init(ObjectClass *oc, void *data)
 {
     ChardevClass *cc = CHARDEV_CLASS(oc);
 
+    cc->parse            = vdagent_chr_parse;
     cc->open             = vdagent_chr_open;
     cc->chr_write        = vdagent_chr_write;
     cc->chr_set_fe_open  = vdagent_chr_set_fe_open;
diff --git a/qapi/char.json b/qapi/char.json
index 6e565ce42753..586ef2137368 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -395,10 +395,12 @@
 #
 # Configuration info for vdagent.
 #
+# @mouse: enable/disable mouse, default is enabled.
+#
 # Since: 6.0
 ##
 { 'struct': 'ChardevVDAgent',
-  'data': { },
+  'data': { '*mouse'    : 'bool' },
   'base': 'ChardevCommon' }
 
 ##
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 4/7] ui/vdagent: add clipboard support
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (2 preceding siblings ...)
  2021-03-18  9:16 ` [PATCH v2 3/7] ui/vdagent: add mouse support Gerd Hoffmann
@ 2021-03-18  9:16 ` Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 5/7] ui/vnc: " Gerd Hoffmann
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

This patch adds support for clipboard messages to the qemu vdagent
implementation, which allows the guest exchange clipboard data with
qemu.  Clipboard support can be enabled/disabled using the new
'clipboard' parameter for the vdagent chardev.  Default is off.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 chardev/char.c  |   3 +
 ui/vdagent.c    | 259 ++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/char.json  |   4 +-
 ui/trace-events |   2 +
 4 files changed, 267 insertions(+), 1 deletion(-)

diff --git a/chardev/char.c b/chardev/char.c
index fd4d86d0dd3f..353f3eb298d9 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -930,6 +930,9 @@ QemuOptsList qemu_chardev_opts = {
         },{
             .name = "mouse",
             .type = QEMU_OPT_BOOL,
+        },{
+            .name = "clipboard",
+            .type = QEMU_OPT_BOOL,
 #ifdef CONFIG_LINUX
         },{
             .name = "tight",
diff --git a/ui/vdagent.c b/ui/vdagent.c
index 61c12b02b573..89485973c512 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -3,6 +3,7 @@
 #include "chardev/char.h"
 #include "hw/qdev-core.h"
 #include "qemu/option.h"
+#include "ui/clipboard.h"
 #include "ui/console.h"
 #include "ui/input.h"
 #include "trace.h"
@@ -13,12 +14,14 @@
 #include "spice/vd_agent.h"
 
 #define VDAGENT_MOUSE_DEFAULT true
+#define VDAGENT_CLIPBOARD_DEFAULT false
 
 struct VDAgentChardev {
     Chardev parent;
 
     /* config */
     bool mouse;
+    bool clipboard;
 
     /* guest vdagent */
     uint32_t caps;
@@ -33,6 +36,11 @@ struct VDAgentChardev {
     uint32_t mouse_y;
     uint32_t mouse_btn;
     QemuInputHandlerState *mouse_hs;
+
+    /* clipboard */
+    QemuClipboardPeer cbpeer;
+    QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
+    uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
 };
 typedef struct VDAgentChardev VDAgentChardev;
 
@@ -88,6 +96,24 @@ static const char *msg_name[] = {
 #endif
 };
 
+static const char *sel_name[] = {
+    [VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD] = "clipboard",
+    [VD_AGENT_CLIPBOARD_SELECTION_PRIMARY]   = "primary",
+    [VD_AGENT_CLIPBOARD_SELECTION_SECONDARY] = "secondary",
+};
+
+static const char *type_name[] = {
+    [VD_AGENT_CLIPBOARD_NONE]       = "none",
+    [VD_AGENT_CLIPBOARD_UTF8_TEXT]  = "text",
+    [VD_AGENT_CLIPBOARD_IMAGE_PNG]  = "png",
+    [VD_AGENT_CLIPBOARD_IMAGE_BMP]  = "bmp",
+    [VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
+    [VD_AGENT_CLIPBOARD_IMAGE_JPG]  = "jpg",
+#if 0
+    [VD_AGENT_CLIPBOARD_FILE_LIST]  = "files",
+#endif
+};
+
 #define GET_NAME(_m, _v) \
     (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???")
 
@@ -144,6 +170,10 @@ static void vdagent_send_caps(VDAgentChardev *vd)
     if (vd->mouse) {
         caps->caps[0] |= (1 << VD_AGENT_CAP_MOUSE_STATE);
     }
+    if (vd->clipboard) {
+        caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
+        caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
+    }
 
     vdagent_send_msg(vd, msg);
 }
@@ -236,6 +266,209 @@ static QemuInputHandler vdagent_mouse_handler = {
     .sync  = vdagent_pointer_sync,
 };
 
+/* ------------------------------------------------------------------ */
+/* clipboard                                                          */
+
+static uint32_t type_qemu_to_vdagent(enum QemuClipboardType type)
+{
+    switch (type) {
+    case QEMU_CLIPBOARD_TYPE_TEXT:
+        return VD_AGENT_CLIPBOARD_UTF8_TEXT;
+    default:
+        return VD_AGENT_CLIPBOARD_NONE;
+    }
+}
+
+static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
+                                        QemuClipboardInfo *info)
+{
+    g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+                                               sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1));
+    uint8_t *s = msg->data;
+    uint32_t *data = (uint32_t *)(msg->data + 4);
+    uint32_t q, v, type;
+
+    for (q = 0, v = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
+        type = type_qemu_to_vdagent(q);
+        if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
+            data[v++] = type;
+        }
+    }
+
+    *s = info->selection;
+    msg->type = VD_AGENT_CLIPBOARD_GRAB;
+    msg->size = sizeof(uint32_t) * (v + 1);
+
+    vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_send_clipboard_data(VDAgentChardev *vd,
+                                        QemuClipboardInfo *info,
+                                        QemuClipboardType type)
+{
+    g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+                                               sizeof(uint32_t) * 2 +
+                                               info->types[type].size);
+
+    uint8_t *s = msg->data;
+    uint32_t *t = (uint32_t *)(msg->data + 4);
+    uint8_t *d = msg->data + 8;
+
+    *s = info->selection;
+    *t = type_qemu_to_vdagent(type);
+    memcpy(d, info->types[type].data, info->types[type].size);
+
+    msg->type = VD_AGENT_CLIPBOARD;
+    msg->size = sizeof(uint32_t) * 2 + info->types[type].size;
+
+    vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+{
+    VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update);
+    QemuClipboardInfo *info = data;
+    QemuClipboardSelection s = info->selection;
+    QemuClipboardType type;
+    bool self_update = info->owner == &vd->cbpeer;
+
+    if (info != vd->cbinfo[s]) {
+        qemu_clipboard_info_put(vd->cbinfo[s]);
+        vd->cbinfo[s] = qemu_clipboard_info_get(info);
+        vd->cbpending[s] = 0;
+        if (!self_update) {
+            vdagent_send_clipboard_grab(vd, info);
+        }
+        return;
+    }
+
+    if (self_update) {
+        return;
+    }
+
+    for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
+        if (vd->cbpending[s] & (1 << type)) {
+            vd->cbpending[s] &= ~(1 << type);
+            vdagent_send_clipboard_data(vd, info, type);
+        }
+    }
+}
+
+static void vdagent_clipboard_request(QemuClipboardInfo *info,
+                                      QemuClipboardType qtype)
+{
+    VDAgentChardev *vd = container_of(info->owner, VDAgentChardev, cbpeer);
+    g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+                                               sizeof(uint32_t) * 2);
+    uint32_t type = type_qemu_to_vdagent(qtype);
+    uint8_t *s = msg->data;
+    uint32_t *data = (uint32_t *)(msg->data + 4);
+
+    if (type == VD_AGENT_CLIPBOARD_NONE) {
+        return;
+    }
+
+    *s = info->selection;
+    *data = type;
+    msg->type = VD_AGENT_CLIPBOARD_REQUEST;
+    msg->size = sizeof(uint32_t) * 2;
+
+    vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_chr_recv_clipboard(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+    uint8_t s = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+    uint32_t size = msg->size;
+    void *data = msg->data;
+    QemuClipboardInfo *info;
+    QemuClipboardType type;
+
+    if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        if (size < 4) {
+            return;
+        }
+        s = *(uint8_t *)data;
+        data += 4;
+        size -= 4;
+    }
+
+    switch (msg->type) {
+    case VD_AGENT_CLIPBOARD_GRAB:
+        trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s));
+        info = qemu_clipboard_info_new(&vd->cbpeer, s);
+        if (size > sizeof(uint32_t) * 10) {
+            /*
+             * spice has 6 types as of 2021. Limiting to 10 entries
+             * so we we have some wiggle room.
+             */
+            return;
+        }
+        while (size >= sizeof(uint32_t)) {
+            trace_vdagent_cb_grab_type(GET_NAME(type_name, *(uint32_t *)data));
+            switch (*(uint32_t *)data) {
+            case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+                info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+                break;
+            default:
+                break;
+            }
+            data += sizeof(uint32_t);
+            size -= sizeof(uint32_t);
+        }
+        qemu_clipboard_update(info);
+        qemu_clipboard_info_put(info);
+        break;
+    case VD_AGENT_CLIPBOARD_REQUEST:
+        if (size < sizeof(uint32_t)) {
+            return;
+        }
+        switch (*(uint32_t *)data) {
+        case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+            type = QEMU_CLIPBOARD_TYPE_TEXT;
+            break;
+        default:
+            return;
+        }
+        if (vd->cbinfo[s] &&
+            vd->cbinfo[s]->types[type].available &&
+            vd->cbinfo[s]->owner != &vd->cbpeer) {
+            if (vd->cbinfo[s]->types[type].data) {
+                vdagent_send_clipboard_data(vd, vd->cbinfo[s], type);
+            } else {
+                vd->cbpending[s] |= (1 << type);
+                qemu_clipboard_request(vd->cbinfo[s], type);
+            }
+        }
+        break;
+    case VD_AGENT_CLIPBOARD: /* data */
+        if (size < sizeof(uint32_t)) {
+            return;
+        }
+        switch (*(uint32_t *)data) {
+        case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+            type = QEMU_CLIPBOARD_TYPE_TEXT;
+            break;
+        default:
+            return;
+        }
+        data += 4;
+        size -= 4;
+        qemu_clipboard_set_data(&vd->cbpeer, vd->cbinfo[s], type,
+                                size, data, true);
+        break;
+    case VD_AGENT_CLIPBOARD_RELEASE: /* data */
+        if (vd->cbinfo[s] &&
+            vd->cbinfo[s]->owner == &vd->cbpeer) {
+            /* set empty clipboard info */
+            info = qemu_clipboard_info_new(NULL, s);
+            qemu_clipboard_update(info);
+            qemu_clipboard_info_put(info);
+        }
+        break;
+    }
+}
+
 /* ------------------------------------------------------------------ */
 /* chardev backend                                                    */
 
@@ -252,6 +485,11 @@ static void vdagent_chr_open(Chardev *chr,
         vd->mouse = cfg->mouse;
     }
 
+    vd->clipboard = VDAGENT_CLIPBOARD_DEFAULT;
+    if (cfg->has_clipboard) {
+        vd->clipboard = cfg->clipboard;
+    }
+
     if (vd->mouse) {
         vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev,
                                                    &vdagent_mouse_handler);
@@ -283,6 +521,15 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
     if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE) && vd->mouse_hs) {
         qemu_input_handler_activate(vd->mouse_hs);
     }
+    if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND) &&
+        vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION) &&
+        vd->clipboard &&
+        vd->cbpeer.update.notify == NULL) {
+        vd->cbpeer.name = "vdagent";
+        vd->cbpeer.update.notify = vdagent_clipboard_notify;
+        vd->cbpeer.request = vdagent_clipboard_request;
+        qemu_clipboard_peer_register(&vd->cbpeer);
+    }
 }
 
 static void vdagent_chr_recv(VDAgentChardev *vd)
@@ -307,6 +554,12 @@ static void vdagent_chr_recv(VDAgentChardev *vd)
     case VD_AGENT_ANNOUNCE_CAPABILITIES:
         vdagent_chr_recv_caps(vd, msg);
         break;
+    case VD_AGENT_CLIPBOARD:
+    case VD_AGENT_CLIPBOARD_GRAB:
+    case VD_AGENT_CLIPBOARD_REQUEST:
+    case VD_AGENT_CLIPBOARD_RELEASE:
+        vdagent_chr_recv_clipboard(vd, msg);
+        break;
     default:
         break;
     }
@@ -375,6 +628,10 @@ static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
         if (vd->mouse_hs) {
             qemu_input_handler_deactivate(vd->mouse_hs);
         }
+        if (vd->cbpeer.update.notify) {
+            qemu_clipboard_peer_unregister(&vd->cbpeer);
+            memset(&vd->cbpeer, 0, sizeof(vd->cbpeer));
+        }
         return;
     }
 
@@ -391,6 +648,8 @@ static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend,
     qemu_chr_parse_common(opts, qapi_ChardevVDAgent_base(cfg));
     cfg->has_mouse = true;
     cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT);
+    cfg->has_clipboard = true;
+    cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT);
 }
 
 /* ------------------------------------------------------------------ */
diff --git a/qapi/char.json b/qapi/char.json
index 586ef2137368..86b8e202b0ba 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -396,11 +396,13 @@
 # Configuration info for vdagent.
 #
 # @mouse: enable/disable mouse, default is enabled.
+# @clipboard: enable/disable clipboard, default is disabled.
 #
 # Since: 6.0
 ##
 { 'struct': 'ChardevVDAgent',
-  'data': { '*mouse'    : 'bool' },
+  'data': { '*mouse'    : 'bool',
+            '*clipboard': 'bool' },
   'base': 'ChardevCommon' }
 
 ##
diff --git a/ui/trace-events b/ui/trace-events
index c286065f1a94..bcb5954a549a 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -132,3 +132,5 @@ vdagent_send(const char *name) "msg %s"
 vdagent_recv_chunk(uint32_t size) "size %d"
 vdagent_recv_msg(const char *name) "msg %s"
 vdagent_peer_cap(const char *name) "cap %s"
+vdagent_cb_grab_selection(const char *name) "selection %s"
+vdagent_cb_grab_type(const char *name) "type %s"
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 5/7] ui/vnc: clipboard support
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (3 preceding siblings ...)
  2021-03-18  9:16 ` [PATCH v2 4/7] ui/vdagent: add clipboard support Gerd Hoffmann
@ 2021-03-18  9:16 ` Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 6/7] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

This patch adds support for cut+paste to the qemu vnc server, which
allows the vnc client exchange clipbaord data with qemu and other peers
like the qemu vdagent implementation.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 ui/vnc.h           |  24 ++++
 ui/vnc-clipboard.c | 323 +++++++++++++++++++++++++++++++++++++++++++++
 ui/vnc.c           |  20 ++-
 ui/meson.build     |   1 +
 4 files changed, 362 insertions(+), 6 deletions(-)
 create mode 100644 ui/vnc-clipboard.c

diff --git a/ui/vnc.h b/ui/vnc.h
index d4f3e1555809..a7149831f906 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -29,6 +29,7 @@
 
 #include "qemu/queue.h"
 #include "qemu/thread.h"
+#include "ui/clipboard.h"
 #include "ui/console.h"
 #include "audio/audio.h"
 #include "qemu/bitmap.h"
@@ -348,6 +349,10 @@ struct VncState
 
     Notifier mouse_mode_notifier;
 
+    QemuClipboardPeer cbpeer;
+    QemuClipboardInfo *cbinfo;
+    uint32_t cbpending;
+
     QTAILQ_ENTRY(VncState) next;
 };
 
@@ -417,6 +422,7 @@ enum {
 #define VNC_ENCODING_XVP                  0XFFFFFECB /* -309 */
 #define VNC_ENCODING_ALPHA_CURSOR         0XFFFFFEC6 /* -314 */
 #define VNC_ENCODING_WMVi                 0x574D5669
+#define VNC_ENCODING_CLIPBOARD_EXT        0xc0a1e5ce
 
 /*****************************************************************************
  *
@@ -458,6 +464,7 @@ enum VncFeatures {
     VNC_FEATURE_ZYWRLE,
     VNC_FEATURE_LED_STATE,
     VNC_FEATURE_XVP,
+    VNC_FEATURE_CLIPBOARD_EXT,
 };
 
 #define VNC_FEATURE_RESIZE_MASK              (1 << VNC_FEATURE_RESIZE)
@@ -474,6 +481,7 @@ enum VncFeatures {
 #define VNC_FEATURE_ZYWRLE_MASK              (1 << VNC_FEATURE_ZYWRLE)
 #define VNC_FEATURE_LED_STATE_MASK           (1 << VNC_FEATURE_LED_STATE)
 #define VNC_FEATURE_XVP_MASK                 (1 << VNC_FEATURE_XVP)
+#define VNC_FEATURE_CLIPBOARD_EXT_MASK       (1 <<  VNC_FEATURE_CLIPBOARD_EXT)
 
 
 /* Client -> Server message IDs */
@@ -535,6 +543,17 @@ enum VncFeatures {
 #define VNC_XVP_ACTION_REBOOT 3
 #define VNC_XVP_ACTION_RESET 4
 
+/* extended clipboard flags  */
+#define VNC_CLIPBOARD_TEXT     (1 << 0)
+#define VNC_CLIPBOARD_RTF      (1 << 1)
+#define VNC_CLIPBOARD_HTML     (1 << 2)
+#define VNC_CLIPBOARD_DIB      (1 << 3)
+#define VNC_CLIPBOARD_FILES    (1 << 4)
+#define VNC_CLIPBOARD_CAPS     (1 << 24)
+#define VNC_CLIPBOARD_REQUEST  (1 << 25)
+#define VNC_CLIPBOARD_PEEK     (1 << 26)
+#define VNC_CLIPBOARD_NOTIFY   (1 << 27)
+#define VNC_CLIPBOARD_PROVIDE  (1 << 28)
 
 /*****************************************************************************
  *
@@ -618,4 +637,9 @@ int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
 int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
 void vnc_zrle_clear(VncState *vs);
 
+/* vnc-clipboard.c */
+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);
+
 #endif /* QEMU_VNC_H */
diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c
new file mode 100644
index 000000000000..10ada8004f38
--- /dev/null
+++ b/ui/vnc-clipboard.c
@@ -0,0 +1,323 @@
+/*
+ * QEMU VNC display driver -- clipboard support
+ *
+ * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "vnc.h"
+#include "vnc-jobs.h"
+
+static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
+{
+    z_stream stream = {
+        .next_in  = in,
+        .avail_in = in_len,
+        .zalloc   = Z_NULL,
+        .zfree    = Z_NULL,
+    };
+    uint32_t out_len = 8;
+    uint8_t *out = g_malloc(out_len);
+    int ret;
+
+    stream.next_out = out + stream.total_out;
+    stream.avail_out = out_len - stream.total_out;
+
+    ret = inflateInit(&stream);
+    if (ret != Z_OK) {
+        goto err;
+    }
+
+    while (stream.avail_in) {
+        ret = inflate(&stream, Z_FINISH);
+        switch (ret) {
+        case Z_OK:
+        case Z_STREAM_END:
+            break;
+        case Z_BUF_ERROR:
+            out_len <<= 1;
+            if (out_len > (1 << 20)) {
+                goto err_end;
+            }
+            out = g_realloc(out, out_len);
+            stream.next_out = out + stream.total_out;
+            stream.avail_out = out_len - stream.total_out;
+            break;
+        default:
+            goto err_end;
+        }
+    }
+
+    *size = stream.total_out;
+    inflateEnd(&stream);
+
+    return out;
+
+err_end:
+    inflateEnd(&stream);
+err:
+    g_free(out);
+    return NULL;
+}
+
+static uint8_t *deflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
+{
+    z_stream stream = {
+        .next_in  = in,
+        .avail_in = in_len,
+        .zalloc   = Z_NULL,
+        .zfree    = Z_NULL,
+    };
+    uint32_t out_len = 8;
+    uint8_t *out = g_malloc(out_len);
+    int ret;
+
+    stream.next_out = out + stream.total_out;
+    stream.avail_out = out_len - stream.total_out;
+
+    ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+    if (ret != Z_OK) {
+        goto err;
+    }
+
+    while (ret != Z_STREAM_END) {
+        ret = deflate(&stream, Z_FINISH);
+        switch (ret) {
+        case Z_OK:
+        case Z_STREAM_END:
+            break;
+        case Z_BUF_ERROR:
+            out_len <<= 1;
+            if (out_len > (1 << 20)) {
+                goto err_end;
+            }
+            out = g_realloc(out, out_len);
+            stream.next_out = out + stream.total_out;
+            stream.avail_out = out_len - stream.total_out;
+            break;
+        default:
+            goto err_end;
+        }
+    }
+
+    *size = stream.total_out;
+    deflateEnd(&stream);
+
+    return out;
+
+err_end:
+    deflateEnd(&stream);
+err:
+    g_free(out);
+    return NULL;
+}
+
+static void vnc_clipboard_send(VncState *vs, uint32_t count, uint32_t *dwords)
+{
+    int i;
+
+    vnc_lock_output(vs);
+    vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
+    vnc_write_u8(vs, 0);
+    vnc_write_u8(vs, 0);
+    vnc_write_u8(vs, 0);
+    vnc_write_s32(vs, -(count * sizeof(uint32_t)));  /* -(message length) */
+    for (i = 0; i < count; i++) {
+        vnc_write_u32(vs, dwords[i]);
+    }
+    vnc_unlock_output(vs);
+    vnc_flush(vs);
+}
+
+static void vnc_clipboard_provide(VncState *vs,
+                                  QemuClipboardInfo *info,
+                                  QemuClipboardType type)
+{
+    uint32_t flags = 0;
+    g_autofree uint8_t *buf = NULL;
+    g_autofree void *zbuf = NULL;
+    uint32_t zsize;
+
+    switch (type) {
+    case QEMU_CLIPBOARD_TYPE_TEXT:
+        flags |= VNC_CLIPBOARD_TEXT;
+        break;
+    default:
+        return;
+    }
+    flags |= VNC_CLIPBOARD_PROVIDE;
+
+    buf = g_malloc(info->types[type].size + 4);
+    buf[0] = (info->types[type].size >> 24) & 0xff;
+    buf[1] = (info->types[type].size >> 16) & 0xff;
+    buf[2] = (info->types[type].size >>  8) & 0xff;
+    buf[3] = (info->types[type].size >>  0) & 0xff;
+    memcpy(buf + 4, info->types[type].data, info->types[type].size);
+    zbuf = deflate_buffer(buf, info->types[type].size + 4, &zsize);
+    if (!zbuf) {
+        return;
+    }
+
+    vnc_lock_output(vs);
+    vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
+    vnc_write_u8(vs, 0);
+    vnc_write_u8(vs, 0);
+    vnc_write_u8(vs, 0);
+    vnc_write_s32(vs, -(sizeof(uint32_t) + zsize));  /* -(message length) */
+    vnc_write_u32(vs, flags);
+    vnc_write(vs, zbuf, zsize);
+    vnc_unlock_output(vs);
+    vnc_flush(vs);
+}
+
+static void vnc_clipboard_notify(Notifier *notifier, void *data)
+{
+    VncState *vs = container_of(notifier, VncState, cbpeer.update);
+    QemuClipboardInfo *info = data;
+    QemuClipboardType type;
+    bool self_update = info->owner == &vs->cbpeer;
+    uint32_t flags = 0;
+
+    if (info != vs->cbinfo) {
+        qemu_clipboard_info_put(vs->cbinfo);
+        vs->cbinfo = qemu_clipboard_info_get(info);
+        vs->cbpending = 0;
+        if (!self_update) {
+            if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+                flags |= VNC_CLIPBOARD_TEXT;
+            }
+            flags |= VNC_CLIPBOARD_NOTIFY;
+            vnc_clipboard_send(vs, 1, &flags);
+        }
+        return;
+    }
+
+    if (self_update) {
+        return;
+    }
+
+    for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
+        if (vs->cbpending & (1 << type)) {
+            vs->cbpending &= ~(1 << type);
+            vnc_clipboard_provide(vs, info, type);
+        }
+    }
+}
+
+static void vnc_clipboard_request(QemuClipboardInfo *info,
+                                  QemuClipboardType type)
+{
+    VncState *vs = container_of(info->owner, VncState, cbpeer);
+    uint32_t flags = 0;
+
+    if (type == QEMU_CLIPBOARD_TYPE_TEXT) {
+        flags |= VNC_CLIPBOARD_TEXT;
+    }
+    if (!flags) {
+        return;
+    }
+    flags |= VNC_CLIPBOARD_REQUEST;
+
+    vnc_clipboard_send(vs, 1, &flags);
+}
+
+void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data)
+{
+    if (flags & VNC_CLIPBOARD_CAPS) {
+        /* need store caps somewhere ? */
+        return;
+    }
+
+    if (flags & VNC_CLIPBOARD_NOTIFY) {
+        QemuClipboardInfo *info =
+            qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+        if (flags & VNC_CLIPBOARD_TEXT) {
+            info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+        }
+        qemu_clipboard_update(info);
+        qemu_clipboard_info_put(info);
+        return;
+    }
+
+    if (flags & VNC_CLIPBOARD_PROVIDE &&
+        vs->cbinfo &&
+        vs->cbinfo->owner == &vs->cbpeer) {
+        uint32_t size = 0;
+        g_autofree uint8_t *buf = inflate_buffer(data, len - 4, &size);
+        if ((flags & VNC_CLIPBOARD_TEXT) &&
+            buf && size >= 4) {
+            uint32_t tsize = read_u32(buf, 0);
+            uint8_t *tbuf = buf + 4;
+            if (tsize < size) {
+                qemu_clipboard_set_data(&vs->cbpeer, vs->cbinfo,
+                                        QEMU_CLIPBOARD_TYPE_TEXT,
+                                        tsize, tbuf, true);
+            }
+        }
+    }
+
+    if (flags & VNC_CLIPBOARD_REQUEST &&
+        vs->cbinfo &&
+        vs->cbinfo->owner != &vs->cbpeer) {
+        if ((flags & VNC_CLIPBOARD_TEXT) &&
+            vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+            if (vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
+                vnc_clipboard_provide(vs, vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
+            } else {
+                vs->cbpending |= (1 << QEMU_CLIPBOARD_TYPE_TEXT);
+                qemu_clipboard_request(vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
+            }
+        }
+    }
+}
+
+void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text)
+{
+    QemuClipboardInfo *info =
+        qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+
+    qemu_clipboard_set_data(&vs->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
+                            len, text, true);
+    qemu_clipboard_info_put(info);
+}
+
+void vnc_server_cut_text_caps(VncState *vs)
+{
+    uint32_t caps[2];
+
+    if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) {
+        return;
+    }
+
+    caps[0] = (VNC_CLIPBOARD_PROVIDE |
+               VNC_CLIPBOARD_NOTIFY  |
+               VNC_CLIPBOARD_REQUEST |
+               VNC_CLIPBOARD_CAPS    |
+               VNC_CLIPBOARD_TEXT);
+    caps[1] = 0;
+    vnc_clipboard_send(vs, 2, caps);
+
+    vs->cbpeer.name = "vnc";
+    vs->cbpeer.update.notify = vnc_clipboard_notify;
+    vs->cbpeer.request = vnc_clipboard_request;
+    qemu_clipboard_peer_register(&vs->cbpeer);
+}
diff --git a/ui/vnc.c b/ui/vnc.c
index 9c004a11f495..040e707cc89a 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -25,6 +25,7 @@
  */
 
 #include "qemu/osdep.h"
+#include "qemu-common.h"
 #include "vnc.h"
 #include "vnc-jobs.h"
 #include "trace.h"
@@ -1324,6 +1325,9 @@ void vnc_disconnect_finish(VncState *vs)
         /* last client gone */
         vnc_update_server_surface(vs->vd);
     }
+    if (vs->cbpeer.update.notify) {
+        qemu_clipboard_peer_unregister(&vs->cbpeer);
+    }
 
     vnc_unlock_output(vs);
 
@@ -1749,10 +1753,6 @@ uint32_t read_u32(uint8_t *data, size_t offset)
             (data[offset + 2] << 8) | data[offset + 3]);
 }
 
-static void client_cut_text(VncState *vs, size_t len, uint8_t *text)
-{
-}
-
 static void check_pointer_type_change(Notifier *notifier, void *data)
 {
     VncState *vs = container_of(notifier, VncState, mouse_mode_notifier);
@@ -2194,6 +2194,10 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
                 send_xvp_message(vs, VNC_XVP_CODE_INIT);
             }
             break;
+        case VNC_ENCODING_CLIPBOARD_EXT:
+            vs->features |= VNC_FEATURE_CLIPBOARD_EXT_MASK;
+            vnc_server_cut_text_caps(vs);
+            break;
         case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9:
             vs->tight->compression = (enc & 0x0F);
             break;
@@ -2410,7 +2414,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
             return 8;
         }
         if (len == 8) {
-            uint32_t dlen = read_u32(data, 4);
+            uint32_t dlen = abs(read_s32(data, 4));
             if (dlen > (1 << 20)) {
                 error_report("vnc: client_cut_text msg payload has %u bytes"
                              " which exceeds our limit of 1MB.", dlen);
@@ -2422,7 +2426,11 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
             }
         }
 
-        client_cut_text(vs, read_u32(data, 4), data + 8);
+        if (read_s32(data, 4) < 0) {
+            vnc_client_cut_text_ext(vs, abs(read_s32(data, 4)), read_u32(data, 8), data + 12);
+            break;
+        }
+        vnc_client_cut_text(vs, read_u32(data, 4), data + 8);
         break;
     case VNC_MSG_CLIENT_XVP:
         if (!(vs->features & VNC_FEATURE_XVP)) {
diff --git a/ui/meson.build b/ui/meson.build
index 3d382b3b9019..6c0c6ddd74eb 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -30,6 +30,7 @@ vnc_ss.add(files(
   'vnc-auth-vencrypt.c',
   'vnc-ws.c',
   'vnc-jobs.c',
+  'vnc-clipboard.c',
 ))
 vnc_ss.add(zlib, png, jpeg, gnutls)
 vnc_ss.add(when: sasl, if_true: files('vnc-auth-sasl.c'))
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 6/7] ui/gtk: move struct GtkDisplayState to ui/gtk.h
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (4 preceding siblings ...)
  2021-03-18  9:16 ` [PATCH v2 5/7] ui/vnc: " Gerd Hoffmann
@ 2021-03-18  9:16 ` Gerd Hoffmann
  2021-03-18  9:16 ` [PATCH v2 7/7] ui/gtk: add clipboard support Gerd Hoffmann
  2021-03-18  9:30 ` [PATCH v2 0/7] ui: add vdagent implementation and " no-reply
  7 siblings, 0 replies; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

Want place gtk clipboard code in a separate C file, which in turn
requires GtkDisplayState being in a header file.  So move it.  No
functional change.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 include/ui/gtk.h | 57 ++++++++++++++++++++++++++++++++++++++++++++++++
 ui/gtk.c         | 55 ----------------------------------------------
 2 files changed, 57 insertions(+), 55 deletions(-)

diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 5ae0ad60a600..6e751794043f 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -18,12 +18,15 @@
 #include <gdk/gdkwayland.h>
 #endif
 
+#include "ui/console.h"
 #include "ui/kbd-state.h"
 #if defined(CONFIG_OPENGL)
 #include "ui/egl-helpers.h"
 #include "ui/egl-context.h"
 #endif
 
+#define MAX_VCS 10
+
 typedef struct GtkDisplayState GtkDisplayState;
 
 typedef struct VirtualGfxConsole {
@@ -83,6 +86,60 @@ typedef struct VirtualConsole {
     };
 } VirtualConsole;
 
+struct GtkDisplayState {
+    GtkWidget *window;
+
+    GtkWidget *menu_bar;
+
+    GtkAccelGroup *accel_group;
+
+    GtkWidget *machine_menu_item;
+    GtkWidget *machine_menu;
+    GtkWidget *pause_item;
+    GtkWidget *reset_item;
+    GtkWidget *powerdown_item;
+    GtkWidget *quit_item;
+
+    GtkWidget *view_menu_item;
+    GtkWidget *view_menu;
+    GtkWidget *full_screen_item;
+    GtkWidget *copy_item;
+    GtkWidget *zoom_in_item;
+    GtkWidget *zoom_out_item;
+    GtkWidget *zoom_fixed_item;
+    GtkWidget *zoom_fit_item;
+    GtkWidget *grab_item;
+    GtkWidget *grab_on_hover_item;
+
+    int nb_vcs;
+    VirtualConsole vc[MAX_VCS];
+
+    GtkWidget *show_tabs_item;
+    GtkWidget *untabify_item;
+    GtkWidget *show_menubar_item;
+
+    GtkWidget *vbox;
+    GtkWidget *notebook;
+    int button_mask;
+    gboolean last_set;
+    int last_x;
+    int last_y;
+    int grab_x_root;
+    int grab_y_root;
+    VirtualConsole *kbd_owner;
+    VirtualConsole *ptr_owner;
+
+    gboolean full_screen;
+
+    GdkCursor *null_cursor;
+    Notifier mouse_mode_notifier;
+    gboolean free_scale;
+
+    bool external_pause_update;
+
+    DisplayOptions *opts;
+};
+
 extern bool gtk_use_gl_area;
 
 /* ui/gtk.c */
diff --git a/ui/gtk.c b/ui/gtk.c
index 1ea12535284a..7da288a25156 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -60,7 +60,6 @@
 #include "chardev/char.h"
 #include "qom/object.h"
 
-#define MAX_VCS 10
 #define VC_WINDOW_X_MIN  320
 #define VC_WINDOW_Y_MIN  240
 #define VC_TERM_X_MIN     80
@@ -119,60 +118,6 @@
 static const guint16 *keycode_map;
 static size_t keycode_maplen;
 
-struct GtkDisplayState {
-    GtkWidget *window;
-
-    GtkWidget *menu_bar;
-
-    GtkAccelGroup *accel_group;
-
-    GtkWidget *machine_menu_item;
-    GtkWidget *machine_menu;
-    GtkWidget *pause_item;
-    GtkWidget *reset_item;
-    GtkWidget *powerdown_item;
-    GtkWidget *quit_item;
-
-    GtkWidget *view_menu_item;
-    GtkWidget *view_menu;
-    GtkWidget *full_screen_item;
-    GtkWidget *copy_item;
-    GtkWidget *zoom_in_item;
-    GtkWidget *zoom_out_item;
-    GtkWidget *zoom_fixed_item;
-    GtkWidget *zoom_fit_item;
-    GtkWidget *grab_item;
-    GtkWidget *grab_on_hover_item;
-
-    int nb_vcs;
-    VirtualConsole vc[MAX_VCS];
-
-    GtkWidget *show_tabs_item;
-    GtkWidget *untabify_item;
-    GtkWidget *show_menubar_item;
-
-    GtkWidget *vbox;
-    GtkWidget *notebook;
-    int button_mask;
-    gboolean last_set;
-    int last_x;
-    int last_y;
-    int grab_x_root;
-    int grab_y_root;
-    VirtualConsole *kbd_owner;
-    VirtualConsole *ptr_owner;
-
-    gboolean full_screen;
-
-    GdkCursor *null_cursor;
-    Notifier mouse_mode_notifier;
-    gboolean free_scale;
-
-    bool external_pause_update;
-
-    DisplayOptions *opts;
-};
-
 struct VCChardev {
     Chardev parent;
     VirtualConsole *console;
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 7/7] ui/gtk: add clipboard support
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (5 preceding siblings ...)
  2021-03-18  9:16 ` [PATCH v2 6/7] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
@ 2021-03-18  9:16 ` Gerd Hoffmann
  2021-03-18 10:55   ` Marc-André Lureau
  2021-03-18  9:30 ` [PATCH v2 0/7] ui: add vdagent implementation and " no-reply
  7 siblings, 1 reply; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-18  9:16 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Markus Armbruster, Marc-André Lureau

This patch adds clipboard support to the qemu gtk ui.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 include/ui/gtk.h   |  10 +++
 ui/gtk-clipboard.c | 189 +++++++++++++++++++++++++++++++++++++++++++++
 ui/gtk.c           |   1 +
 ui/meson.build     |   2 +-
 4 files changed, 201 insertions(+), 1 deletion(-)
 create mode 100644 ui/gtk-clipboard.c

diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 6e751794043f..9516670ebc87 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -18,6 +18,7 @@
 #include <gdk/gdkwayland.h>
 #endif
 
+#include "ui/clipboard.h"
 #include "ui/console.h"
 #include "ui/kbd-state.h"
 #if defined(CONFIG_OPENGL)
@@ -137,6 +138,12 @@ struct GtkDisplayState {
 
     bool external_pause_update;
 
+    QemuClipboardPeer cbpeer;
+    QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
+    uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
+    GtkClipboard *gtkcb[QEMU_CLIPBOARD_SELECTION__COUNT];
+    bool cbowner[QEMU_CLIPBOARD_SELECTION__COUNT];
+
     DisplayOptions *opts;
 };
 
@@ -207,4 +214,7 @@ void gtk_gl_area_init(void);
 int gd_gl_area_make_current(DisplayChangeListener *dcl,
                             QEMUGLContext ctx);
 
+/* gtk-clipboard.c */
+void gd_clipboard_init(GtkDisplayState *gd);
+
 #endif /* UI_GTK_H */
diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
new file mode 100644
index 000000000000..3585e27cab78
--- /dev/null
+++ b/ui/gtk-clipboard.c
@@ -0,0 +1,189 @@
+/*
+ * GTK UI -- clipboard support
+ *
+ * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+
+#include "ui/gtk.h"
+
+static QemuClipboardSelection gd_find_selection(GtkDisplayState *gd,
+                                                GtkClipboard *clipboard)
+{
+    QemuClipboardSelection s;
+
+    for (s = 0; s < QEMU_CLIPBOARD_SELECTION__COUNT; s++) {
+        if (gd->gtkcb[s] == clipboard) {
+            return s;
+        }
+    }
+    return QEMU_CLIPBOARD_SELECTION_CLIPBOARD;
+}
+
+static void gd_clipboard_get_data(GtkClipboard     *clipboard,
+                                  GtkSelectionData *selection_data,
+                                  guint             selection_info,
+                                  gpointer          data)
+{
+    GtkDisplayState *gd = data;
+    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
+    QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
+    QemuClipboardInfo *info = qemu_clipboard_info_get(gd->cbinfo[s]);
+
+    qemu_clipboard_request(info, type);
+    while (info == gd->cbinfo[s] &&
+           info->types[type].available &&
+           info->types[type].data == NULL) {
+        main_loop_wait(false);
+    }
+
+    if (info == gd->cbinfo[s] && gd->cbowner[s]) {
+        gtk_selection_data_set_text(selection_data,
+                                    info->types[type].data,
+                                    info->types[type].size);
+    } else {
+        /* clipboard owner changed while waiting for the data */
+    }
+
+    qemu_clipboard_info_put(info);
+}
+
+static void gd_clipboard_clear(GtkClipboard *clipboard,
+                               gpointer data)
+{
+    GtkDisplayState *gd = data;
+    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
+
+    gd->cbowner[s] = false;
+}
+
+static void gd_clipboard_notify(Notifier *notifier, void *data)
+{
+    GtkDisplayState *gd = container_of(notifier, GtkDisplayState, cbpeer.update);
+    QemuClipboardInfo *info = data;
+    QemuClipboardSelection s = info->selection;
+    bool self_update = info->owner == &gd->cbpeer;
+
+    if (info != gd->cbinfo[s]) {
+        qemu_clipboard_info_put(gd->cbinfo[s]);
+        gd->cbinfo[s] = qemu_clipboard_info_get(info);
+        gd->cbpending[s] = 0;
+        if (!self_update) {
+            GtkTargetList *list;
+            GtkTargetEntry *targets;
+            gint n_targets;
+
+            list = gtk_target_list_new(NULL, 0);
+            if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+                gtk_target_list_add_text_targets(list, 0);
+            }
+            targets = gtk_target_table_new_from_list(list, &n_targets);
+
+            gtk_clipboard_clear(gd->gtkcb[s]);
+            gd->cbowner[s] = true;
+            gtk_clipboard_set_with_data(gd->gtkcb[s],
+                                        targets, n_targets,
+                                        gd_clipboard_get_data,
+                                        gd_clipboard_clear,
+                                        gd);
+
+            gtk_target_table_free(targets, n_targets);
+            gtk_target_list_unref(list);
+        }
+        return;
+    }
+
+    if (self_update) {
+        return;
+    }
+
+    /*
+     * Clipboard got updated, with data probably.  No action here, we
+     * are waiting for updates in gd_clipboard_get_data().
+     */
+}
+
+static void gd_clipboard_request(QemuClipboardInfo *info,
+                                 QemuClipboardType type)
+{
+    GtkDisplayState *gd = container_of(info->owner, GtkDisplayState, cbpeer);
+    char *text;
+
+    switch (type) {
+    case QEMU_CLIPBOARD_TYPE_TEXT:
+        text = gtk_clipboard_wait_for_text(gd->gtkcb[info->selection]);
+        qemu_clipboard_set_data(&gd->cbpeer, info, type,
+                                strlen(text), text, true);
+        break;
+    default:
+        break;
+    }
+}
+
+static void gd_owner_change(GtkClipboard *clipboard,
+                            GdkEvent *event,
+                            gpointer data)
+{
+    GtkDisplayState *gd = data;
+    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
+    QemuClipboardInfo *info;
+
+    if (gd->cbowner[s]) {
+        /* ignore notifications about our own grabs */
+        return;
+    }
+
+
+    switch (event->owner_change.reason) {
+    case GDK_SETTING_ACTION_NEW:
+        info = qemu_clipboard_info_new(&gd->cbpeer, s);
+        if (gtk_clipboard_wait_is_text_available(clipboard)) {
+            info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+        }
+
+        qemu_clipboard_update(info);
+        qemu_clipboard_info_put(info);
+        break;
+    default:
+        break;
+    }
+}
+
+void gd_clipboard_init(GtkDisplayState *gd)
+{
+    gd->cbpeer.name = "gtk";
+    gd->cbpeer.update.notify = gd_clipboard_notify;
+    gd->cbpeer.request = gd_clipboard_request;
+    qemu_clipboard_peer_register(&gd->cbpeer);
+
+    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =
+        gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE));
+    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY] =
+        gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
+    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY] =
+        gtk_clipboard_get(gdk_atom_intern("SECONDARY", FALSE));
+
+    g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD],
+                     "owner-change", G_CALLBACK(gd_owner_change), gd);
+    g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY],
+                     "owner-change", G_CALLBACK(gd_owner_change), gd);
+    g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY],
+                     "owner-change", G_CALLBACK(gd_owner_change), gd);
+}
diff --git a/ui/gtk.c b/ui/gtk.c
index 7da288a25156..98046f577b9d 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -2267,6 +2267,7 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
         opts->u.gtk.grab_on_hover) {
         gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
     }
+    gd_clipboard_init(s);
 }
 
 static void early_gtk_display_init(DisplayOptions *opts)
diff --git a/ui/meson.build b/ui/meson.build
index 6c0c6ddd74eb..9ec0c03b5ad1 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -65,7 +65,7 @@ if gtk.found()
   softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
 
   gtk_ss = ss.source_set()
-  gtk_ss.add(gtk, vte, pixman, files('gtk.c'))
+  gtk_ss.add(gtk, vte, pixman, files('gtk.c', 'gtk-clipboard.c'))
   gtk_ss.add(when: x11, if_true: files('x_keymap.c'))
   gtk_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: files('gtk-gl-area.c'))
   gtk_ss.add(when: [x11, opengl, 'CONFIG_OPENGL'], if_true: files('gtk-egl.c'))
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 0/7] ui: add vdagent implementation and clipboard support.
  2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (6 preceding siblings ...)
  2021-03-18  9:16 ` [PATCH v2 7/7] ui/gtk: add clipboard support Gerd Hoffmann
@ 2021-03-18  9:30 ` no-reply
  7 siblings, 0 replies; 20+ messages in thread
From: no-reply @ 2021-03-18  9:30 UTC (permalink / raw)
  To: kraxel; +Cc: pbonzini, marcandre.lureau, qemu-devel, armbru, kraxel

Patchew URL: https://patchew.org/QEMU/20210318091647.3233178-1-kraxel@redhat.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Type: series
Message-id: 20210318091647.3233178-1-kraxel@redhat.com
Subject: [PATCH v2 0/7] ui: add vdagent implementation and clipboard support.

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 - [tag update]      patchew/20210317143325.2165821-1-eblake@redhat.com -> patchew/20210317143325.2165821-1-eblake@redhat.com
 - [tag update]      patchew/20210317194849.2262346-1-wainersm@redhat.com -> patchew/20210317194849.2262346-1-wainersm@redhat.com
 - [tag update]      patchew/20210318023801.18287-1-gshan@redhat.com -> patchew/20210318023801.18287-1-gshan@redhat.com
 * [new tag]         patchew/20210318091647.3233178-1-kraxel@redhat.com -> patchew/20210318091647.3233178-1-kraxel@redhat.com
Switched to a new branch 'test'
509f0ec ui/gtk: add clipboard support
6413531 ui/gtk: move struct GtkDisplayState to ui/gtk.h
be4f996 ui/vnc: clipboard support
15ab2b9 ui/vdagent: add clipboard support
c920715 ui/vdagent: add mouse support
d23759f ui/vdagent: core infrastructure
f59e025 ui: add clipboard infrastructure

=== OUTPUT BEGIN ===
1/7 Checking commit f59e025bdd71 (ui: add clipboard infrastructure)
Use of uninitialized value $acpi_testexpected in string eq at ./scripts/checkpatch.pl line 1529.
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#20: 
new file mode 100644

total: 0 errors, 1 warnings, 167 lines checked

Patch 1/7 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
2/7 Checking commit d23759fc5d14 (ui/vdagent: core infrastructure)
Use of uninitialized value $acpi_testexpected in string eq at ./scripts/checkpatch.pl line 1529.
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#91: 
new file mode 100644

ERROR: if this code is redundant consider removing it
#141: FILE: ui/vdagent.c:46:
+#if 0

WARNING: line over 80 characters
#143: FILE: ui/vdagent.c:48:
+    [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",

ERROR: if this code is redundant consider removing it
#164: FILE: ui/vdagent.c:69:
+#if 0

WARNING: line over 80 characters
#216: FILE: ui/vdagent.c:121:
+                                               sizeof(VDAgentAnnounceCapabilities) +

total: 2 errors, 3 warnings, 322 lines checked

Patch 2/7 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

3/7 Checking commit c9207151ac3c (ui/vdagent: add mouse support)
ERROR: if this code is redundant consider removing it
#137: FILE: ui/vdagent.c:179:
+#if 0

total: 1 errors, 0 warnings, 228 lines checked

Patch 3/7 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

4/7 Checking commit 15ab2b94f99a (ui/vdagent: add clipboard support)
ERROR: if this code is redundant consider removing it
#120: FILE: ui/vdagent.c:112:
+#if 0

ERROR: line over 90 characters
#159: FILE: ui/vdagent.c:286:
+                                               sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1));

WARNING: line over 80 characters
#406: FILE: ui/vdagent.c:652:
+    cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT);

total: 2 errors, 1 warnings, 359 lines checked

Patch 4/7 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

5/7 Checking commit be4f996a546c (ui/vnc: clipboard support)
Use of uninitialized value $acpi_testexpected in string eq at ./scripts/checkpatch.pl line 1529.
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#33: 
new file mode 100644

WARNING: line over 80 characters
#280: FILE: ui/vnc-clipboard.c:243:
+void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data)

WARNING: line over 80 characters
#289: FILE: ui/vnc-clipboard.c:252:
+            qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);

WARNING: line over 80 characters
#333: FILE: ui/vnc-clipboard.c:296:
+        qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);

ERROR: line over 90 characters
#420: FILE: ui/vnc.c:2430:
+            vnc_client_cut_text_ext(vs, abs(read_s32(data, 4)), read_u32(data, 8), data + 12);

WARNING: line over 80 characters
#499: FILE: ui/vnc.h:643:
+void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data);

total: 1 errors, 5 warnings, 450 lines checked

Patch 5/7 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

6/7 Checking commit 6413531353b7 (ui/gtk: move struct GtkDisplayState to ui/gtk.h)
7/7 Checking commit 509f0ec4d648 (ui/gtk: add clipboard support)
Use of uninitialized value $acpi_testexpected in string eq at ./scripts/checkpatch.pl line 1529.
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#52: 
new file mode 100644

WARNING: line over 80 characters
#135: FILE: ui/gtk-clipboard.c:79:
+    GtkDisplayState *gd = container_of(notifier, GtkDisplayState, cbpeer.update);

total: 0 errors, 2 warnings, 230 lines checked

Patch 7/7 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20210318091647.3233178-1-kraxel@redhat.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 3/7] ui/vdagent: add mouse support
  2021-03-18  9:16 ` [PATCH v2 3/7] ui/vdagent: add mouse support Gerd Hoffmann
@ 2021-03-18 10:24   ` Marc-André Lureau
  0 siblings, 0 replies; 20+ messages in thread
From: Marc-André Lureau @ 2021-03-18 10:24 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

[-- Attachment #1: Type: text/plain, Size: 8853 bytes --]

Hi

On Thu, Mar 18, 2021 at 1:22 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

> This patch adds support for mouse messages to the vdagent
> implementation.  This can be enabled/disabled using the new
> 'mouse' parameter for the vdagent chardev.  Default is on.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  chardev/char.c |   3 ++
>  ui/vdagent.c   | 141 +++++++++++++++++++++++++++++++++++++++++++++++++
>  qapi/char.json |   4 +-
>  3 files changed, 147 insertions(+), 1 deletion(-)
>
> diff --git a/chardev/char.c b/chardev/char.c
> index 97cafd68494a..fd4d86d0dd3f 100644
> --- a/chardev/char.c
> +++ b/chardev/char.c
> @@ -927,6 +927,9 @@ QemuOptsList qemu_chardev_opts = {
>          },{
>              .name = "logappend",
>              .type = QEMU_OPT_BOOL,
> +        },{
> +            .name = "mouse",
> +            .type = QEMU_OPT_BOOL,
>  #ifdef CONFIG_LINUX
>          },{
>              .name = "tight",
> diff --git a/ui/vdagent.c b/ui/vdagent.c
> index 146ddb8e31b4..61c12b02b573 100644
> --- a/ui/vdagent.c
> +++ b/ui/vdagent.c
> @@ -1,21 +1,38 @@
>  #include "qemu/osdep.h"
>  #include "include/qemu-common.h"
>  #include "chardev/char.h"
> +#include "hw/qdev-core.h"
> +#include "qemu/option.h"
> +#include "ui/console.h"
> +#include "ui/input.h"
>  #include "trace.h"
>
>  #include "qapi/qapi-types-char.h"
> +#include "qapi/qapi-types-ui.h"
>
>  #include "spice/vd_agent.h"
>
> +#define VDAGENT_MOUSE_DEFAULT true
> +
>  struct VDAgentChardev {
>      Chardev parent;
>
> +    /* config */
> +    bool mouse;
> +
>      /* guest vdagent */
>      uint32_t caps;
>      VDIChunkHeader chunk;
>      uint32_t chunksize;
>      uint8_t *msgbuf;
>      uint32_t msgsize;
> +
> +    /* mouse */
> +    DeviceState mouse_dev;
> +    uint32_t mouse_x;
> +    uint32_t mouse_y;
> +    uint32_t mouse_btn;
> +    QemuInputHandlerState *mouse_hs;
>  };
>  typedef struct VDAgentChardev VDAgentChardev;
>
> @@ -120,13 +137,105 @@ static void vdagent_send_caps(VDAgentChardev *vd)
>      g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
>
> sizeof(VDAgentAnnounceCapabilities) +
>                                                 sizeof(uint32_t));
> +    VDAgentAnnounceCapabilities *caps = (void *)msg->data;
>
>      msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
>      msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t);
> +    if (vd->mouse) {
> +        caps->caps[0] |= (1 << VD_AGENT_CAP_MOUSE_STATE);
> +    }
>
>      vdagent_send_msg(vd, msg);
>  }
>
> +/* ------------------------------------------------------------------ */
> +/* mouse events                                                       */
> +
> +static void vdagent_send_mouse(VDAgentChardev *vd)
> +{
> +    g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
> +                                               sizeof(VDAgentMouseState));
> +    VDAgentMouseState *mouse = (void *)msg->data;
> +
> +    msg->type = VD_AGENT_MOUSE_STATE;
> +    msg->size = sizeof(VDAgentMouseState);
> +
> +    mouse->x       = vd->mouse_x;
> +    mouse->y       = vd->mouse_y;
> +    mouse->buttons = vd->mouse_btn;
> +
>

Should there be a TODO for device_id support ? If not, it could use a
comment.


> +    vdagent_send_msg(vd, msg);
> +}
> +
> +static void vdagent_pointer_event(DeviceState *dev, QemuConsole *src,
> +                                  InputEvent *evt)
> +{
> +    static const int bmap[INPUT_BUTTON__MAX] = {
> +        [INPUT_BUTTON_LEFT]        = VD_AGENT_LBUTTON_MASK,
> +        [INPUT_BUTTON_RIGHT]       = VD_AGENT_RBUTTON_MASK,
> +        [INPUT_BUTTON_MIDDLE]      = VD_AGENT_MBUTTON_MASK,
> +        [INPUT_BUTTON_WHEEL_UP]    = VD_AGENT_UBUTTON_MASK,
> +        [INPUT_BUTTON_WHEEL_DOWN]  = VD_AGENT_DBUTTON_MASK,
> +#if 0
> +        [INPUT_BUTTON_SIDE]        = VD_AGENT_SBUTTON_MASK,
> +        [INPUT_BUTTON_EXTRA]       = VD_AGENT_EBUTTON_MASK,
> +#endif
> +    };
> +
> +    VDAgentChardev *vd = container_of(dev, struct VDAgentChardev,
> mouse_dev);
> +    InputMoveEvent *move;
> +    InputBtnEvent *btn;
> +    uint32_t xres, yres;
> +
> +    switch (evt->type) {
> +    case INPUT_EVENT_KIND_ABS:
> +        move = evt->u.abs.data;
> +        xres = qemu_console_get_width(src, 1024);
> +        yres = qemu_console_get_height(src, 768);
> +        if (move->axis == INPUT_AXIS_X) {
> +            vd->mouse_x = qemu_input_scale_axis(move->value,
> +                                                INPUT_EVENT_ABS_MIN,
> +                                                INPUT_EVENT_ABS_MAX,
> +                                                0, xres);
> +        } else if (move->axis == INPUT_AXIS_Y) {
> +            vd->mouse_y = qemu_input_scale_axis(move->value,
> +                                                INPUT_EVENT_ABS_MIN,
> +                                                INPUT_EVENT_ABS_MAX,
> +                                                0, yres);
> +        }
> +        break;
> +
> +    case INPUT_EVENT_KIND_BTN:
> +        btn = evt->u.btn.data;
> +        if (btn->down) {
> +            vd->mouse_btn |= bmap[btn->button];
> +        } else {
> +            vd->mouse_btn &= ~bmap[btn->button];
> +        }
> +        break;
> +
> +    default:
> +        /* keep gcc happy */
> +        break;
> +    }
> +}
> +
> +static void vdagent_pointer_sync(DeviceState *dev)
> +{
> +    VDAgentChardev *vd = container_of(dev, struct VDAgentChardev,
> mouse_dev);
> +
> +    if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)) {
> +        vdagent_send_mouse(vd);
> +    }
> +}
> +
> +static QemuInputHandler vdagent_mouse_handler = {
> +    .name  = "vdagent mouse",
> +    .mask  = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
> +    .event = vdagent_pointer_event,
> +    .sync  = vdagent_pointer_sync,
> +};
> +
>  /* ------------------------------------------------------------------ */
>  /* chardev backend                                                    */
>
> @@ -135,6 +244,19 @@ static void vdagent_chr_open(Chardev *chr,
>                               bool *be_opened,
>                               Error **errp)
>  {
> +    VDAgentChardev *vd = VDAGENT_CHARDEV(chr);
> +    ChardevVDAgent *cfg = backend->u.vdagent.data;
> +
> +    vd->mouse = VDAGENT_MOUSE_DEFAULT;
> +    if (cfg->has_mouse) {
> +        vd->mouse = cfg->mouse;
> +    }
> +
> +    if (vd->mouse) {
> +        vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev,
> +
>  &vdagent_mouse_handler);
> +    }
> +
>      *be_opened = true;
>  }
>
> @@ -158,6 +280,9 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd,
> VDAgentMessage *msg)
>      if (caps->request) {
>          vdagent_send_caps(vd);
>      }
> +    if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE) && vd->mouse_hs) {
> +        qemu_input_handler_activate(vd->mouse_hs);
> +    }
>  }
>
>  static void vdagent_chr_recv(VDAgentChardev *vd)
> @@ -247,18 +372,34 @@ static void vdagent_chr_set_fe_open(struct Chardev
> *chr, int fe_open)
>          /* reset state */
>          vdagent_reset_bufs(vd);
>          vd->caps = 0;
> +        if (vd->mouse_hs) {
> +            qemu_input_handler_deactivate(vd->mouse_hs);
> +        }
>          return;
>      }
>
>      trace_vdagent_open();
>  }
>
> +static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend,
> +                              Error **errp)
> +{
> +    ChardevVDAgent *cfg;
> +
> +    backend->type = CHARDEV_BACKEND_KIND_VDAGENT;
> +    cfg = backend->u.vdagent.data = g_new0(ChardevVDAgent, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevVDAgent_base(cfg));
> +    cfg->has_mouse = true;
> +    cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT);
> +}
> +
>  /* ------------------------------------------------------------------ */
>
>  static void vdagent_chr_class_init(ObjectClass *oc, void *data)
>  {
>      ChardevClass *cc = CHARDEV_CLASS(oc);
>
> +    cc->parse            = vdagent_chr_parse;
>      cc->open             = vdagent_chr_open;
>      cc->chr_write        = vdagent_chr_write;
>      cc->chr_set_fe_open  = vdagent_chr_set_fe_open;
> diff --git a/qapi/char.json b/qapi/char.json
> index 6e565ce42753..586ef2137368 100644
> --- a/qapi/char.json
> +++ b/qapi/char.json
> @@ -395,10 +395,12 @@
>  #
>  # Configuration info for vdagent.
>  #
> +# @mouse: enable/disable mouse, default is enabled.
> +#
>  # Since: 6.0
>  ##
>  { 'struct': 'ChardevVDAgent',
> -  'data': { },
> +  'data': { '*mouse'    : 'bool' },
>    'base': 'ChardevCommon' }
>
>  ##
> --
> 2.30.2
>
>
>

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 11332 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/7] ui/vdagent: core infrastructure
  2021-03-18  9:16 ` [PATCH v2 2/7] ui/vdagent: core infrastructure Gerd Hoffmann
@ 2021-03-18 10:32   ` Marc-André Lureau
  2021-03-22 10:27     ` Gerd Hoffmann
  0 siblings, 1 reply; 20+ messages in thread
From: Marc-André Lureau @ 2021-03-18 10:32 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

[-- Attachment #1: Type: text/plain, Size: 13053 bytes --]

On Thu, Mar 18, 2021 at 1:17 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

> The vdagent protocol allows the guest agent (spice-vdagent) and the
> spice client exchange messages to implement features which require
> guest cooperation, for example clipboard support.
>
> This is a qemu implementation of the spice client side.  This allows
> the spice guest agent talk to qemu directly when not using the spice
> protocol.
>
> usage: qemu \
>   -chardev vdagent,id=vdagent \
>   -device virtserialport,chardev=vdagent,name=com.redhat.spice.0
>
> This patch adds just the protocol basics: initial handshake and
> capability negotiation.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  ui/vdagent.c    | 279 ++++++++++++++++++++++++++++++++++++++++++++++++
>  qapi/char.json  |  13 +++
>  ui/meson.build  |   1 +
>  ui/trace-events |   8 ++
>  4 files changed, 301 insertions(+)
>  create mode 100644 ui/vdagent.c
>
> diff --git a/ui/vdagent.c b/ui/vdagent.c
> new file mode 100644
> index 000000000000..146ddb8e31b4
> --- /dev/null
> +++ b/ui/vdagent.c
> @@ -0,0 +1,279 @@
> +#include "qemu/osdep.h"
> +#include "include/qemu-common.h"
> +#include "chardev/char.h"
> +#include "trace.h"
> +
> +#include "qapi/qapi-types-char.h"
> +
> +#include "spice/vd_agent.h"
> +
> +struct VDAgentChardev {
> +    Chardev parent;
> +
> +    /* guest vdagent */
> +    uint32_t caps;
> +    VDIChunkHeader chunk;
> +    uint32_t chunksize;
> +    uint8_t *msgbuf;
> +    uint32_t msgsize;
> +};
> +typedef struct VDAgentChardev VDAgentChardev;
> +
> +#define TYPE_CHARDEV_VDAGENT "chardev-vdagent"
> +
> +DECLARE_INSTANCE_CHECKER(VDAgentChardev, VDAGENT_CHARDEV,
> +                         TYPE_CHARDEV_VDAGENT);
> +
> +/* ------------------------------------------------------------------ */
> +/* names, for debug logging                                           */
> +
> +static const char *cap_name[] = {
> +    [VD_AGENT_CAP_MOUSE_STATE]                    = "mouse-state",
> +    [VD_AGENT_CAP_MONITORS_CONFIG]                = "monitors-config",
> +    [VD_AGENT_CAP_REPLY]                          = "reply",
> +    [VD_AGENT_CAP_CLIPBOARD]                      = "clipboard",
> +    [VD_AGENT_CAP_DISPLAY_CONFIG]                 = "display-config",
> +    [VD_AGENT_CAP_CLIPBOARD_BY_DEMAND]            = "clipboard-by-demand",
> +    [VD_AGENT_CAP_CLIPBOARD_SELECTION]            = "clipboard-selection",
> +    [VD_AGENT_CAP_SPARSE_MONITORS_CONFIG]         =
> "sparse-monitors-config",
> +    [VD_AGENT_CAP_GUEST_LINEEND_LF]               = "guest-lineend-lf",
> +    [VD_AGENT_CAP_GUEST_LINEEND_CRLF]             = "guest-lineend-crlf",
> +    [VD_AGENT_CAP_MAX_CLIPBOARD]                  = "max-clipboard",
> +    [VD_AGENT_CAP_AUDIO_VOLUME_SYNC]              = "audio-volume-sync",
> +    [VD_AGENT_CAP_MONITORS_CONFIG_POSITION]       =
> "monitors-config-position",
> +    [VD_AGENT_CAP_FILE_XFER_DISABLED]             = "file-xfer-disabled",
> +    [VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS]      =
> "file-xfer-detailed-errors",
> +#if 0
> +    [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO]           =
> "graphics-device-info",
> +    [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] =
> "clipboard-no-release-on-regrab",
> +    [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL]          =
> "clipboard-grab-serial",
> +#endif
> +};
> +
> +static const char *msg_name[] = {
> +    [VD_AGENT_MOUSE_STATE]           = "mouse-state",
> +    [VD_AGENT_MONITORS_CONFIG]       = "monitors-config",
> +    [VD_AGENT_REPLY]                 = "reply",
> +    [VD_AGENT_CLIPBOARD]             = "clipboard",
> +    [VD_AGENT_DISPLAY_CONFIG]        = "display-config",
> +    [VD_AGENT_ANNOUNCE_CAPABILITIES] = "announce-capabilities",
> +    [VD_AGENT_CLIPBOARD_GRAB]        = "clipboard-grab",
> +    [VD_AGENT_CLIPBOARD_REQUEST]     = "clipboard-request",
> +    [VD_AGENT_CLIPBOARD_RELEASE]     = "clipboard-release",
> +    [VD_AGENT_FILE_XFER_START]       = "file-xfer-start",
> +    [VD_AGENT_FILE_XFER_STATUS]      = "file-xfer-status",
> +    [VD_AGENT_FILE_XFER_DATA]        = "file-xfer-data",
> +    [VD_AGENT_CLIENT_DISCONNECTED]   = "client-disconnected",
> +    [VD_AGENT_MAX_CLIPBOARD]         = "max-clipboard",
> +    [VD_AGENT_AUDIO_VOLUME_SYNC]     = "audio-volume-sync",
> +#if 0
> +    [VD_AGENT_GRAPHICS_DEVICE_INFO]  = "graphics-device-info",
> +#endif
> +};
> +
> +#define GET_NAME(_m, _v) \
> +    (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???")
> +
> +/* ------------------------------------------------------------------ */
> +/* send messages                                                      */
> +
> +static void vdagent_send_buf(VDAgentChardev *vd, void *ptr, uint32_t
> msgsize)
> +{
> +    uint8_t *msgbuf = ptr;
> +    uint32_t len, pos = 0;
> +
> +    while (pos < msgsize) {
> +        len = qemu_chr_be_can_write(CHARDEV(vd));
> +        if (len > msgsize - pos) {
> +            len = msgsize - pos;
> +        }
> +        qemu_chr_be_write(CHARDEV(vd), msgbuf + pos, len);
> +        pos += len;
> +    }
> +}
> +
> +static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg)
> +{
> +    uint8_t *msgbuf = (void *)msg;
> +    uint32_t msgsize = sizeof(VDAgentMessage) + msg->size;
> +    uint32_t msgoff = 0;
> +    VDIChunkHeader chunk;
> +
> +    trace_vdagent_send(GET_NAME(msg_name, msg->type));
> +
> +    msg->protocol = VD_AGENT_PROTOCOL;
> +
> +    while (msgoff < msgsize) {
> +        chunk.port = VDP_CLIENT_PORT;
> +        chunk.size = msgsize - msgoff;
> +        if (chunk.size > 1024) {
> +            chunk.size = 1024;
> +        }
> +        vdagent_send_buf(vd, &chunk, sizeof(chunk));
> +        vdagent_send_buf(vd, msgbuf + msgoff, chunk.size);
> +        msgoff += chunk.size;
> +    }
> +}
> +
> +static void vdagent_send_caps(VDAgentChardev *vd)
> +{
> +    g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
> +
>  sizeof(VDAgentAnnounceCapabilities) +
> +                                               sizeof(uint32_t));
> +
> +    msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
> +    msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t);
> +
> +    vdagent_send_msg(vd, msg);
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* chardev backend                                                    */
> +
> +static void vdagent_chr_open(Chardev *chr,
> +                             ChardevBackend *backend,
> +                             bool *be_opened,
> +                             Error **errp)
> +{
> +    *be_opened = true;
> +}
> +
> +static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
> +{
> +    VDAgentAnnounceCapabilities *caps = (void *)msg->data;
> +    int i;
> +
> +    if (msg->size < (sizeof(VDAgentAnnounceCapabilities) +
> +                     sizeof(uint32_t))) {
> +        return;
> +    }
> +
> +    for (i = 0; i < ARRAY_SIZE(cap_name); i++) {
> +        if (caps->caps[0] & (1 << i)) {
> +            trace_vdagent_peer_cap(GET_NAME(cap_name, i));
> +        }
> +    }
> +
> +    vd->caps = caps->caps[0];
> +    if (caps->request) {
> +        vdagent_send_caps(vd);
> +    }
> +}
> +
> +static void vdagent_chr_recv(VDAgentChardev *vd)
> +{
> +    VDAgentMessage *msg = (void *)vd->msgbuf;
> +
> +    if (vd->msgsize < sizeof(*msg)) {
> +        fprintf(stderr, "%s: message too small: %d < %zd\n", __func__,
> +                vd->msgsize, sizeof(*msg));
> +        return;
> +    }
> +    if (vd->msgsize != msg->size + sizeof(*msg)) {
> +        /* FIXME: handle parse messages splitted into multiple chunks */
> +        fprintf(stderr, "%s: size mismatch: chunk %d, msg %d (+%zd)\n",
> +                __func__, vd->msgsize, msg->size, sizeof(*msg));
>

Not fixing? You handle chunking on sending but not on receiving?

+        return;
> +    }
> +
> +    trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type));
> +
> +    switch (msg->type) {
> +    case VD_AGENT_ANNOUNCE_CAPABILITIES:
> +        vdagent_chr_recv_caps(vd, msg);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static void vdagent_reset_bufs(VDAgentChardev *vd)
> +{
> +    memset(&vd->chunk, 0, sizeof(vd->chunk));
> +    vd->chunksize = 0;
> +    g_free(vd->msgbuf);
> +    vd->msgbuf = NULL;
> +    vd->msgsize = 0;
> +}
> +
> +static int vdagent_chr_write(Chardev *chr, const uint8_t *buf, int len)
> +{
> +    VDAgentChardev *vd = VDAGENT_CHARDEV(chr);
> +    uint32_t copy, ret = len;
> +
> +    while (len) {
> +        if (vd->chunksize < sizeof(vd->chunk)) {
> +            copy = sizeof(vd->chunk) - vd->chunksize;
> +            if (copy > len) {
> +                copy = len;
> +            }
> +            memcpy((void *)(&vd->chunk) + vd->chunksize, buf, copy);
> +            vd->chunksize += copy;
> +            buf += copy;
> +            len -= copy;
> +            if (vd->chunksize < sizeof(vd->chunk)) {
> +                break;
> +            }
> +
> +            assert(vd->msgbuf == NULL);
> +            vd->msgbuf = g_malloc0(vd->chunk.size);
> +        }
> +
> +        copy = vd->chunk.size - vd->msgsize;
> +        if (copy > len) {
> +            copy = len;
> +        }
> +        memcpy(vd->msgbuf + vd->msgsize, buf, copy);
> +        vd->msgsize += copy;
> +        buf += copy;
> +        len -= copy;
> +
> +        if (vd->msgsize == vd->chunk.size) {
> +            trace_vdagent_recv_chunk(vd->chunk.size);
> +            vdagent_chr_recv(vd);
> +            vdagent_reset_bufs(vd);
> +        }
> +    }
> +
> +    return ret;
> +}
> +
> +static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
> +{
> +    VDAgentChardev *vd = VDAGENT_CHARDEV(chr);
> +
> +    if (!fe_open) {
> +        trace_vdagent_close();
> +        /* reset state */
> +        vdagent_reset_bufs(vd);
> +        vd->caps = 0;
> +        return;
> +    }
> +
> +    trace_vdagent_open();
> +}
> +
> +/* ------------------------------------------------------------------ */
> +
> +static void vdagent_chr_class_init(ObjectClass *oc, void *data)
> +{
> +    ChardevClass *cc = CHARDEV_CLASS(oc);
> +
> +    cc->open             = vdagent_chr_open;
> +    cc->chr_write        = vdagent_chr_write;
> +    cc->chr_set_fe_open  = vdagent_chr_set_fe_open;
> +}
> +
> +static const TypeInfo vdagent_chr_type_info = {
> +    .name = TYPE_CHARDEV_VDAGENT,
> +    .parent = TYPE_CHARDEV,
> +    .instance_size = sizeof(VDAgentChardev),
> +    .class_init = vdagent_chr_class_init,
> +};
> +
> +static void register_types(void)
> +{
> +    type_register_static(&vdagent_chr_type_info);
> +}
> +
> +type_init(register_types);
> diff --git a/qapi/char.json b/qapi/char.json
> index 6413970fa73b..6e565ce42753 100644
> --- a/qapi/char.json
> +++ b/qapi/char.json
> @@ -390,6 +390,17 @@
>    'data': { '*size': 'int' },
>    'base': 'ChardevCommon' }
>
> +##
> +# @ChardevVDAgent:
> +#
> +# Configuration info for vdagent.
> +#
> +# Since: 6.0
> +##
> +{ 'struct': 'ChardevVDAgent',
> +  'data': { },
> +  'base': 'ChardevCommon' }
> +
>  ##
>  # @ChardevBackend:
>  #
> @@ -417,6 +428,8 @@
>                            'if': 'defined(CONFIG_SPICE)' },
>              'spiceport': { 'type': 'ChardevSpicePort',
>                             'if': 'defined(CONFIG_SPICE)' },
> +            'vdagent': { 'type': 'ChardevVDAgent',
> +                         'if': 'defined(CONFIG_SPICE)' },
>              'vc': 'ChardevVC',
>              'ringbuf': 'ChardevRingbuf',
>              # next one is just for compatibility
> diff --git a/ui/meson.build b/ui/meson.build
> index fc4fb75c2869..3d382b3b9019 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -14,6 +14,7 @@ softmmu_ss.add(files(
>    'qemu-pixman.c',
>  ))
>  softmmu_ss.add([spice_headers, files('spice-module.c')])
> +softmmu_ss.add(when: spice_headers, if_true: files('vdagent.c'))
>
>  softmmu_ss.add(when: 'CONFIG_LINUX', if_true: files('input-linux.c'))
>  softmmu_ss.add(when: cocoa, if_true: files('cocoa.m'))
> diff --git a/ui/trace-events b/ui/trace-events
> index 5d1da6f23668..c286065f1a94 100644
> --- a/ui/trace-events
> +++ b/ui/trace-events
> @@ -124,3 +124,11 @@ xkeymap_extension(const char *name) "extension '%s'"
>  xkeymap_vendor(const char *name) "vendor '%s'"
>  xkeymap_keycodes(const char *name) "keycodes '%s'"
>  xkeymap_keymap(const char *name) "keymap '%s'"
> +
> +# vdagent.c
> +vdagent_open(void) ""
> +vdagent_close(void) ""
> +vdagent_send(const char *name) "msg %s"
> +vdagent_recv_chunk(uint32_t size) "size %d"
> +vdagent_recv_msg(const char *name) "msg %s"
> +vdagent_peer_cap(const char *name) "cap %s"
> --
> 2.30.2
>
>
>

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 16477 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 7/7] ui/gtk: add clipboard support
  2021-03-18  9:16 ` [PATCH v2 7/7] ui/gtk: add clipboard support Gerd Hoffmann
@ 2021-03-18 10:55   ` Marc-André Lureau
  2021-03-24 10:16     ` Gerd Hoffmann
  0 siblings, 1 reply; 20+ messages in thread
From: Marc-André Lureau @ 2021-03-18 10:55 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

[-- Attachment #1: Type: text/plain, Size: 10455 bytes --]

Hi

On Thu, Mar 18, 2021 at 1:25 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

> This patch adds clipboard support to the qemu gtk ui.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  include/ui/gtk.h   |  10 +++
>  ui/gtk-clipboard.c | 189 +++++++++++++++++++++++++++++++++++++++++++++
>  ui/gtk.c           |   1 +
>  ui/meson.build     |   2 +-
>  4 files changed, 201 insertions(+), 1 deletion(-)
>  create mode 100644 ui/gtk-clipboard.c
>
> diff --git a/include/ui/gtk.h b/include/ui/gtk.h
> index 6e751794043f..9516670ebc87 100644
> --- a/include/ui/gtk.h
> +++ b/include/ui/gtk.h
> @@ -18,6 +18,7 @@
>  #include <gdk/gdkwayland.h>
>  #endif
>
> +#include "ui/clipboard.h"
>  #include "ui/console.h"
>  #include "ui/kbd-state.h"
>  #if defined(CONFIG_OPENGL)
> @@ -137,6 +138,12 @@ struct GtkDisplayState {
>
>      bool external_pause_update;
>
> +    QemuClipboardPeer cbpeer;
> +    QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
> +    uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
> +    GtkClipboard *gtkcb[QEMU_CLIPBOARD_SELECTION__COUNT];
> +    bool cbowner[QEMU_CLIPBOARD_SELECTION__COUNT];
> +
>      DisplayOptions *opts;
>  };
>
> @@ -207,4 +214,7 @@ void gtk_gl_area_init(void);
>  int gd_gl_area_make_current(DisplayChangeListener *dcl,
>                              QEMUGLContext ctx);
>
> +/* gtk-clipboard.c */
> +void gd_clipboard_init(GtkDisplayState *gd);
> +
>  #endif /* UI_GTK_H */
> diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
> new file mode 100644
> index 000000000000..3585e27cab78
> --- /dev/null
> +++ b/ui/gtk-clipboard.c
> @@ -0,0 +1,189 @@
> +/*
> + * GTK UI -- clipboard support
> + *
> + * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program 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
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu-common.h"
> +#include "qemu/main-loop.h"
> +
> +#include "ui/gtk.h"
> +
> +static QemuClipboardSelection gd_find_selection(GtkDisplayState *gd,
> +                                                GtkClipboard *clipboard)
> +{
> +    QemuClipboardSelection s;
> +
> +    for (s = 0; s < QEMU_CLIPBOARD_SELECTION__COUNT; s++) {
> +        if (gd->gtkcb[s] == clipboard) {
> +            return s;
> +        }
> +    }
> +    return QEMU_CLIPBOARD_SELECTION_CLIPBOARD;
> +}
> +
> +static void gd_clipboard_get_data(GtkClipboard     *clipboard,
> +                                  GtkSelectionData *selection_data,
> +                                  guint             selection_info,
> +                                  gpointer          data)
> +{
> +    GtkDisplayState *gd = data;
> +    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
> +    QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
> +    QemuClipboardInfo *info = qemu_clipboard_info_get(gd->cbinfo[s]);
> +
> +    qemu_clipboard_request(info, type);
> +    while (info == gd->cbinfo[s] &&
> +           info->types[type].available &&
> +           info->types[type].data == NULL) {
> +        main_loop_wait(false);
>

Added to the list of reasons not to want running the clipboard  (or shall I
say any UI code) inside qemu.

(gtk4 is supposed to have improved that, fwiw)

+    }
> +
> +    if (info == gd->cbinfo[s] && gd->cbowner[s]) {
> +        gtk_selection_data_set_text(selection_data,
> +                                    info->types[type].data,
> +                                    info->types[type].size);
> +    } else {
> +        /* clipboard owner changed while waiting for the data */
> +    }
> +
> +    qemu_clipboard_info_put(info);
> +}
> +
> +static void gd_clipboard_clear(GtkClipboard *clipboard,
> +                               gpointer data)
> +{
> +    GtkDisplayState *gd = data;
> +    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
> +
> +    gd->cbowner[s] = false;
> +}
> +
> +static void gd_clipboard_notify(Notifier *notifier, void *data)
> +{
> +    GtkDisplayState *gd = container_of(notifier, GtkDisplayState,
> cbpeer.update);
> +    QemuClipboardInfo *info = data;
> +    QemuClipboardSelection s = info->selection;
> +    bool self_update = info->owner == &gd->cbpeer;
> +
> +    if (info != gd->cbinfo[s]) {
> +        qemu_clipboard_info_put(gd->cbinfo[s]);
> +        gd->cbinfo[s] = qemu_clipboard_info_get(info);
> +        gd->cbpending[s] = 0;
> +        if (!self_update) {
> +            GtkTargetList *list;
> +            GtkTargetEntry *targets;
> +            gint n_targets;
> +
> +            list = gtk_target_list_new(NULL, 0);
> +            if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
> +                gtk_target_list_add_text_targets(list, 0);
> +            }
> +            targets = gtk_target_table_new_from_list(list, &n_targets);
> +
> +            gtk_clipboard_clear(gd->gtkcb[s]);
> +            gd->cbowner[s] = true;
> +            gtk_clipboard_set_with_data(gd->gtkcb[s],
> +                                        targets, n_targets,
> +                                        gd_clipboard_get_data,
> +                                        gd_clipboard_clear,
> +                                        gd);
> +
> +            gtk_target_table_free(targets, n_targets);
> +            gtk_target_list_unref(list);
> +        }
> +        return;
> +    }
> +
> +    if (self_update) {
> +        return;
> +    }
> +
> +    /*
> +     * Clipboard got updated, with data probably.  No action here, we
> +     * are waiting for updates in gd_clipboard_get_data().
> +     */
> +}
> +
> +static void gd_clipboard_request(QemuClipboardInfo *info,
> +                                 QemuClipboardType type)
> +{
> +    GtkDisplayState *gd = container_of(info->owner, GtkDisplayState,
> cbpeer);
> +    char *text;
> +
> +    switch (type) {
> +    case QEMU_CLIPBOARD_TYPE_TEXT:
> +        text = gtk_clipboard_wait_for_text(gd->gtkcb[info->selection]);
> +        qemu_clipboard_set_data(&gd->cbpeer, info, type,
> +                                strlen(text), text, true);
>

From v1:
text might be NULL if it failed.

And you must free it.

+        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static void gd_owner_change(GtkClipboard *clipboard,
> +                            GdkEvent *event,
> +                            gpointer data)
> +{
> +    GtkDisplayState *gd = data;
> +    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
> +    QemuClipboardInfo *info;
> +
> +    if (gd->cbowner[s]) {
> +        /* ignore notifications about our own grabs */
> +        return;
> +    }
> +
> +
> +    switch (event->owner_change.reason) {
> +    case GDK_SETTING_ACTION_NEW:
> +        info = qemu_clipboard_info_new(&gd->cbpeer, s);
> +        if (gtk_clipboard_wait_is_text_available(clipboard)) {
> +            info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
> +        }
>

Same comment as v1:
So after gtk_clipboard_set_text() the client side is actually taking
the ownership away from the guest clipboard I presume. That might have some
weird interaction issues. Hopefully the other side isn't playing the same
game...


If we don't address it now, I think it deserves a warning comment, possibly
a FIXME.

+
> +        qemu_clipboard_update(info);
> +        qemu_clipboard_info_put(info);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +void gd_clipboard_init(GtkDisplayState *gd)
> +{
> +    gd->cbpeer.name = "gtk";
> +    gd->cbpeer.update.notify = gd_clipboard_notify;
> +    gd->cbpeer.request = gd_clipboard_request;
> +    qemu_clipboard_peer_register(&gd->cbpeer);
> +
> +    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =
> +        gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE));
>

Why not use GDK_SELECTION_* ?

+    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY] =
> +        gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
> +    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY] =
> +        gtk_clipboard_get(gdk_atom_intern("SECONDARY", FALSE));
> +
> +    g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD],
> +                     "owner-change", G_CALLBACK(gd_owner_change), gd);
> +    g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY],
> +                     "owner-change", G_CALLBACK(gd_owner_change), gd);
> +    g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY],
> +                     "owner-change", G_CALLBACK(gd_owner_change), gd);
> +}
> diff --git a/ui/gtk.c b/ui/gtk.c
> index 7da288a25156..98046f577b9d 100644
> --- a/ui/gtk.c
> +++ b/ui/gtk.c
> @@ -2267,6 +2267,7 @@ static void gtk_display_init(DisplayState *ds,
> DisplayOptions *opts)
>          opts->u.gtk.grab_on_hover) {
>          gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
>      }
> +    gd_clipboard_init(s);
>  }
>
>  static void early_gtk_display_init(DisplayOptions *opts)
> diff --git a/ui/meson.build b/ui/meson.build
> index 6c0c6ddd74eb..9ec0c03b5ad1 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -65,7 +65,7 @@ if gtk.found()
>    softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
>
>    gtk_ss = ss.source_set()
> -  gtk_ss.add(gtk, vte, pixman, files('gtk.c'))
> +  gtk_ss.add(gtk, vte, pixman, files('gtk.c', 'gtk-clipboard.c'))
>    gtk_ss.add(when: x11, if_true: files('x_keymap.c'))
>    gtk_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true:
> files('gtk-gl-area.c'))
>    gtk_ss.add(when: [x11, opengl, 'CONFIG_OPENGL'], if_true:
> files('gtk-egl.c'))
> --
> 2.30.2
>
>
>

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 13593 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/7] ui/vdagent: core infrastructure
  2021-03-18 10:32   ` Marc-André Lureau
@ 2021-03-22 10:27     ` Gerd Hoffmann
  2021-03-24  9:46       ` Gerd Hoffmann
  0 siblings, 1 reply; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-22 10:27 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

  Hi,

> > +    if (vd->msgsize != msg->size + sizeof(*msg)) {
> > +        /* FIXME: handle parse messages splitted into multiple chunks */
> > +        fprintf(stderr, "%s: size mismatch: chunk %d, msg %d (+%zd)\n",
> > +                __func__, vd->msgsize, msg->size, sizeof(*msg));
> >
> 
> Not fixing? You handle chunking on sending but not on receiving?

linux vdagent doesn't do chunking on send, so no need (and also no
testcase) so far.

Didn't try windows guests (yet), but that is next on my clipboard
todo list.

take care,
  Gerd



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/7] ui/vdagent: core infrastructure
  2021-03-22 10:27     ` Gerd Hoffmann
@ 2021-03-24  9:46       ` Gerd Hoffmann
  2021-03-24 10:31         ` Marc-André Lureau
  0 siblings, 1 reply; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-24  9:46 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

On Mon, Mar 22, 2021 at 11:27:17AM +0100, Gerd Hoffmann wrote:
>   Hi,
> 
> > > +    if (vd->msgsize != msg->size + sizeof(*msg)) {
> > > +        /* FIXME: handle parse messages splitted into multiple chunks */
> > > +        fprintf(stderr, "%s: size mismatch: chunk %d, msg %d (+%zd)\n",
> > > +                __func__, vd->msgsize, msg->size, sizeof(*msg));
> > >
> > 
> > Not fixing? You handle chunking on sending but not on receiving?
> 
> linux vdagent doesn't do chunking on send, so no need (and also no
> testcase) so far.
> 
> Didn't try windows guests (yet), but that is next on my clipboard
> todo list.

Hmm, windows guest agent doesn't has VD_AGENT_CAP_CLIPBOARD_SELECTION,
so I have to deal with that.

Windows guests do actually send large messages in chunks, so I have
something to test with, good.

What are VD_AGENT_CAP_GUEST_LINEEND_LF + VD_AGENT_CAP_GUEST_LINEEND_CRLF
are good for btw?  Are linefeeds converted automatically between dos and
unix conventions?  If so, who is supposed to handle that?

take care,
  Gerd



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 7/7] ui/gtk: add clipboard support
  2021-03-18 10:55   ` Marc-André Lureau
@ 2021-03-24 10:16     ` Gerd Hoffmann
  2021-03-24 10:26       ` Marc-André Lureau
  0 siblings, 1 reply; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-24 10:16 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

  Hi,

> > +    if (gd->cbowner[s]) {
> > +        /* ignore notifications about our own grabs */
> > +        return;
> > +    }
> > +
> > +
> > +    switch (event->owner_change.reason) {
> > +    case GDK_SETTING_ACTION_NEW:
> > +        info = qemu_clipboard_info_new(&gd->cbpeer, s);
> > +        if (gtk_clipboard_wait_is_text_available(clipboard)) {
> > +            info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
> > +        }
> >
> 
> Same comment as v1:
> So after gtk_clipboard_set_text() the client side is actually taking
> the ownership away from the guest clipboard I presume. That might have some
> weird interaction issues. Hopefully the other side isn't playing the same
> game...

The cbowner check above should avoid that ...

> > +    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =
> > +        gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE));
> 
> Why not use GDK_SELECTION_* ?

So I don't have to worry about converting GDK_SELECTION_* to
QEMU_CLIPBOARD_SELECTION_* ?

take care,
  Gerd



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 7/7] ui/gtk: add clipboard support
  2021-03-24 10:16     ` Gerd Hoffmann
@ 2021-03-24 10:26       ` Marc-André Lureau
  2021-03-24 12:57         ` Gerd Hoffmann
  0 siblings, 1 reply; 20+ messages in thread
From: Marc-André Lureau @ 2021-03-24 10:26 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

[-- Attachment #1: Type: text/plain, Size: 1437 bytes --]

Hi

On Wed, Mar 24, 2021 at 2:16 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

>   Hi,
>
> > > +    if (gd->cbowner[s]) {
> > > +        /* ignore notifications about our own grabs */
> > > +        return;
> > > +    }
> > > +
> > > +
> > > +    switch (event->owner_change.reason) {
> > > +    case GDK_SETTING_ACTION_NEW:
> > > +        info = qemu_clipboard_info_new(&gd->cbpeer, s);
> > > +        if (gtk_clipboard_wait_is_text_available(clipboard)) {
> > > +            info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
> > > +        }
> > >
> >
> > Same comment as v1:
> > So after gtk_clipboard_set_text() the client side is actually taking
> > the ownership away from the guest clipboard I presume. That might have
> some
> > weird interaction issues. Hopefully the other side isn't playing the same
> > game...
>
> The cbowner check above should avoid that ...
>

I fail to see how that works, imagine the other end is the same code (qemu
in the guest), it will take clipboard ownership and it is in a endless
loop, isn't it?

>
> > > +    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =
> > > +        gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE));
> >
> > Why not use GDK_SELECTION_* ?
>
> So I don't have to worry about converting GDK_SELECTION_* to
> QEMU_CLIPBOARD_SELECTION_* ?
>
>
GDK_SELECTION* is gdk_atom_intern(*, FALSE)) afaik

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 2215 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/7] ui/vdagent: core infrastructure
  2021-03-24  9:46       ` Gerd Hoffmann
@ 2021-03-24 10:31         ` Marc-André Lureau
  0 siblings, 0 replies; 20+ messages in thread
From: Marc-André Lureau @ 2021-03-24 10:31 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

[-- Attachment #1: Type: text/plain, Size: 1369 bytes --]

On Wed, Mar 24, 2021 at 1:47 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

> On Mon, Mar 22, 2021 at 11:27:17AM +0100, Gerd Hoffmann wrote:
> >   Hi,
> >
> > > > +    if (vd->msgsize != msg->size + sizeof(*msg)) {
> > > > +        /* FIXME: handle parse messages splitted into multiple
> chunks */
> > > > +        fprintf(stderr, "%s: size mismatch: chunk %d, msg %d
> (+%zd)\n",
> > > > +                __func__, vd->msgsize, msg->size, sizeof(*msg));
> > > >
> > >
> > > Not fixing? You handle chunking on sending but not on receiving?
> >
> > linux vdagent doesn't do chunking on send, so no need (and also no
> > testcase) so far.
> >
> > Didn't try windows guests (yet), but that is next on my clipboard
> > todo list.
>
> Hmm, windows guest agent doesn't has VD_AGENT_CAP_CLIPBOARD_SELECTION,
> so I have to deal with that.
>

ok


> Windows guests do actually send large messages in chunks, so I have
> something to test with, good.
>
> What are VD_AGENT_CAP_GUEST_LINEEND_LF + VD_AGENT_CAP_GUEST_LINEEND_CRLF
> are good for btw?  Are linefeeds converted automatically between dos and
> unix conventions?  If so, who is supposed to handle that?
>
>
I think that means the guest agent uses a text clipboard with CRLF ending,
both ways. spice-gtk has dos2unix & unix2dos helpers to deal with that.

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 2176 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 7/7] ui/gtk: add clipboard support
  2021-03-24 10:26       ` Marc-André Lureau
@ 2021-03-24 12:57         ` Gerd Hoffmann
  2021-03-24 13:26           ` Marc-André Lureau
  0 siblings, 1 reply; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-24 12:57 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

  Hi,

> I fail to see how that works, imagine the other end is the same code (qemu
> in the guest), it will take clipboard ownership and it is in a endless
> loop, isn't it?

Notifications on guest-triggered clipboard updates will not be sent back
to the guest, exactly to avoid that kind of loop.  See self_update
checks in vdagent_clipboard_notify().  gtk and vnc notify callbacks have
simliar checks for simliar reasons ;)

take care,
  Gerd



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 7/7] ui/gtk: add clipboard support
  2021-03-24 12:57         ` Gerd Hoffmann
@ 2021-03-24 13:26           ` Marc-André Lureau
  2021-03-24 14:47             ` Gerd Hoffmann
  0 siblings, 1 reply; 20+ messages in thread
From: Marc-André Lureau @ 2021-03-24 13:26 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

[-- Attachment #1: Type: text/plain, Size: 927 bytes --]

Hi

On Wed, Mar 24, 2021 at 4:57 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

>   Hi,
>
> > I fail to see how that works, imagine the other end is the same code
> (qemu
> > in the guest), it will take clipboard ownership and it is in a endless
> > loop, isn't it?
>
> Notifications on guest-triggered clipboard updates will not be sent back
> to the guest, exactly to avoid that kind of loop.  See self_update
> checks in vdagent_clipboard_notify().  gtk and vnc notify callbacks have
> simliar checks for simliar reasons ;)
>
>
That means the other side may have wrong clipboard expectations. It may ask
for what was previously an image and that will fails. This is not a big
issue per se if error returns are handled correctly all the way. It may
also have an impact on some UI aspects (since some actions shold become
unactive when clipboard has some content or another etc)

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 1333 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 7/7] ui/gtk: add clipboard support
  2021-03-24 13:26           ` Marc-André Lureau
@ 2021-03-24 14:47             ` Gerd Hoffmann
  0 siblings, 0 replies; 20+ messages in thread
From: Gerd Hoffmann @ 2021-03-24 14:47 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

On Wed, Mar 24, 2021 at 05:26:24PM +0400, Marc-André Lureau wrote:
> Hi
> 
> On Wed, Mar 24, 2021 at 4:57 PM Gerd Hoffmann <kraxel@redhat.com> wrote:
> 
> >   Hi,
> >
> > > I fail to see how that works, imagine the other end is the same code
> > (qemu
> > > in the guest), it will take clipboard ownership and it is in a endless
> > > loop, isn't it?
> >
> > Notifications on guest-triggered clipboard updates will not be sent back
> > to the guest, exactly to avoid that kind of loop.  See self_update
> > checks in vdagent_clipboard_notify().  gtk and vnc notify callbacks have
> > simliar checks for simliar reasons ;)

> That means the other side may have wrong clipboard expectations. It may ask
> for what was previously an image and that will fails.

Should not happen.  qemu keeps track of the clipboard source
(vnc/gtk/vdgent) and uses that to filter notifications.

So when the guest (i.e. vdagent source inside qemu) grabs the clipboard
the updates will not be propagated back to the guest to avoid loops, but
when some host applications updates the clipboard afterwards the changes
are sent to the guest because they come from gtk (or vnc).

Latest bits are here:
	https://git.kraxel.org/cgit/qemu/log/?h=sirius/vnc-clipboard

Feel free to try break it ;)

take care,
  Gerd



^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2021-03-24 14:57 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-18  9:16 [PATCH v2 0/7] ui: add vdagent implementation and clipboard support Gerd Hoffmann
2021-03-18  9:16 ` [PATCH v2 1/7] ui: add clipboard infrastructure Gerd Hoffmann
2021-03-18  9:16 ` [PATCH v2 2/7] ui/vdagent: core infrastructure Gerd Hoffmann
2021-03-18 10:32   ` Marc-André Lureau
2021-03-22 10:27     ` Gerd Hoffmann
2021-03-24  9:46       ` Gerd Hoffmann
2021-03-24 10:31         ` Marc-André Lureau
2021-03-18  9:16 ` [PATCH v2 3/7] ui/vdagent: add mouse support Gerd Hoffmann
2021-03-18 10:24   ` Marc-André Lureau
2021-03-18  9:16 ` [PATCH v2 4/7] ui/vdagent: add clipboard support Gerd Hoffmann
2021-03-18  9:16 ` [PATCH v2 5/7] ui/vnc: " Gerd Hoffmann
2021-03-18  9:16 ` [PATCH v2 6/7] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
2021-03-18  9:16 ` [PATCH v2 7/7] ui/gtk: add clipboard support Gerd Hoffmann
2021-03-18 10:55   ` Marc-André Lureau
2021-03-24 10:16     ` Gerd Hoffmann
2021-03-24 10:26       ` Marc-André Lureau
2021-03-24 12:57         ` Gerd Hoffmann
2021-03-24 13:26           ` Marc-André Lureau
2021-03-24 14:47             ` Gerd Hoffmann
2021-03-18  9:30 ` [PATCH v2 0/7] ui: add vdagent implementation and " no-reply

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).