All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/9] ui: add vdagent implementation and clipboard support.
@ 2021-05-05  6:08 Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 1/9] build: add separate spice-protocol config option Gerd Hoffmann
                   ` (9 more replies)
  0 siblings, 10 replies; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-05  6:08 UTC (permalink / raw)
  To: qemu-devel
  Cc: Marc-André Lureau, Paolo Bonzini, Gerd Hoffmann, Markus Armbruster

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

v5:
 - use error_report.
 - error out on bigendian hosts.
 - rename chardev to 'qemu-vdagent'.
 - misc minor tweaks.
v3:
 - rebase to latest master.
 - make spice-protocol a separate build dependency.
 - update qapi version annotation.
 - add documentatin for qemu clipboard.
 - misc little tweaks.
v2:
 - add a bunch of sanity checks.
 - add proper chunking.
 - use autofree.
v3:
 - support agents without VD_AGENT_CAP_CLIPBOARD_SELECTION.
 - properly parse chunked messages.
 - test with windows guests, minor fixes.
 - set display_id for agent mouse events.

Gerd Hoffmann (9):
  build: add separate spice-protocol config option
  ui: add clipboard infrastructure
  ui: add clipboard documentation
  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

 configure              |  36 +-
 include/ui/clipboard.h | 193 +++++++++++
 include/ui/gtk.h       |  67 ++++
 ui/vnc.h               |  24 ++
 chardev/char.c         |   6 +
 ui/clipboard.c         |  92 +++++
 ui/gtk-clipboard.c     | 192 +++++++++++
 ui/gtk.c               |  56 +--
 ui/vdagent.c           | 765 +++++++++++++++++++++++++++++++++++++++++
 ui/vnc-clipboard.c     | 323 +++++++++++++++++
 ui/vnc.c               |  20 +-
 docs/devel/index.rst   |   1 +
 docs/devel/ui.rst      |   8 +
 meson.build            |   4 +
 qapi/char.json         |  21 +-
 ui/meson.build         |   5 +-
 ui/trace-events        |  10 +
 17 files changed, 1756 insertions(+), 67 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
 create mode 100644 docs/devel/ui.rst

-- 
2.31.1




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

* [PATCH v5 1/9] build: add separate spice-protocol config option
  2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
@ 2021-05-05  6:08 ` Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 2/9] ui: add clipboard infrastructure Gerd Hoffmann
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-05  6:08 UTC (permalink / raw)
  To: qemu-devel
  Cc: Marc-André Lureau, Paolo Bonzini, Gerd Hoffmann, Markus Armbruster

When implementing spice vdagent protocol in qemu we only need the
spice-protocol package for that, spice-server is not needed.  So
go split those two build dependencies.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 configure   | 36 ++++++++++++++++++++++++++++++++----
 meson.build |  4 ++++
 2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/configure b/configure
index 4f374b48890e..e6f959da2347 100755
--- a/configure
+++ b/configure
@@ -388,6 +388,7 @@ qom_cast_debug="yes"
 trace_backends="log"
 trace_file="trace"
 spice="$default_feature"
+spice_protocol="auto"
 rbd="auto"
 smartcard="$default_feature"
 u2f="auto"
@@ -1129,7 +1130,15 @@ for opt do
   ;;
   --disable-spice) spice="no"
   ;;
-  --enable-spice) spice="yes"
+  --enable-spice)
+      spice_protocol="yes"
+      spice="yes"
+  ;;
+  --disable-spice-protocol)
+      spice_protocol="no"
+      spice="no"
+  ;;
+  --enable-spice-protocol) spice_protocol="yes"
   ;;
   --disable-libiscsi) libiscsi="disabled"
   ;;
@@ -1866,6 +1875,7 @@ disabled with --disable-FEATURE, default is enabled if available
   vhost-user-blk-server    vhost-user-blk server support
   vhost-vdpa      vhost-vdpa kernel backend support
   spice           spice
+  spice-protocol  spice-protocol
   rbd             rados block device (rbd)
   libiscsi        iscsi support
   libnfs          nfs support
@@ -4149,6 +4159,19 @@ fi
 
 ##########################################
 # spice probe
+if test "$spice_protocol" != "no" ; then
+  spice_protocol_cflags=$($pkg_config --cflags spice-protocol 2>/dev/null)
+  if $pkg_config --atleast-version=0.12.3 spice-protocol; then
+    spice_protocol="yes"
+  else
+    if test "$spice_protocol" = "yes" ; then
+      feature_not_found "spice_protocol" \
+          "Install spice-protocol(>=0.12.3) devel"
+    fi
+    spice_protocol="no"
+  fi
+fi
+
 if test "$spice" != "no" ; then
   cat > $TMPC << EOF
 #include <spice.h>
@@ -4157,13 +4180,13 @@ EOF
   spice_cflags=$($pkg_config --cflags spice-protocol spice-server 2>/dev/null)
   spice_libs=$($pkg_config --libs spice-protocol spice-server 2>/dev/null)
   if $pkg_config --atleast-version=0.12.5 spice-server && \
-     $pkg_config --atleast-version=0.12.3 spice-protocol && \
+     test "$spice_protocol" = "yes" && \
      compile_prog "$spice_cflags" "$spice_libs" ; then
     spice="yes"
   else
     if test "$spice" = "yes" ; then
       feature_not_found "spice" \
-          "Install spice-server(>=0.12.5) and spice-protocol(>=0.12.3) devel"
+          "Install spice-server(>=0.12.5) devel"
     fi
     spice="no"
   fi
@@ -5807,9 +5830,14 @@ fi
 if test "$posix_memalign" = "yes" ; then
   echo "CONFIG_POSIX_MEMALIGN=y" >> $config_host_mak
 fi
+
+if test "$spice_protocol" = "yes" ; then
+  echo "CONFIG_SPICE_PROTOCOL=y" >> $config_host_mak
+  echo "SPICE_PROTOCOL_CFLAGS=$spice_protocol_cflags" >> $config_host_mak
+fi
 if test "$spice" = "yes" ; then
   echo "CONFIG_SPICE=y" >> $config_host_mak
-  echo "SPICE_CFLAGS=$spice_cflags" >> $config_host_mak
+  echo "SPICE_CFLAGS=$spice_cflags $spice_protocol_cflags" >> $config_host_mak
   echo "SPICE_LIBS=$spice_libs" >> $config_host_mak
 fi
 
diff --git a/meson.build b/meson.build
index d8bb1ec5aa97..51898a96ba4d 100644
--- a/meson.build
+++ b/meson.build
@@ -445,11 +445,15 @@ if 'CONFIG_LIBJACK' in config_host
 endif
 spice = not_found
 spice_headers = not_found
