ui: add cut+paste support.
ui: bugfixes for spice and vnc. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmCnrKYACgkQTLbY7tPo cTi8Hg/+JsIRFeX66cNcZ+VBqJwRQYD8LvZg0vMMN0usAEYqPQt8EIZ42JFh/TF6 oxZsQtGkNXGt5oeIN72Ve28yJBHAuGyc/3zZiV+2pDZqOzir+PxNjr0430MU2ZFL X/LWwYh6Jar8h12Qh0SYOVCXbGro1ABe3CoAXC156/XGSplxBapfgoQrnYd9pwMT aa6+qm1/xD7bJaN7XT2nPZ1OfeVzQr6lJcccg2jtTZtVKlR6lbXAZW8oAy2FhlyL cVLDcUqs5OoZeM3XIoSt33o31le7nOHAFMeL7YDlEFbSVsZDDgwHEmPz7ijLaW8G gv7HKoa3LTVde8BGr5ABz4Ffg3DL0pEYzw6y5X84sLj8TjM1vhAD1Zid5X5rjvJj Zfge/SnLXmSDJFh2amCQu/5JVbAMwvL2hxyO8WDZw1aC4swMfUcgMxShLUV+FUD/ DheUf/778QJhkZXGEyH3iZMcMRhY0d1D3K1RxrJXWHg1mImCzWVfkeqqkf1+FPor 9gE7HV9Cffry+g51Hd+UjV50ss8CIYzniVGQjDmAlDh05nDATAcjDPqZfntvsqli uIM3NP1l/nzPXR7FZhYfIbI9+yVbkNiITz9wSRIAIFT7EB13QkxLq0T/l35t7pd2 R03BZIdxPwxxDo6a++w7LeAZcqompqvs3gmbdEmP6GvP7NOmob8= =i3HO -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kraxel/tags/ui-20210521-pull-request' into staging ui: add cut+paste support. ui: bugfixes for spice and vnc. # gpg: Signature made Fri 21 May 2021 13:50:46 BST # gpg: using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138 # gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full] # gpg: aka "Gerd Hoffmann <gerd@kraxel.org>" [full] # gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full] # Primary key fingerprint: A032 8CFF B93A 17A7 9901 FE7D 4CB6 D8EE D3E8 7138 * remotes/kraxel/tags/ui-20210521-pull-request: ui/gtk: add clipboard support ui/gtk: move struct GtkDisplayState to ui/gtk.h ui/vnc: clipboard support ui/vdagent: add clipboard support ui/vdagent: add mouse support ui/vdagent: core infrastructure ui: add clipboard documentation ui: add clipboard infrastructure build: add separate spice-protocol config option ui/spice-display: check NULL pointer in interface_release_resource() vnc: spelling fix (enable->enabled) Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
3bbaed2cd0
@ -931,6 +931,12 @@ QemuOptsList qemu_chardev_opts = {
|
||||
},{
|
||||
.name = "logappend",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
},{
|
||||
.name = "mouse",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
},{
|
||||
.name = "clipboard",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
#ifdef CONFIG_LINUX
|
||||
},{
|
||||
.name = "tight",
|
||||
|
36
configure
vendored
36
configure
vendored
@ -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
|
||||
|
||||
|
@ -36,6 +36,7 @@ Contents:
|
||||
multi-thread-tcg
|
||||
tcg-plugins
|
||||
bitops
|
||||
ui
|
||||
reset
|
||||
s390-dasd-ipl
|
||||
clocks
|
||||
|
8
docs/devel/ui.rst
Normal file
8
docs/devel/ui.rst
Normal file
@ -0,0 +1,8 @@
|
||||
=================
|
||||
Qemu UI subsystem
|
||||
=================
|
||||
|
||||
Qemu Clipboard
|
||||
--------------
|
||||
|
||||
.. kernel-doc:: include/ui/clipboard.h
|
193
include/ui/clipboard.h
Normal file
193
include/ui/clipboard.h
Normal file
@ -0,0 +1,193 @@
|
||||
#ifndef QEMU_CLIPBOARD_H
|
||||
#define QEMU_CLIPBOARD_H
|
||||
|
||||
#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,
|
||||
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,
|
||||
QEMU_CLIPBOARD_SELECTION_SECONDARY,
|
||||
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;
|
||||
void (*request)(QemuClipboardInfo *info,
|
||||
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;
|
||||
QemuClipboardSelection selection;
|
||||
struct {
|
||||
bool available;
|
||||
bool requested;
|
||||
size_t size;
|
||||
void *data;
|
||||
} 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,
|
||||
uint32_t size,
|
||||
void *data,
|
||||
bool update);
|
||||
|
||||
#endif /* QEMU_CLIPBOARD_H */
|
@ -18,12 +18,16 @@
|
||||
#include <gdk/gdkwayland.h>
|
||||
#endif
|
||||
|
||||
#include "ui/clipboard.h"
|
||||
#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 +87,66 @@ 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;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
extern bool gtk_use_gl_area;
|
||||
|
||||
/* ui/gtk.c */
|
||||
@ -150,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 */
|
||||
|
@ -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
|
||||
|
@ -390,12 +390,29 @@
|
||||
'data': { '*size': 'int' },
|
||||
'base': 'ChardevCommon' }
|
||||
|
||||
##
|
||||
# @ChardevQemuVDAgent:
|
||||
#
|
||||
# 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',
|
||||
'*clipboard': 'bool' },
|
||||
'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 +434,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
|
||||
|
92
ui/clipboard.c
Normal file
92
ui/clipboard.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
192
ui/gtk-clipboard.c
Normal file
192
ui/gtk-clipboard.c
Normal file
@ -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);
|
||||
}
|
56
ui/gtk.c
56
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;
|
||||
@ -2322,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)
|
||||
|
@ -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',
|
||||
@ -13,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'))
|
||||
@ -28,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'))
|
||||
@ -62,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'))
|
||||
|
@ -561,6 +561,10 @@ static void interface_release_resource(QXLInstance *sin,
|
||||
SimpleSpiceCursor *cursor;
|
||||
QXLCommandExt *ext;
|
||||
|
||||
if (!rext.info) {
|
||||
return;
|
||||
}
|
||||
|
||||
ext = (void *)(intptr_t)(rext.info->id);
|
||||
switch (ext->cmd.type) {
|
||||
case QXL_CMD_DRAW:
|
||||
|
@ -124,3 +124,13 @@ 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"
|
||||
vdagent_cb_grab_selection(const char *name) "selection %s"
|
||||
vdagent_cb_grab_type(const char *name) "type %s"
|
||||
|
803
ui/vdagent.c
Normal file
803
ui/vdagent.c
Normal file
@ -0,0 +1,803 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#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/clipboard.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
|
||||
#define VDAGENT_CLIPBOARD_DEFAULT false
|
||||
|
||||
struct VDAgentChardev {
|
||||
Chardev parent;
|
||||
|
||||
/* config */
|
||||
bool mouse;
|
||||
bool clipboard;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* mouse */
|
||||
DeviceState mouse_dev;
|
||||
uint32_t mouse_x;
|
||||
uint32_t mouse_y;
|
||||
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;
|
||||
|
||||
#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
|
||||
};
|
||||
|
||||
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]) : "???")
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 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));
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 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,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 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 */
|
||||
|
||||
static void vdagent_chr_open(Chardev *chr,
|
||||
ChardevBackend *backend,
|
||||
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,
|
||||
* so we have to byteswap everything on BE hosts.
|
||||
*/
|
||||
error_setg(errp, "vdagent is not supported on bigendian hosts");
|
||||
return;
|
||||
#endif
|
||||
|
||||
vd->mouse = VDAGENT_MOUSE_DEFAULT;
|
||||
if (cfg->has_mouse) {
|
||||
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);
|
||||
}
|
||||
|
||||
*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);
|
||||
}
|
||||
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)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
cfg->has_clipboard = true;
|
||||
cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_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;
|
||||
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);
|
323
ui/vnc-clipboard.c
Normal file
323
ui/vnc-clipboard.c
Normal file
@ -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);
|
||||
}
|
23
ui/vnc.c
23
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"
|
||||
@ -596,7 +597,7 @@ bool vnc_display_reload_certs(const char *id, Error **errp)
|
||||
}
|
||||
|
||||
if (!vd->tlscreds) {
|
||||
error_setg(errp, "vnc tls is not enable");
|
||||
error_setg(errp, "vnc tls is not enabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -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)) {
|
||||
|
24
ui/vnc.h
24
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 */
|
||||
|
Loading…
Reference in New Issue
Block a user