* [PATCH v6 1/9] build: add separate spice-protocol config option
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 2/9] ui: add clipboard infrastructure Gerd Hoffmann
` (8 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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 0e4233fd8ab4..1c45dd4b8c21 100755
--- a/configure
+++ b/configure
@@ -389,6 +389,7 @@ qom_cast_debug="yes"
trace_backends="log"
trace_file="trace"
spice="$default_feature"
+spice_protocol="auto"
rbd="auto"
smartcard="$default_feature"
u2f="auto"
@@ -1132,7 +1133,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"
;;
@@ -1870,6 +1879,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
@@ -4153,6 +4163,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>
@@ -4161,13 +4184,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
@@ -5836,9 +5859,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 8e16e05c2ade..36c17a5ba921 100644
--- a/meson.build
+++ b/meson.build
@@ -458,11 +458,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] 15+ messages in thread
* [PATCH v6 2/9] ui: add clipboard infrastructure
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 1/9] build: add separate spice-protocol config option Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 3/9] ui: add clipboard documentation Gerd Hoffmann
` (7 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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] 15+ messages in thread
* [PATCH v6 3/9] ui: add clipboard documentation
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 1/9] build: add separate spice-protocol config option Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 2/9] ui: add clipboard infrastructure Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
` (6 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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] 15+ messages in thread
* [PATCH v6 4/9] ui/vdagent: core infrastructure
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
` (2 preceding siblings ...)
2021-05-19 5:39 ` [PATCH v6 3/9] ui: add clipboard documentation Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 9:47 ` Marc-André Lureau
2021-05-19 5:39 ` [PATCH v6 5/9] ui/vdagent: add mouse support Gerd Hoffmann
` (5 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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 | 361 ++++++++++++++++++++++++++++++++++++++++++++++++
qapi/char.json | 17 ++-
ui/meson.build | 1 +
ui/trace-events | 8 ++
4 files changed, 386 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..21e55a41eaba
--- /dev/null
+++ b/ui/vdagent.c
@@ -0,0 +1,361 @@
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "include/qemu-common.h"
+#include "chardev/char.h"
+#include "qemu/buffer.h"
+#include "qemu/units.h"
+#include "trace.h"
+
+#include "qapi/qapi-types-char.h"
+
+#include "spice/vd_agent.h"
+
+#define VDAGENT_BUFFER_LIMIT (1 * MiB)
+
+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;
+ Buffer outbuf;
+};
+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)
+{
+ uint32_t len;
+
+ while (!buffer_empty(&vd->outbuf)) {
+ len = qemu_chr_be_can_write(CHARDEV(vd));
+ if (len == 0) {
+ return;
+ }
+ if (len > vd->outbuf.offset) {
+ len = vd->outbuf.offset;
+ }
+ qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len);
+ buffer_advance(&vd->outbuf, 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;
+
+ if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) {
+ error_report("buffer full, dropping message");
+ return;
+ }
+
+ while (msgoff < msgsize) {
+ chunk.port = VDP_CLIENT_PORT;
+ chunk.size = msgsize - msgoff;
+ if (chunk.size > 1024) {
+ chunk.size = 1024;
+ }
+ 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)
+{
+ 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_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);
+
+ 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;
+ cc->chr_accept_input = vdagent_chr_accept_input;
+}
+
+static void vdagent_chr_init(Object *obj)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
+
+ buffer_init(&vd->outbuf, "vdagent-outbuf");
+}
+
+static void vdagent_chr_fini(Object *obj)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
+
+ buffer_free(&vd->outbuf);
+}
+
+static const TypeInfo vdagent_chr_type_info = {
+ .name = TYPE_CHARDEV_QEMU_VDAGENT,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(VDAgentChardev),
+ .instance_init = vdagent_chr_init,
+ .instance_finalize = vdagent_chr_fini,
+ .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] 15+ messages in thread
* Re: [PATCH v6 4/9] ui/vdagent: core infrastructure
2021-05-19 5:39 ` [PATCH v6 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
@ 2021-05-19 9:47 ` Marc-André Lureau
0 siblings, 0 replies; 15+ messages in thread
From: Marc-André Lureau @ 2021-05-19 9:47 UTC (permalink / raw)
To: Gerd Hoffmann; +Cc: Paolo Bonzini, qemu-devel, Markus Armbruster
[-- Attachment #1: Type: text/plain, Size: 15919 bytes --]
On Wed, May 19, 2021 at 9:40 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>
>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
> ui/vdagent.c | 361 ++++++++++++++++++++++++++++++++++++++++++++++++
> qapi/char.json | 17 ++-
> ui/meson.build | 1 +
> ui/trace-events | 8 ++
> 4 files changed, 386 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..21e55a41eaba
> --- /dev/null
> +++ b/ui/vdagent.c
> @@ -0,0 +1,361 @@
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "include/qemu-common.h"
> +#include "chardev/char.h"
> +#include "qemu/buffer.h"
> +#include "qemu/units.h"
> +#include "trace.h"
> +
> +#include "qapi/qapi-types-char.h"
> +
> +#include "spice/vd_agent.h"
> +
> +#define VDAGENT_BUFFER_LIMIT (1 * MiB)
> +
> +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;
> + Buffer outbuf;
> +};
> +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)
> +{
> + uint32_t len;
> +
> + while (!buffer_empty(&vd->outbuf)) {
> + len = qemu_chr_be_can_write(CHARDEV(vd));
> + if (len == 0) {
> + return;
> + }
> + if (len > vd->outbuf.offset) {
> + len = vd->outbuf.offset;
> + }
> + qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len);
> + buffer_advance(&vd->outbuf, 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;
> +
> + if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) {
> + error_report("buffer full, dropping message");
> + return;
> + }
> +
> + while (msgoff < msgsize) {
> + chunk.port = VDP_CLIENT_PORT;
> + chunk.size = msgsize - msgoff;
> + if (chunk.size > 1024) {
> + chunk.size = 1024;
> + }
> + 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)
> +{
> + 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_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);
> +
> + 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;
> + cc->chr_accept_input = vdagent_chr_accept_input;
> +}
> +
> +static void vdagent_chr_init(Object *obj)
> +{
> + VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
> +
> + buffer_init(&vd->outbuf, "vdagent-outbuf");
> +}
> +
> +static void vdagent_chr_fini(Object *obj)
> +{
> + VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
> +
> + buffer_free(&vd->outbuf);
> +}
> +
> +static const TypeInfo vdagent_chr_type_info = {
> + .name = TYPE_CHARDEV_QEMU_VDAGENT,
> + .parent = TYPE_CHARDEV,
> + .instance_size = sizeof(VDAgentChardev),
> + .instance_init = vdagent_chr_init,
> + .instance_finalize = vdagent_chr_fini,
> + .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
>
>
[-- Attachment #2: Type: text/html, Size: 20004 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v6 5/9] ui/vdagent: add mouse support
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
` (3 preceding siblings ...)
2021-05-19 5:39 ` [PATCH v6 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 12:06 ` Markus Armbruster
2021-05-19 5:39 ` [PATCH v6 6/9] ui/vdagent: add clipboard support Gerd Hoffmann
` (4 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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 | 149 +++++++++++++++++++++++++++++++++++++++++++++++++
qapi/char.json | 4 +-
3 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/chardev/char.c b/chardev/char.c
index a4ebfcc5ac20..52c567e8ff00 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -931,6 +931,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 21e55a41eaba..cf81ab6beb68 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -3,18 +3,27 @@
#include "include/qemu-common.h"
#include "chardev/char.h"
#include "qemu/buffer.h"
+#include "qemu/option.h"
#include "qemu/units.h"
+#include "hw/qdev-core.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_BUFFER_LIMIT (1 * MiB)
+#define VDAGENT_MOUSE_DEFAULT true
struct VDAgentChardev {
Chardev parent;
+ /* config */
+ bool mouse;
+
/* guest vdagent */
uint32_t caps;
VDIChunkHeader chunk;
@@ -24,6 +33,14 @@ struct VDAgentChardev {
uint8_t *xbuf;
uint32_t xoff, xsize;
Buffer outbuf;
+
+ /* 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;
@@ -137,13 +154,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,
+#ifdef VD_AGENT_EBUTTON_MASK
+ [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 */
@@ -152,6 +269,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,
@@ -161,6 +281,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;
}
@@ -184,6 +314,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)
@@ -312,18 +445,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] 15+ messages in thread
* Re: [PATCH v6 5/9] ui/vdagent: add mouse support
2021-05-19 5:39 ` [PATCH v6 5/9] ui/vdagent: add mouse support Gerd Hoffmann
@ 2021-05-19 12:06 ` Markus Armbruster
0 siblings, 0 replies; 15+ messages in thread
From: Markus Armbruster @ 2021-05-19 12:06 UTC (permalink / raw)
To: Gerd Hoffmann; +Cc: Paolo Bonzini, qemu-devel, Marc-André Lureau
Gerd Hoffmann <kraxel@redhat.com> writes:
> 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>
QAPI schema
Acked-by: Markus Armbruster <armbru@redhat.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v6 6/9] ui/vdagent: add clipboard support
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
` (4 preceding siblings ...)
2021-05-19 5:39 ` [PATCH v6 5/9] ui/vdagent: add mouse support Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 9:47 ` Marc-André Lureau
2021-05-19 5:39 ` [PATCH v6 7/9] ui/vnc: " Gerd Hoffmann
` (3 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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 | 293 ++++++++++++++++++++++++++++++++++++++++++++++++
qapi/char.json | 4 +-
ui/trace-events | 2 +
4 files changed, 301 insertions(+), 1 deletion(-)
diff --git a/chardev/char.c b/chardev/char.c
index 52c567e8ff00..d959eec5229c 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -934,6 +934,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 cf81ab6beb68..a253a8fe63a6 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -6,6 +6,7 @@
#include "qemu/option.h"
#include "qemu/units.h"
#include "hw/qdev-core.h"
+#include "ui/clipboard.h"
#include "ui/console.h"
#include "ui/input.h"
#include "trace.h"
@@ -17,12 +18,14 @@
#define VDAGENT_BUFFER_LIMIT (1 * MiB)
#define VDAGENT_MOUSE_DEFAULT true
+#define VDAGENT_CLIPBOARD_DEFAULT false
struct VDAgentChardev {
Chardev parent;
/* config */
bool mouse;
+ bool clipboard;
/* guest vdagent */
uint32_t caps;
@@ -41,6 +44,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;
@@ -96,6 +104,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]) : "???")
@@ -161,6 +187,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);
}
@@ -261,6 +291,244 @@ 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 */
@@ -286,6 +554,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);
@@ -317,6 +590,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)
@@ -327,6 +606,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;
}
@@ -448,6 +735,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;
}
@@ -464,6 +755,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] 15+ messages in thread
* Re: [PATCH v6 6/9] ui/vdagent: add clipboard support
2021-05-19 5:39 ` [PATCH v6 6/9] ui/vdagent: add clipboard support Gerd Hoffmann
@ 2021-05-19 9:47 ` Marc-André Lureau
0 siblings, 0 replies; 15+ messages in thread
From: Marc-André Lureau @ 2021-05-19 9:47 UTC (permalink / raw)
To: Gerd Hoffmann; +Cc: Paolo Bonzini, qemu-devel, Markus Armbruster
[-- Attachment #1: Type: text/plain, Size: 15406 bytes --]
On Wed, May 19, 2021 at 9:40 AM Gerd Hoffmann <kraxel@redhat.com> wrote:
> 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>
>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
> chardev/char.c | 3 +
> ui/vdagent.c | 293 ++++++++++++++++++++++++++++++++++++++++++++++++
> qapi/char.json | 4 +-
> ui/trace-events | 2 +
> 4 files changed, 301 insertions(+), 1 deletion(-)
>
> diff --git a/chardev/char.c b/chardev/char.c
> index 52c567e8ff00..d959eec5229c 100644
> --- a/chardev/char.c
> +++ b/chardev/char.c
> @@ -934,6 +934,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 cf81ab6beb68..a253a8fe63a6 100644
> --- a/ui/vdagent.c
> +++ b/ui/vdagent.c
> @@ -6,6 +6,7 @@
> #include "qemu/option.h"
> #include "qemu/units.h"
> #include "hw/qdev-core.h"
> +#include "ui/clipboard.h"
> #include "ui/console.h"
> #include "ui/input.h"
> #include "trace.h"
> @@ -17,12 +18,14 @@
>
> #define VDAGENT_BUFFER_LIMIT (1 * MiB)
> #define VDAGENT_MOUSE_DEFAULT true
> +#define VDAGENT_CLIPBOARD_DEFAULT false
>
> struct VDAgentChardev {
> Chardev parent;
>
> /* config */
> bool mouse;
> + bool clipboard;
>
> /* guest vdagent */
> uint32_t caps;
> @@ -41,6 +44,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;
>
> @@ -96,6 +104,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]) : "???")
>
> @@ -161,6 +187,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);
> }
> @@ -261,6 +291,244 @@ 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 */
>
> @@ -286,6 +554,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);
> @@ -317,6 +590,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)
> @@ -327,6 +606,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;
> }
> @@ -448,6 +735,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;
> }
>
> @@ -464,6 +755,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
>
>
[-- Attachment #2: Type: text/html, Size: 19181 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v6 7/9] ui/vnc: clipboard support
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
` (5 preceding siblings ...)
2021-05-19 5:39 ` [PATCH v6 6/9] ui/vdagent: add clipboard support Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 8/9] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
` (2 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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 | 21 ++-
ui/meson.build | 1 +
4 files changed, 363 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..85bb96338729 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,12 @@ 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] 15+ messages in thread
* [PATCH v6 8/9] ui/gtk: move struct GtkDisplayState to ui/gtk.h
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
` (6 preceding siblings ...)
2021-05-19 5:39 ` [PATCH v6 7/9] ui/vnc: " Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 5:39 ` [PATCH v6 9/9] ui/gtk: add clipboard support Gerd Hoffmann
2021-05-19 5:54 ` [PATCH v6 0/9] ui: add vdagent implementation and " no-reply
9 siblings, 0 replies; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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] 15+ messages in thread
* [PATCH v6 9/9] ui/gtk: add clipboard support
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
` (7 preceding siblings ...)
2021-05-19 5:39 ` [PATCH v6 8/9] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
@ 2021-05-19 5:39 ` Gerd Hoffmann
2021-05-19 9:46 ` Marc-André Lureau
2021-05-19 5:54 ` [PATCH v6 0/9] ui: add vdagent implementation and " no-reply
9 siblings, 1 reply; 15+ messages in thread
From: Gerd Hoffmann @ 2021-05-19 5:39 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Gerd Hoffmann, Marc-André Lureau, 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] 15+ messages in thread
* Re: [PATCH v6 9/9] ui/gtk: add clipboard support
2021-05-19 5:39 ` [PATCH v6 9/9] ui/gtk: add clipboard support Gerd Hoffmann
@ 2021-05-19 9:46 ` Marc-André Lureau
0 siblings, 0 replies; 15+ messages in thread
From: Marc-André Lureau @ 2021-05-19 9:46 UTC (permalink / raw)
To: Gerd Hoffmann; +Cc: Paolo Bonzini, qemu-devel, Markus Armbruster
[-- Attachment #1: Type: text/plain, Size: 9948 bytes --]
On Wed, May 19, 2021 at 9:40 AM Gerd Hoffmann <kraxel@redhat.com> wrote:
> This patch adds clipboard support to the qemu gtk ui.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
>
Reviewed-by: Marc-André Lureau <marcandre.lureau@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
>
>
[-- Attachment #2: Type: text/html, Size: 12434 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v6 0/9] ui: add vdagent implementation and clipboard support.
2021-05-19 5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
` (8 preceding siblings ...)
2021-05-19 5:39 ` [PATCH v6 9/9] ui/gtk: add clipboard support Gerd Hoffmann
@ 2021-05-19 5:54 ` no-reply
9 siblings, 0 replies; 15+ messages in thread
From: no-reply @ 2021-05-19 5:54 UTC (permalink / raw)
To: kraxel; +Cc: pbonzini, armbru, qemu-devel, marcandre.lureau, kraxel
Patchew URL: https://patchew.org/QEMU/20210519053940.1888907-1-kraxel@redhat.com/
Hi,
This series seems to have some coding style problems. See output below for
more information:
Type: series
Message-id: 20210519053940.1888907-1-kraxel@redhat.com
Subject: [PATCH v6 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
- [tag update] patchew/20210518131542.2941207-1-pbonzini@redhat.com -> patchew/20210518131542.2941207-1-pbonzini@redhat.com
* [new tag] patchew/20210519053940.1888907-1-kraxel@redhat.com -> patchew/20210519053940.1888907-1-kraxel@redhat.com
Switched to a new branch 'test'
f38e1b9 ui/gtk: add clipboard support
b5112a9 ui/gtk: move struct GtkDisplayState to ui/gtk.h
8feb571 ui/vnc: clipboard support
97c5587 ui/vdagent: add clipboard support
76b41e6 ui/vdagent: add mouse support
64a57ad ui/vdagent: core infrastructure
5b03384 ui: add clipboard documentation
a482369 ui: add clipboard infrastructure
fa3c141 build: add separate spice-protocol config option
=== OUTPUT BEGIN ===
1/9 Checking commit fa3c14163bf1 (build: add separate spice-protocol config option)
2/9 Checking commit a48236937e4a (ui: add clipboard infrastructure)
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 5b03384c273a (ui: add clipboard documentation)
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 64a57ad8e2a2 (ui/vdagent: core infrastructure)
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
#160: FILE: ui/vdagent.c:54:
+#if 0
WARNING: line over 80 characters
#162: FILE: ui/vdagent.c:56:
+ [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
ERROR: if this code is redundant consider removing it
#183: FILE: ui/vdagent.c:77:
+#if 0
WARNING: line over 80 characters
#244: FILE: ui/vdagent.c:138:
+ sizeof(VDAgentAnnounceCapabilities) +
total: 2 errors, 3 warnings, 412 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 76b41e6999f0 (ui/vdagent: add mouse support)
6/9 Checking commit 97c5587b9065 (ui/vdagent: add clipboard support)
ERROR: if this code is redundant consider removing it
#121: FILE: ui/vdagent.c:120:
+#if 0
WARNING: line over 80 characters
#441: FILE: ui/vdagent.c:759:
+ cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT);
total: 1 errors, 1 warnings, 394 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 8feb57182d5c (ui/vnc: clipboard support)
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);
WARNING: line over 80 characters
#501: FILE: ui/vnc.h:643:
+void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data);
total: 0 errors, 5 warnings, 451 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 b5112a9b2973 (ui/gtk: move struct GtkDisplayState to ui/gtk.h)
9/9 Checking commit f38e1b93466f (ui/gtk: add clipboard support)
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/20210519053940.1888907-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] 15+ messages in thread