+spice_protocol = not_found
 if 'CONFIG_SPICE' in config_host
   spice = declare_dependency(compile_args: config_host['SPICE_CFLAGS'].split(),
                              link_args: config_host['SPICE_LIBS'].split())
   spice_headers = declare_dependency(compile_args: config_host['SPICE_CFLAGS'].split())
 endif
+if 'CONFIG_SPICE_PROTOCOL' in config_host
+  spice_protocol = declare_dependency(compile_args: config_host['SPICE_PROTOCOL_CFLAGS'].split())
+endif
 rt = cc.find_library('rt', required: false)
 libdl = not_found
 if 'CONFIG_PLUGIN' in config_host
-- 
2.31.1



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

* [PATCH v5 2/9] ui: add clipboard infrastructure
  2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 1/9] build: add separate spice-protocol config option Gerd Hoffmann
@ 2021-05-05  6:08 ` Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 3/9] ui: add clipboard documentation Gerd Hoffmann
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-05  6:08 UTC (permalink / raw)
  To: qemu-devel
  Cc: Marc-André Lureau, Paolo Bonzini, Gerd Hoffmann, Markus Armbruster

Add some infrastructure to manage the clipboard in qemu.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/clipboard.h | 62 ++++++++++++++++++++++++++++
 ui/clipboard.c         | 92 ++++++++++++++++++++++++++++++++++++++++++
 ui/meson.build         |  1 +
 3 files changed, 155 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..876de7621911
--- /dev/null
+++ b/include/ui/clipboard.h
@@ -0,0 +1,62 @@
+#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];
+};
+
+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_ref(QemuClipboardInfo *info);
+void qemu_clipboard_info_unref(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..abf2b98f1f89
--- /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_ref(QemuClipboardInfo *info)
+{
+    info->refcount++;
+    return info;
+}
+
+void qemu_clipboard_info_unref(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.31.1



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

* [PATCH v5 3/9] ui: add clipboard documentation
  2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 1/9] build: add separate spice-protocol config option Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 2/9] ui: add clipboard infrastructure Gerd Hoffmann
@ 2021-05-05  6:08 ` Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-05  6:08 UTC (permalink / raw)
  To: qemu-devel
  Cc: Marc-André Lureau, Paolo Bonzini, Gerd Hoffmann, Markus Armbruster

Document clipboard infrastructure in qemu.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/clipboard.h | 133 ++++++++++++++++++++++++++++++++++++++++-
 docs/devel/index.rst   |   1 +
 docs/devel/ui.rst      |   8 +++
 3 files changed, 141 insertions(+), 1 deletion(-)
 create mode 100644 docs/devel/ui.rst

diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
index 876de7621911..e5bcb365ed62 100644
--- a/include/ui/clipboard.h
+++ b/include/ui/clipboard.h
@@ -3,17 +3,47 @@
 
 #include "qemu/notify.h"
 
+/**
+ * DOC: Introduction
+ *
+ * The header ``ui/clipboard.h`` declares the qemu clipboard interface.
+ *
+ * All qemu elements which want use the clipboard can register as
+ * clipboard peer.  Subsequently they can set the clipboard content
+ * and get notifications for clipboard updates.
+ *
+ * Typical users are user interfaces (gtk), remote access protocols
+ * (vnc) and devices talking to the guest (vdagent).
+ *
+ * Even though the design allows different data types only plain text
+ * is supported for now.
+ */
+
 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: type count.
+ */
 enum QemuClipboardType {
-    QEMU_CLIPBOARD_TYPE_TEXT,  /* text/plain; charset=utf-8 */
+    QEMU_CLIPBOARD_TYPE_TEXT,
     QEMU_CLIPBOARD_TYPE__COUNT,
 };
 
 /* same as VD_AGENT_CLIPBOARD_SELECTION_* */
+/**
+ * enum QemuClipboardSelection
+ *
+ * @QEMU_CLIPBOARD_SELECTION_CLIPBOARD: clipboard (explitcit cut+paste).
+ * @QEMU_CLIPBOARD_SELECTION_PRIMARY: primary selection (select + middle mouse button).
+ * @QEMU_CLIPBOARD_SELECTION_SECONDARY: secondary selection (dunno).
+ * @QEMU_CLIPBOARD_SELECTION__COUNT: selection count.
+ */
 enum QemuClipboardSelection {
     QEMU_CLIPBOARD_SELECTION_CLIPBOARD,
     QEMU_CLIPBOARD_SELECTION_PRIMARY,
@@ -21,6 +51,15 @@ enum QemuClipboardSelection {
     QEMU_CLIPBOARD_SELECTION__COUNT,
 };
 
+/**
+ * struct QemuClipboardPeer
+ *
+ * @name: peer name.
+ * @update: notifier for clipboard updates.
+ * @request: callback for clipboard data requests.
+ *
+ * Clipboard peer description.
+ */
 struct QemuClipboardPeer {
     const char *name;
     Notifier update;
@@ -28,6 +67,16 @@ struct QemuClipboardPeer {
                     QemuClipboardType type);
 };
 
+/**
+ * struct QemuClipboardInfo
+ *
+ * @refcount: reference counter.
+ * @owner: clipboard owner.
+ * @selection: clipboard selection.
+ * @types: clipboard data array (one entry per type).
+ *
+ * Clipboard content data and metadata.
+ */
 struct QemuClipboardInfo {
     uint32_t refcount;
     QemuClipboardPeer *owner;
@@ -40,18 +89,100 @@ struct QemuClipboardInfo {
     } types[QEMU_CLIPBOARD_TYPE__COUNT];
 };
 
+/**
+ * qemu_clipboard_peer_register
+ *
+ * @peer: peer information.
+ *
+ * Register clipboard peer.  Registering is needed for both active
+ * (set+grab clipboard) and passive (watch clipboard for updates)
+ * interaction with the qemu clipboard.
+ */
 void qemu_clipboard_peer_register(QemuClipboardPeer *peer);
+
+/**
+ * qemu_clipboard_peer_unregister
+ *
+ * @peer: peer information.
+ *
+ * Unregister clipboard peer.
+ */
 void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer);
 
+/**
+ * qemu_clipboard_info_new
+ *
+ * @owner: clipboard owner.
+ * @selection: clipboard selection.
+ *
+ * Allocate a new QemuClipboardInfo and initialize it with the given
+ * @owner and @selection.
+ *
+ * QemuClipboardInfo is a reference-counted struct.  The new struct is
+ * returned with a reference already taken (i.e. reference count is
+ * one).
+ */
 QemuClipboardInfo *qemu_clipboard_info_new(QemuClipboardPeer *owner,
                                            QemuClipboardSelection selection);
