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:
Peter Maydell
2021-05-21 14:27:56 +01:00
18 changed files with 1800 additions and 68 deletions
+6
View File
@@ -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",
Vendored
+32 -4
View File
@@ -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
+1
View File
@@ -36,6 +36,7 @@ Contents:
multi-thread-tcg
tcg-plugins
bitops
ui
reset
s390-dasd-ipl
clocks
+8
View File
@@ -0,0 +1,8 @@
=================
Qemu UI subsystem
=================
Qemu Clipboard
--------------
.. kernel-doc:: include/ui/clipboard.h
+193
View 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 */
+67
View File
@@ -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 */
+4
View File
@@ -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
+20 -1
View File
@@ -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
View 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
View 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);
}
+1 -55
View File
@@ -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)
+4 -1
View File
@@ -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'))
+4
View File
@@ -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:
+10
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+323
View 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);
}
+16 -7
View File
@@ -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
View File
@@ -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 */