+/**
+ * qemu_clipboard_info_ref
+ *
+ * @info: clipboard info.
+ *
+ * Increase @info reference count.
+ */
 QemuClipboardInfo *qemu_clipboard_info_ref(QemuClipboardInfo *info);
+
+/**
+ * qemu_clipboard_info_unref
+ *
+ * @info: clipboard info.
+ *
+ * Decrease @info reference count.  When the count goes down to zero
+ * free the @info struct itself and all clipboard data.
+ */
 void qemu_clipboard_info_unref(QemuClipboardInfo *info);
 
+/**
+ * qemu_clipboard_update
+ *
+ * @info: clipboard info.
+ *
+ * Update the qemu clipboard.  Notify all registered peers (including
+ * the clipboard owner) that the qemu clipboard has been updated.
+ *
+ * This is used for both new completely clipboard content and for
+ * clipboard data updates in response to qemu_clipboard_request()
+ * calls.
+ */
 void qemu_clipboard_update(QemuClipboardInfo *info);
+
+/**
+ * qemu_clipboard_request
+ *
+ * @info: clipboard info.
+ * @type: clipboard data type.
+ *
+ * Request clipboard content.  Typically the clipboard owner only
+ * advertises the available data types and provides the actual data
+ * only on request.
+ */
 void qemu_clipboard_request(QemuClipboardInfo *info,
                             QemuClipboardType type);
 
+/**
+ * qemu_clipboard_set_data
+ *
+ * @peer: clipboard peer.
+ * @info: clipboard info.
+ * @type: clipboard data type.
+ * @size: data size.
+ * @data: data blob.
+ * @update: notify peers about the update.
+ *
+ * Set clipboard content for the given @type.  This function will make
+ * a copy of the content data and store that.
+ */
 void qemu_clipboard_set_data(QemuClipboardPeer *peer,
                              QemuClipboardInfo *info,
                              QemuClipboardType type,
diff --git a/docs/devel/index.rst b/docs/devel/index.rst
index 6cf7e2d2330c..cbdbb9049182 100644
--- a/docs/devel/index.rst
+++ b/docs/devel/index.rst
@@ -36,6 +36,7 @@ Contents:
    multi-thread-tcg
    tcg-plugins
    bitops
+   ui
    reset
    s390-dasd-ipl
    clocks
diff --git a/docs/devel/ui.rst b/docs/devel/ui.rst
new file mode 100644
index 000000000000..06c7d622ce74
--- /dev/null
+++ b/docs/devel/ui.rst
@@ -0,0 +1,8 @@
+=================
+Qemu UI subsystem
+=================
+
+Qemu Clipboard
+--------------
+
+.. kernel-doc:: include/ui/clipboard.h
-- 
2.31.1



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

* [PATCH v5 4/9] ui/vdagent: core infrastructure
  2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (2 preceding siblings ...)
  2021-05-05  6:08 ` [PATCH v5 3/9] ui: add clipboard documentation Gerd Hoffmann
@ 2021-05-05  6:08 ` Gerd Hoffmann
  2021-05-05  9:54   ` Marc-André Lureau
  2021-05-05  6:08 ` [PATCH v5 5/9] ui/vdagent: add mouse support Gerd Hoffmann
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-05  6:08 UTC (permalink / raw)
  To: qemu-devel
  Cc: Marc-André Lureau, Paolo Bonzini, Gerd Hoffmann, Markus Armbruster

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 qemu-vdagent,id=vdagent \
  -device virtserialport,chardev=vdagent,name=com.redhat.spice.0

This patch adds just the protocol basics: initial handshake and
capability negotiation.  The following patches will add actual
functionality and also add fields to the initially empty
ChardevVDAgent qapi struct.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Acked-by: Markus Armbruster <armbru@redhat.com>
---
 ui/vdagent.c    | 323 ++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/char.json  |  17 ++-
 ui/meson.build  |   1 +
 ui/trace-events |   8 ++
 4 files changed, 348 insertions(+), 1 deletion(-)
 create mode 100644 ui/vdagent.c

diff --git a/ui/vdagent.c b/ui/vdagent.c
new file mode 100644
index 000000000000..e757a3c9c710
--- /dev/null
+++ b/ui/vdagent.c
@@ -0,0 +1,323 @@
+#include "qemu/osdep.h"
+#include "qapi/error.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;
+    uint8_t *xbuf;
+    uint32_t xoff, xsize;
+};
+typedef struct VDAgentChardev VDAgentChardev;
+
+#define TYPE_CHARDEV_QEMU_VDAGENT "chardev-qemu-vdagent"
+
+DECLARE_INSTANCE_CHECKER(VDAgentChardev, QEMU_VDAGENT_CHARDEV,
+                         TYPE_CHARDEV_QEMU_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)
+{
+#if defined(HOST_WORDS_BIGENDIAN)
+    /*
+     * TODO: vdagent protocol is defined to be LE,
+     * so we have to byteswap everything on BE hosts.
+     */
+    error_setg(errp, "vdagent is not supported on bigendian hosts");
+    return;
+#endif
+
+    *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_msg(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+    trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type), msg->size);
+
+    switch (msg->type) {
+    case VD_AGENT_ANNOUNCE_CAPABILITIES:
+        vdagent_chr_recv_caps(vd, msg);
+        break;
+    default:
+        break;
+    }
+}
+
+static void vdagent_reset_xbuf(VDAgentChardev *vd)
+{
+    g_clear_pointer(&vd->xbuf, g_free);
+    vd->xoff = 0;
+    vd->xsize = 0;
+}
+
+static void vdagent_chr_recv_chunk(VDAgentChardev *vd)
+{
+    VDAgentMessage *msg = (void *)vd->msgbuf;
+
+    if (!vd->xsize) {
+        if (vd->msgsize < sizeof(*msg)) {
+            error_report("%s: message too small: %d < %zd", __func__,
+                         vd->msgsize, sizeof(*msg));
+            return;
+        }
+        if (vd->msgsize == msg->size + sizeof(*msg)) {
+            vdagent_chr_recv_msg(vd, msg);
+            return;
+        }
+    }
+
+    if (!vd->xsize) {
+        vd->xsize = msg->size + sizeof(*msg);
+        vd->xbuf = g_malloc0(vd->xsize);
+    }
+
+    if (vd->xoff + vd->msgsize > vd->xsize) {
+        error_report("%s: Oops: %d+%d > %d", __func__,
+                     vd->xoff, vd->msgsize, vd->xsize);
+        vdagent_reset_xbuf(vd);
+        return;
+    }
+
+    memcpy(vd->xbuf + vd->xoff, vd->msgbuf, vd->msgsize);
+    vd->xoff += vd->msgsize;
+    if (vd->xoff < vd->xsize) {
+        return;
+    }
+
+    msg = (void *)vd->xbuf;
+    vdagent_chr_recv_msg(vd, msg);
+    vdagent_reset_xbuf(vd);
+}
+
+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 = QEMU_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_chunk(vd);
+            vdagent_reset_bufs(vd);
+        }
+    }
+
+    return ret;
+}
+
+static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
+{
+    VDAgentChardev *vd = QEMU_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_QEMU_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..990801e642bb 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -390,12 +390,25 @@
   'data': { '*size': 'int' },
   'base': 'ChardevCommon' }
 
+##
+# @ChardevQemuVDAgent:
+#
+# Configuration info for qemu vdagent implementation.
+#
+# Since: 6.1
+#
+##
+{ 'struct': 'ChardevQemuVDAgent',
+  'data': { },
+  'base': 'ChardevCommon',
+  'if': 'defined(CONFIG_SPICE_PROTOCOL)' }
+
 ##
 # @ChardevBackend:
 #
 # Configuration info for the new chardev backend.
 #
-# Since: 1.4 (testdev since 2.2, wctablet since 2.9)
+# Since: 1.4 (testdev since 2.2, wctablet since 2.9, vdagent since 6.1)
 ##
 { 'union': 'ChardevBackend',
   'data': { 'file': 'ChardevFile',
@@ -417,6 +430,8 @@
                           'if': 'defined(CONFIG_SPICE)' },
             'spiceport': { 'type': 'ChardevSpicePort',
                            'if': 'defined(CONFIG_SPICE)' },
+            'qemu-vdagent': { 'type': 'ChardevQemuVDAgent',
+                              'if': 'defined(CONFIG_SPICE_PROTOCOL)' },
             'vc': 'ChardevVC',
             'ringbuf': 'ChardevRingbuf',
             # next one is just for compatibility
diff --git a/ui/meson.build b/ui/meson.build
index fc4fb75c2869..bad49fb6de60 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_protocol, 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..c34cffb0452b 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, uint32_t size) "msg %s, size %d"
+vdagent_peer_cap(const char *name) "cap %s"
-- 
2.31.1



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

* [PATCH v5 5/9] ui/vdagent: add mouse support
  2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (3 preceding siblings ...)
  2021-05-05  6:08 ` [PATCH v5 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
@ 2021-05-05  6:08 ` Gerd Hoffmann
  2021-05-05  6:08 ` [PATCH v5 6/9] ui/vdagent: add clipboard support Gerd Hoffmann
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-05  6:08 UTC (permalink / raw)
  To: qemu-devel
  Cc: Marc-André Lureau, Paolo Bonzini, Gerd Hoffmann, Markus Armbruster

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>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 chardev/char.c |   3 +
 ui/vdagent.c   | 150 +++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/char.json |   4 +-
 3 files changed, 156 insertions(+), 1 deletion(-)

diff --git a/chardev/char.c b/chardev/char.c
index 398f09df19cd..9714057541fb 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -932,6 +932,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 e757a3c9c710..c013706efb6a 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -2,15 +2,25 @@
 #include "qapi/error.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;
@@ -19,6 +29,14 @@ struct VDAgentChardev {
     uint32_t msgsize;
     uint8_t *xbuf;
     uint32_t xoff, xsize;
+
+    /* mouse */
+    DeviceState mouse_dev;
+    uint32_t mouse_x;
+    uint32_t mouse_y;
+    uint32_t mouse_btn;
+    uint32_t mouse_display;
+    QemuInputHandlerState *mouse_hs;
 };
 typedef struct VDAgentChardev VDAgentChardev;
 
@@ -123,13 +141,113 @@ 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 bool have_mouse(VDAgentChardev *vd)
+{
+    return vd->mouse &&
+        (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE));
+}
+
+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;
+    mouse->display_id = vd->mouse_display;
+
+    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);
+        }
+        vd->mouse_display = qemu_console_get_index(src);
+        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                                                    */
 
@@ -138,6 +256,9 @@ static void vdagent_chr_open(Chardev *chr,
                              bool *be_opened,
                              Error **errp)
 {
+    VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
+    ChardevQemuVDAgent *cfg = backend->u.qemu_vdagent.data;
+
 #if defined(HOST_WORDS_BIGENDIAN)
     /*
      * TODO: vdagent protocol is defined to be LE,
@@ -147,6 +268,16 @@ static void vdagent_chr_open(Chardev *chr,
     return;
 #endif
 
+    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;
 }
 
@@ -170,6 +301,9 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
     if (caps->request) {
         vdagent_send_caps(vd);
     }
+    if (have_mouse(vd) && vd->mouse_hs) {
+        qemu_input_handler_activate(vd->mouse_hs);
+    }
 }
 
 static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg)
@@ -291,18 +425,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)
+{
+    ChardevQemuVDAgent *cfg;
+
+    backend->type = CHARDEV_BACKEND_KIND_QEMU_VDAGENT;
+    cfg = backend->u.qemu_vdagent.data = g_new0(ChardevQemuVDAgent, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_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 990801e642bb..5711e8c60aeb 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -395,11 +395,13 @@
 #
 # Configuration info for qemu vdagent implementation.
 #
+# @mouse: enable/disable mouse, default is enabled.
+#
 # Since: 6.1
 #
 ##
 { 'struct': 'ChardevQemuVDAgent',
-  'data': { },
+  'data': { '*mouse': 'bool' },
   'base': 'ChardevCommon',
   'if': 'defined(CONFIG_SPICE_PROTOCOL)' }
 
-- 
2.31.1



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

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

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    | 292 ++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/char.json  |   4 +-
 ui/trace-events |   2 +
 4 files changed, 300 insertions(+), 1 deletion(-)

diff --git a/chardev/char.c b/chardev/char.c
index 9714057541fb..39d41d3df7bb 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -935,6 +935,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 c013706efb6a..64213aa25a06 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -4,6 +4,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"
@@ -14,12 +15,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;
@@ -37,6 +40,11 @@ struct VDAgentChardev {
     uint32_t mouse_btn;
     uint32_t mouse_display;
     QemuInputHandlerState *mouse_hs;
+
+    /* clipboard */
+    QemuClipboardPeer cbpeer;
+    QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
+    uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
 };
 typedef struct VDAgentChardev VDAgentChardev;
 
@@ -92,6 +100,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]) : "???")
 
@@ -148,6 +174,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);
 }
@@ -248,6 +278,243 @@ static QemuInputHandler vdagent_mouse_handler = {
     .sync  = vdagent_pointer_sync,
 };
 
+/* ------------------------------------------------------------------ */
+/* clipboard                                                          */
+
+static bool have_clipboard(VDAgentChardev *vd)
+{
+    return vd->clipboard &&
+        (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
+}
+
+static bool have_selection(VDAgentChardev *vd)
+{
+    return vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
+}
+
+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;
+    uint32_t q, type;
+
+    if (have_selection(vd)) {
+        *s = info->selection;
+        data++;
+        msg->size += sizeof(uint32_t);
+    } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
+        return;
+    }
+
+    for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
+        type = type_qemu_to_vdagent(q);
+        if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
+            *data = type;
+            data++;
+            msg->size += sizeof(uint32_t);
+        }
+    }
+
+    msg->type = VD_AGENT_CLIPBOARD_GRAB;
+    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 *data = (uint32_t *)msg->data;
+
+    if (have_selection(vd)) {
+        *s = info->selection;
+        data++;
+        msg->size += sizeof(uint32_t);
+    } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
+        return;
+    }
+
+    *data = type_qemu_to_vdagent(type);
+    data++;
+    msg->size += sizeof(uint32_t);
+
+    memcpy(data, info->types[type].data, info->types[type].size);
+    msg->size += info->types[type].size;
+
+    msg->type = VD_AGENT_CLIPBOARD;
+    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_unref(vd->cbinfo[s]);
+        vd->cbinfo[s] = qemu_clipboard_info_ref(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;
+
+    if (type == VD_AGENT_CLIPBOARD_NONE) {
+        return;
+    }
+
+    if (have_selection(vd)) {
+        *s = info->selection;
+        data++;
+        msg->size += sizeof(uint32_t);
+    }
+
+    *data = type;
+    msg->size += sizeof(uint32_t);
+
+    msg->type = VD_AGENT_CLIPBOARD_REQUEST;
+    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 (have_selection(vd)) {
+        if (size < 4) {
+            return;
+        }
+        s = *(uint8_t *)data;
+        if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
+            return;
+        }
+        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_unref(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_unref(info);
+        }
+        break;
+    }
+}
+
 /* ------------------------------------------------------------------ */
 /* chardev backend                                                    */
 
@@ -273,6 +540,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);
@@ -304,6 +576,12 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
     if (have_mouse(vd) && vd->mouse_hs) {
         qemu_input_handler_activate(vd->mouse_hs);
     }
+    if (have_clipboard(vd) && 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_msg(VDAgentChardev *vd, VDAgentMessage *msg)
@@ -314,6 +592,14 @@ static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg)
     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:
+        if (have_clipboard(vd)) {
+            vdagent_chr_recv_clipboard(vd, msg);
+        }
+        break;
     default:
         break;
     }
@@ -428,6 +714,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;
     }
 
@@ -444,6 +734,8 @@ static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend,
     qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_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 5711e8c60aeb..adf2685f6889 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -396,12 +396,14 @@
 # Configuration info for qemu vdagent implementation.
 #
 # @mouse: enable/disable mouse, default is enabled.
+# @clipboard: enable/disable clipboard, default is disabled.
 #
 # Since: 6.1
 #
 ##
 { 'struct': 'ChardevQemuVDAgent',
-  'data': { '*mouse': 'bool' },
+  'data': { '*mouse': 'bool',
+            '*clipboard': 'bool' },
   'base': 'ChardevCommon',
   'if': 'defined(CONFIG_SPICE_PROTOCOL)' }
 
diff --git a/ui/trace-events b/ui/trace-events
index c34cffb0452b..c86542e2b69b 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, uint32_t size) "msg %s, size %d"
 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.31.1



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

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

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>
Reviewed-by: Marc-André Lureau <marcandre.lureau@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..9f077965d056
--- /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_unref(vs->cbinfo);
+        vs->cbinfo = qemu_clipboard_info_ref(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_unref(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_unref(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 456db47d713d..1d7f7f326c52 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"
@@ -1352,6 +1353,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);
 
@@ -1777,10 +1781,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);
@@ -2222,6 +2222,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;
@@ -2438,7 +2442,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);
@@ -2450,7 +2454,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 bad49fb6de60..f37ef882e0e3 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.31.1



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

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

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>
Reviewed-by: Marc-André Lureau <marcandre.lureau@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.31.1



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

* [PATCH v5 9/9] ui/gtk: add clipboard support
  2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (7 preceding siblings ...)
  2021-05-05  6:09 ` [PATCH v5 8/9] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
@ 2021-05-05  6:09 ` Gerd Hoffmann
  2021-05-05  6:21 ` [PATCH v5 0/9] ui: add vdagent implementation and " no-reply
  9 siblings, 0 replies; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-05  6:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Marc-André Lureau, Paolo Bonzini, Gerd Hoffmann, Markus Armbruster

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 | 192 +++++++++++++++++++++++++++++++++++++++++++++
 ui/gtk.c           |   1 +
 ui/meson.build     |   2 +-
 4 files changed, 204 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..bff28d203014
--- /dev/null
+++ b/ui/gtk-clipboard.c
@@ -0,0 +1,192 @@
+/*
+ * 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_ref(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_unref(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_unref(gd->cbinfo[s]);
+        gd->cbinfo[s] = qemu_clipboard_info_ref(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]);
+        if (text) {
+            qemu_clipboard_set_data(&gd->cbpeer, info, type,
+                                    strlen(text), text, true);
+            g_free(text);
+        }
+        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_unref(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 f37ef882e0e3..b5aed14886cf 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.31.1



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

* Re: [PATCH v5 0/9] ui: add vdagent implementation and clipboard support.
  2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
                   ` (8 preceding siblings ...)
  2021-05-05  6:09 ` [PATCH v5 9/9] ui/gtk: add clipboard support Gerd Hoffmann
@ 2021-05-05  6:21 ` no-reply
  9 siblings, 0 replies; 14+ messages in thread
From: no-reply @ 2021-05-05  6:21 UTC (permalink / raw)
  To: kraxel; +Cc: marcandre.lureau, kraxel, qemu-devel, armbru, pbonzini

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



Hi,

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

Type: series
Message-id: 20210505060901.828658-1-kraxel@redhat.com
Subject: [PATCH v5 0/9] 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
 * [new tag]         patchew/20210505060901.828658-1-kraxel@redhat.com -> patchew/20210505060901.828658-1-kraxel@redhat.com
Switched to a new branch 'test'
acaef73 ui/gtk: add clipboard support
06a78fb ui/gtk: move struct GtkDisplayState to ui/gtk.h
984719a ui/vnc: clipboard support
a9dd088 ui/vdagent: add clipboard support
98910e7 ui/vdagent: add mouse support
a9d3566 ui/vdagent: core infrastructure
654f3c6 ui: add clipboard documentation
a87f32b ui: add clipboard infrastructure
705d039 build: add separate spice-protocol config option

=== OUTPUT BEGIN ===
1/9 Checking commit 705d0398e61e (build: add separate spice-protocol config option)
2/9 Checking commit a87f32b2c22b (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?
#19: 
new file mode 100644

total: 0 errors, 1 warnings, 161 lines checked

Patch 2/9 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
3/9 Checking commit 654f3c6fd00e (ui: add clipboard documentation)
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?
#31: 
new file mode 100644

WARNING: line over 80 characters
#90: FILE: include/ui/clipboard.h:43:
+ * @QEMU_CLIPBOARD_SELECTION_PRIMARY: primary selection (select + middle mouse button).

total: 0 errors, 2 warnings, 194 lines checked

Patch 3/9 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
4/9 Checking commit a9d3566dcfdc (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?
#102: 
new file mode 100644

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

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

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

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

total: 2 errors, 3 warnings, 374 lines checked

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

5/9 Checking commit 98910e77b573 (ui/vdagent: add mouse support)
ERROR: if this code is redundant consider removing it
#148: FILE: ui/vdagent.c:190:
+#if 0

total: 1 errors, 0 warnings, 244 lines checked

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

6/9 Checking commit a9dd088eb5bb (ui/vdagent: add clipboard support)
ERROR: if this code is redundant consider removing it
#121: FILE: ui/vdagent.c:116:
+#if 0

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

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

total: 2 errors, 1 warnings, 393 lines checked

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

7/9 Checking commit 984719a1ee04 (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?
#34: 
new file mode 100644

WARNING: line over 80 characters
#281: 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
#290: FILE: ui/vnc-clipboard.c:252:
+            qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);

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

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

WARNING: line over 80 characters
#500: 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 7/9 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

8/9 Checking commit 06a78fb10b88 (ui/gtk: move struct GtkDisplayState to ui/gtk.h)
9/9 Checking commit acaef730d67f (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, 233 lines checked

Patch 9/9 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/20210505060901.828658-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] 14+ messages in thread

* Re: [PATCH v5 4/9] ui/vdagent: core infrastructure
  2021-05-05  6:08 ` [PATCH v5 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
@ 2021-05-05  9:54   ` Marc-André Lureau
  2021-05-11  9:04     ` Gerd Hoffmann
  0 siblings, 1 reply; 14+ messages in thread
From: Marc-André Lureau @ 2021-05-05  9:54 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

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

Hi

On Wed, May 5, 2021 at 10:14 AM 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 qemu-vdagent,id=vdagent \
>   -device virtserialport,chardev=vdagent,name=com.redhat.spice.0
>
> This patch adds just the protocol basics: initial handshake and
> capability negotiation.  The following patches will add actual
> functionality and also add fields to the initially empty
> ChardevVDAgent qapi struct.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> Acked-by: Markus Armbruster <armbru@redhat.com>
>

looks ok to me, but:

---
>  ui/vdagent.c    | 323 ++++++++++++++++++++++++++++++++++++++++++++++++
>  qapi/char.json  |  17 ++-
>  ui/meson.build  |   1 +
>  ui/trace-events |   8 ++
>  4 files changed, 348 insertions(+), 1 deletion(-)
>  create mode 100644 ui/vdagent.c
>
> diff --git a/ui/vdagent.c b/ui/vdagent.c
> new file mode 100644
> index 000000000000..e757a3c9c710
> --- /dev/null
> +++ b/ui/vdagent.c
> @@ -0,0 +1,323 @@
> +#include "qemu/osdep.h"
> +#include "qapi/error.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;
> +    uint8_t *xbuf;
> +    uint32_t xoff, xsize;
> +};
> +typedef struct VDAgentChardev VDAgentChardev;
> +
> +#define TYPE_CHARDEV_QEMU_VDAGENT "chardev-qemu-vdagent"
> +
> +DECLARE_INSTANCE_CHECKER(VDAgentChardev, QEMU_VDAGENT_CHARDEV,
> +                         TYPE_CHARDEV_QEMU_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;
> +    }
>

This looks like it could easily busy loop. Have you thought about fixing
this?

+}
> +
> +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)
> +{
> +#if defined(HOST_WORDS_BIGENDIAN)
> +    /*
> +     * TODO: vdagent protocol is defined to be LE,
> +     * so we have to byteswap everything on BE hosts.
> +     */
> +    error_setg(errp, "vdagent is not supported on bigendian hosts");
> +    return;
> +#endif
> +
> +    *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_msg(VDAgentChardev *vd, VDAgentMessage *msg)
> +{
> +    trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type), msg->size);
> +
> +    switch (msg->type) {
> +    case VD_AGENT_ANNOUNCE_CAPABILITIES:
> +        vdagent_chr_recv_caps(vd, msg);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static void vdagent_reset_xbuf(VDAgentChardev *vd)
> +{
> +    g_clear_pointer(&vd->xbuf, g_free);
> +    vd->xoff = 0;
> +    vd->xsize = 0;
> +}
> +
> +static void vdagent_chr_recv_chunk(VDAgentChardev *vd)
> +{
> +    VDAgentMessage *msg = (void *)vd->msgbuf;
> +
> +    if (!vd->xsize) {
> +        if (vd->msgsize < sizeof(*msg)) {
> +            error_report("%s: message too small: %d < %zd", __func__,
> +                         vd->msgsize, sizeof(*msg));
> +            return;
> +        }
> +        if (vd->msgsize == msg->size + sizeof(*msg)) {
> +            vdagent_chr_recv_msg(vd, msg);
> +            return;
> +        }
> +    }
> +
> +    if (!vd->xsize) {
> +        vd->xsize = msg->size + sizeof(*msg);
> +        vd->xbuf = g_malloc0(vd->xsize);
> +    }
> +
> +    if (vd->xoff + vd->msgsize > vd->xsize) {
> +        error_report("%s: Oops: %d+%d > %d", __func__,
> +                     vd->xoff, vd->msgsize, vd->xsize);
> +        vdagent_reset_xbuf(vd);
> +        return;
> +    }
> +
> +    memcpy(vd->xbuf + vd->xoff, vd->msgbuf, vd->msgsize);
> +    vd->xoff += vd->msgsize;
> +    if (vd->xoff < vd->xsize) {
> +        return;
> +    }
> +
> +    msg = (void *)vd->xbuf;
> +    vdagent_chr_recv_msg(vd, msg);
> +    vdagent_reset_xbuf(vd);
> +}
> +
> +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 = QEMU_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_chunk(vd);
> +            vdagent_reset_bufs(vd);
> +        }
> +    }
> +
> +    return ret;
> +}
> +
> +static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
> +{
> +    VDAgentChardev *vd = QEMU_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_QEMU_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..990801e642bb 100644
> --- a/qapi/char.json
> +++ b/qapi/char.json
> @@ -390,12 +390,25 @@
>    'data': { '*size': 'int' },
>    'base': 'ChardevCommon' }
>
> +##
> +# @ChardevQemuVDAgent:
> +#
> +# Configuration info for qemu vdagent implementation.
> +#
> +# Since: 6.1
> +#
> +##
> +{ 'struct': 'ChardevQemuVDAgent',
> +  'data': { },
> +  'base': 'ChardevCommon',
> +  'if': 'defined(CONFIG_SPICE_PROTOCOL)' }
> +
>  ##
>  # @ChardevBackend:
>  #
>  # Configuration info for the new chardev backend.
>  #
> -# Since: 1.4 (testdev since 2.2, wctablet since 2.9)
> +# Since: 1.4 (testdev since 2.2, wctablet since 2.9, vdagent since 6.1)
>  ##
>  { 'union': 'ChardevBackend',
>    'data': { 'file': 'ChardevFile',
> @@ -417,6 +430,8 @@
>                            'if': 'defined(CONFIG_SPICE)' },
>              'spiceport': { 'type': 'ChardevSpicePort',
>                             'if': 'defined(CONFIG_SPICE)' },
> +            'qemu-vdagent': { 'type': 'ChardevQemuVDAgent',
> +                              'if': 'defined(CONFIG_SPICE_PROTOCOL)' },
>              'vc': 'ChardevVC',
>              'ringbuf': 'ChardevRingbuf',
>              # next one is just for compatibility
> diff --git a/ui/meson.build b/ui/meson.build
> index fc4fb75c2869..bad49fb6de60 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_protocol, 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..c34cffb0452b 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, uint32_t size) "msg %s, size %d"
> +vdagent_peer_cap(const char *name) "cap %s"
> --
> 2.31.1
>
>
>

-- 
Marc-André Lureau

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

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

* Re: [PATCH v5 4/9] ui/vdagent: core infrastructure
  2021-05-05  9:54   ` Marc-André Lureau
@ 2021-05-11  9:04     ` Gerd Hoffmann
  2021-05-11  9:27       ` Marc-André Lureau
  0 siblings, 1 reply; 14+ messages in thread
From: Gerd Hoffmann @ 2021-05-11  9:04 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

> > +/* ------------------------------------------------------------------ */
> > +/* 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;
> > +    }
> >
> 
> This looks like it could easily busy loop. Have you thought about fixing
> this?

Incremental fix [ to be squashed ]

take care,
  Gerd

diff --git a/ui/vdagent.c b/ui/vdagent.c
index 64213aa25a06..efa98725fb22 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -3,7 +3,9 @@
 #include "include/qemu-common.h"
 #include "chardev/char.h"
 #include "hw/qdev-core.h"
+#include "qemu/buffer.h"
 #include "qemu/option.h"
+#include "qemu/units.h"
 #include "ui/clipboard.h"
 #include "ui/console.h"
 #include "ui/input.h"
@@ -16,6 +18,7 @@
 
 #define VDAGENT_MOUSE_DEFAULT true
 #define VDAGENT_CLIPBOARD_DEFAULT false
+#define VDAGENT_BUFFER_LIMIT (1 * MiB)
 
 struct VDAgentChardev {
     Chardev parent;
@@ -32,6 +35,7 @@ struct VDAgentChardev {
     uint32_t msgsize;
     uint8_t *xbuf;
     uint32_t xoff, xsize;
+    Buffer outbuf;
 
     /* mouse */
     DeviceState mouse_dev;
@@ -124,18 +128,20 @@ static const char *type_name[] = {
 /* ------------------------------------------------------------------ */
 /* send messages                                                      */
 
-static void vdagent_send_buf(VDAgentChardev *vd, void *ptr, uint32_t msgsize)
+static void vdagent_send_buf(VDAgentChardev *vd)
 {
-    uint8_t *msgbuf = ptr;
-    uint32_t len, pos = 0;
+    uint32_t len;
 
-    while (pos < msgsize) {
+    while (!buffer_empty(&vd->outbuf)) {
         len = qemu_chr_be_can_write(CHARDEV(vd));
-        if (len > msgsize - pos) {
-            len = msgsize - pos;
+        if (len == 0) {
+            return;
         }
-        qemu_chr_be_write(CHARDEV(vd), msgbuf + pos, len);
-        pos += len;
+        if (len > vd->outbuf.offset) {
+            len = vd->outbuf.offset;
+        }
+        qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len);
+        buffer_advance(&vd->outbuf, len);
     }
 }
 
@@ -150,16 +156,22 @@ static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg)
 
     msg->protocol = VD_AGENT_PROTOCOL;
 
+    if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) {
+        return;
+    }
+
     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);
+        buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size);
+        buffer_append(&vd->outbuf, &chunk, sizeof(chunk));
+        buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size);
         msgoff += chunk.size;
     }
+    vdagent_send_buf(vd);
 }
 
 static void vdagent_send_caps(VDAgentChardev *vd)
@@ -550,6 +562,7 @@ static void vdagent_chr_open(Chardev *chr,
                                                    &vdagent_mouse_handler);
     }
 
+    buffer_init(&vd->outbuf, "vdagent-outbuf");
     *be_opened = true;
 }
 
@@ -702,6 +715,13 @@ static int vdagent_chr_write(Chardev *chr, const uint8_t *buf, int len)
     return ret;
 }
 
+static void vdagent_chr_accept_input(Chardev *chr)
+{
+    VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
+
+    vdagent_send_buf(vd);
+}
+
 static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
 {
     VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
@@ -748,6 +768,7 @@ static void vdagent_chr_class_init(ObjectClass *oc, void *data)
     cc->open             = vdagent_chr_open;
     cc->chr_write        = vdagent_chr_write;
     cc->chr_set_fe_open  = vdagent_chr_set_fe_open;
+    cc->chr_accept_input = vdagent_chr_accept_input;
 }
 
 static const TypeInfo vdagent_chr_type_info = {
-- 
2.31.1



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

* Re: [PATCH v5 4/9] ui/vdagent: core infrastructure
  2021-05-11  9:04     ` Gerd Hoffmann
@ 2021-05-11  9:27       ` Marc-André Lureau
  0 siblings, 0 replies; 14+ messages in thread
From: Marc-André Lureau @ 2021-05-11  9:27 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, QEMU, Markus Armbruster

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

Hi

On Tue, May 11, 2021 at 1:04 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

> > > +/* ------------------------------------------------------------------
> */
> > > +/* 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;
> > > +    }
> > >
> >
> > This looks like it could easily busy loop. Have you thought about fixing
> > this?
>
> Incremental fix [ to be squashed ]
>
>
thanks, a few comments below

take care,
>   Gerd
>
> diff --git a/ui/vdagent.c b/ui/vdagent.c
> index 64213aa25a06..efa98725fb22 100644
> --- a/ui/vdagent.c
> +++ b/ui/vdagent.c
> @@ -3,7 +3,9 @@
>  #include "include/qemu-common.h"
>  #include "chardev/char.h"
>  #include "hw/qdev-core.h"
> +#include "qemu/buffer.h"
>  #include "qemu/option.h"
> +#include "qemu/units.h"
>  #include "ui/clipboard.h"
>  #include "ui/console.h"
>  #include "ui/input.h"
> @@ -16,6 +18,7 @@
>
>  #define VDAGENT_MOUSE_DEFAULT true
>  #define VDAGENT_CLIPBOARD_DEFAULT false
> +#define VDAGENT_BUFFER_LIMIT (1 * MiB)
>
>  struct VDAgentChardev {
>      Chardev parent;
> @@ -32,6 +35,7 @@ struct VDAgentChardev {
>      uint32_t msgsize;
>      uint8_t *xbuf;
>      uint32_t xoff, xsize;
> +    Buffer outbuf;
>
>      /* mouse */
>      DeviceState mouse_dev;
> @@ -124,18 +128,20 @@ static const char *type_name[] = {
>  /* ------------------------------------------------------------------ */
>  /* send messages                                                      */
>
> -static void vdagent_send_buf(VDAgentChardev *vd, void *ptr, uint32_t
> msgsize)
> +static void vdagent_send_buf(VDAgentChardev *vd)
>  {
> -    uint8_t *msgbuf = ptr;
> -    uint32_t len, pos = 0;
> +    uint32_t len;
>
> -    while (pos < msgsize) {
> +    while (!buffer_empty(&vd->outbuf)) {
>          len = qemu_chr_be_can_write(CHARDEV(vd));
> -        if (len > msgsize - pos) {
> -            len = msgsize - pos;
> +        if (len == 0) {
> +            return;
>          }
> -        qemu_chr_be_write(CHARDEV(vd), msgbuf + pos, len);
> -        pos += len;
> +        if (len > vd->outbuf.offset) {
> +            len = vd->outbuf.offset;
> +        }
> +        qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len);
> +        buffer_advance(&vd->outbuf, len);
>      }
>  }
>
> @@ -150,16 +156,22 @@ static void vdagent_send_msg(VDAgentChardev *vd,
> VDAgentMessage *msg)
>
>      msg->protocol = VD_AGENT_PROTOCOL;
>
> +    if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) {
> +        return;
> +    }
>

Silently dropping messages, there might be some bad consequences. At least
I think we should error_report(). Eventually, the caller should be informed
too.


> +
>      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);
> +        buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size);
> +        buffer_append(&vd->outbuf, &chunk, sizeof(chunk));
> +        buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size);
>          msgoff += chunk.size;
>      }
> +    vdagent_send_buf(vd);
>  }
>
>  static void vdagent_send_caps(VDAgentChardev *vd)
> @@ -550,6 +562,7 @@ static void vdagent_chr_open(Chardev *chr,
>
> &vdagent_mouse_handler);
>      }
>
> +    buffer_init(&vd->outbuf, "vdagent-outbuf");
>

Needs a buffer_free(). Move it to object init/finalize ?

     *be_opened = true;
>  }
>
> @@ -702,6 +715,13 @@ static int vdagent_chr_write(Chardev *chr, const
> uint8_t *buf, int len)
>      return ret;
>  }
>
> +static void vdagent_chr_accept_input(Chardev *chr)
> +{
> +    VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
> +
> +    vdagent_send_buf(vd);
> +}
> +
>  static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
>  {
>      VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
> @@ -748,6 +768,7 @@ static void vdagent_chr_class_init(ObjectClass *oc,
> void *data)
>      cc->open             = vdagent_chr_open;
>      cc->chr_write        = vdagent_chr_write;
>      cc->chr_set_fe_open  = vdagent_chr_set_fe_open;
> +    cc->chr_accept_input = vdagent_chr_accept_input;
>  }
>
>  static const TypeInfo vdagent_chr_type_info = {
> --
> 2.31.1
>
>

-- 
Marc-André Lureau

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

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

end of thread, other threads:[~2021-05-11  9:43 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-05  6:08 [PATCH v5 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
2021-05-05  6:08 ` [PATCH v5 1/9] build: add separate spice-protocol config option Gerd Hoffmann
2021-05-05  6:08 ` [PATCH v5 2/9] ui: add clipboard infrastructure Gerd Hoffmann
2021-05-05  6:08 ` [PATCH v5 3/9] ui: add clipboard documentation Gerd Hoffmann
2021-05-05  6:08 ` [PATCH v5 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
2021-05-05  9:54   ` Marc-André Lureau
2021-05-11  9:04     ` Gerd Hoffmann
2021-05-11  9:27       ` Marc-André Lureau
2021-05-05  6:08 ` [PATCH v5 5/9] ui/vdagent: add mouse support Gerd Hoffmann
2021-05-05  6:08 ` [PATCH v5 6/9] ui/vdagent: add clipboard support Gerd Hoffmann
2021-05-05  6:08 ` [PATCH v5 7/9] ui/vnc: " Gerd Hoffmann
2021-05-05  6:09 ` [PATCH v5 8/9] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
2021-05-05  6:09 ` [PATCH v5 9/9] ui/gtk: add clipboard support Gerd Hoffmann
2021-05-05  6:21 ` [PATCH v5 0/9] ui: add vdagent implementation and " no-reply

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.