[Spice-devel] [PATCH vdagent 07/12] Add initial seamless mode support
Jakub Janků
janku.jakub.jj at gmail.com
Tue Aug 8 12:56:57 UTC 2017
From: Ondrej Holy <oholy at redhat.com>
Seamless mode is a way to use guest applications directly on the
client system desktop side-by-side with client applications.
Add code to detect visible rectangle areas for X11 guests.
Set VD_AGENT_CAP_SEAMLESS_MODE capability.
Handle VD_AGENT_SEAMLESS_MODE message. It is used by client to enable/disable
sending of VD_AGENT_SEAMLESS_MODE_LIST messages.
Send periodical VD_AGENT_SEAMLESS_MODE_LIST messages with list of visible
rectangles areas if client enabled this. It is sent with every list change.
https://bugs.freedesktop.org/show_bug.cgi?id=39238
---
Makefile.am | 1 +
src/vdagent/vdagent.c | 3 +
src/vdagent/x11-priv.h | 6 +
src/vdagent/x11-seamless-mode.c | 289 ++++++++++++++++++++++++++++++++++++++++
src/vdagent/x11.c | 41 +++++-
src/vdagent/x11.h | 3 +
src/vdagentd-proto-strings.h | 2 +
src/vdagentd-proto.h | 2 +
src/vdagentd/vdagentd.c | 27 +++-
9 files changed, 367 insertions(+), 7 deletions(-)
create mode 100644 src/vdagent/x11-seamless-mode.c
diff --git a/Makefile.am b/Makefile.am
index 7755f09..80a8ef8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,6 +35,7 @@ src_spice_vdagent_SOURCES = \
src/vdagent/file-xfers.h \
src/vdagent/x11-priv.h \
src/vdagent/x11-randr.c \
+ src/vdagent/x11-seamless-mode.c \
src/vdagent/x11.c \
src/vdagent/x11.h \
src/vdagent/vdagent.c \
diff --git a/src/vdagent/vdagent.c b/src/vdagent/vdagent.c
index 9900303..e2a2017 100644
--- a/src/vdagent/vdagent.c
+++ b/src/vdagent/vdagent.c
@@ -137,6 +137,9 @@ static void daemon_read_complete(struct udscs_connection **connp,
fx_open_dir, debug);
}
break;
+ case VDAGENTD_SEAMLESS_MODE:
+ vdagent_x11_set_seamless_mode(x11, (VDAgentSeamlessMode *)data);
+ break;
default:
syslog(LOG_ERR, "Unknown message from vdagentd type: %d, ignoring",
header->type);
diff --git a/src/vdagent/x11-priv.h b/src/vdagent/x11-priv.h
index 677a44d..c264dd3 100644
--- a/src/vdagent/x11-priv.h
+++ b/src/vdagent/x11-priv.h
@@ -136,6 +136,8 @@ struct vdagent_x11 {
int xrandr_minor;
int has_xinerama;
int dont_send_guest_xorg_res;
+
+ gboolean seamless_mode;
};
extern int (*vdagent_x11_prev_error_handler)(Display *, XErrorEvent *);
@@ -151,5 +153,9 @@ int vdagent_x11_randr_handle_event(struct vdagent_x11 *x11,
void vdagent_x11_set_error_handler(struct vdagent_x11 *x11,
int (*handler)(Display *, XErrorEvent *));
int vdagent_x11_restore_error_handler(struct vdagent_x11 *x11);
+int vdagent_x11_debug_error_handler(Display *display, XErrorEvent *error);
+int vdagent_x11_ignore_bad_window_handler(Display *display, XErrorEvent *error);
+
+void vdagent_x11_seamless_mode_send_list(struct vdagent_x11 *x11);
#endif // VDAGENT_X11_PRIV
diff --git a/src/vdagent/x11-seamless-mode.c b/src/vdagent/x11-seamless-mode.c
new file mode 100644
index 0000000..ccdb8e0
--- /dev/null
+++ b/src/vdagent/x11-seamless-mode.c
@@ -0,0 +1,289 @@
+/* vdagent-x11-seamless-mode.c vdagent seamless mode integration code
+
+ Copyright 2015 Red Hat, Inc.
+
+ Red Hat Authors:
+ Ondrej Holy <oholy at 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 3 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 <glib.h>
+#include <X11/X.h>
+#include <X11/Xproto.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <syslog.h>
+
+#include "vdagentd-proto.h"
+#include "x11.h"
+#include "x11-priv.h"
+
+typedef struct spice_window {
+ gint x;
+ gint y;
+ guint w;
+ guint h;
+} SpiceWindow;
+
+/* Get window property. */
+static gulong
+get_window_property(Display *display, Window window,
+ const gchar *property, Atom type, int format,
+ guchar **data_ret)
+{
+ Atom property_atom, type_ret;
+ gulong nitems_ret, bytes_after_ret;
+ guchar *data = NULL;
+ gint format_ret, rc;
+
+ property_atom = XInternAtom(display, property, TRUE);
+ if (!property_atom)
+ return 0;
+
+ rc = XGetWindowProperty(display, window, property_atom,
+ 0, LONG_MAX, False, type,
+ &type_ret, &format_ret, &nitems_ret,
+ &bytes_after_ret, &data);
+ if (rc == Success && type_ret == type && format_ret == format) {
+ *data_ret = data;
+
+ return nitems_ret;
+ }
+
+ if (data) {
+ syslog(LOG_WARNING, "vdagent-x11-seamless-mode: "
+ "XGetWindowProperty(%s) returned data of unexpected format/type",
+ property);
+ XFree(data);
+ }
+
+ return 0;
+}
+
+/* Get current desktop number, or -1. */
+static gint
+get_current_desktop(Display *display)
+{
+ guchar *data = NULL;
+ Window root;
+
+ root = DefaultRootWindow(display);
+ if (get_window_property(display, root, "_NET_CURRENT_DESKTOP",
+ XA_CARDINAL, 32, &data) == 1) {
+ gulong current;
+
+ current = *(gulong *)data;
+ XFree(data);
+
+ return (gint)current;
+ }
+
+ return -1;
+}
+
+/* Get window desktop number, or -1. */
+static gint
+get_desktop(Display *display, Window window)
+{
+ guchar *data = NULL;
+
+ if (get_window_property(display, window, "_NET_WM_DESKTOP",
+ XA_CARDINAL, 32, &data) == 1) {
+ gulong desktop;
+
+ desktop = *(gulong *)data;
+ XFree(data);
+
+ return (gint)desktop;
+ }
+
+ return -1;
+}
+
+/* Get window type, or None. */
+static Atom
+get_window_type(Display *display, Window window)
+{
+ guchar *data = NULL;
+
+ if (get_window_property(display, window, "_NET_WM_WINDOW_TYPE",
+ XA_ATOM, 32, &data) == 1) {
+ Atom type;
+
+ type = *(Atom *)data;
+ XFree (data);
+
+ return type;
+ }
+
+ return None;
+}
+
+static void
+get_geometry(Display *display, Window window,
+ gint *x, gint *y, guint *w, guint *h)
+{
+ guchar *data = NULL;
+ gulong *extents;
+ gint x_abs, y_abs;
+ Window root, child;
+ guint border, depth;
+ XWindowAttributes attributes;
+
+ XGetGeometry(display, window, &root, x, y, w, h, &border, &depth);
+ XTranslateCoordinates(display, window, root, -border, -border,
+ &x_abs, &y_abs, &child);
+ XGetWindowAttributes(display, window, &attributes);
+
+ /* Change relative to absolute mapping (e.g. gnome-terminal, firefox). */
+ if (x_abs != *x || y_abs != *y) {
+ *x = x_abs - *x + attributes.x;
+ *y = y_abs - *y + attributes.y;
+ }
+
+ /* Remove WM border (e.g. gnome-terminal, firefox). */
+ if (get_window_property(display, window, "_NET_FRAME_EXTENTS",
+ XA_CARDINAL, 32, &data) == 4) {
+ extents = (gulong *)data; /* left, right, top, bottom */
+ *x -= extents[0];
+ *y -= extents[2];
+ *w += extents[0] + extents[1];
+ *h += extents[2] + extents[3];
+
+ XFree(data);
+ }
+
+ /* Remove GTK border (client-side decorations). */
+ if (get_window_property(display, window, "_GTK_FRAME_EXTENTS",
+ XA_CARDINAL, 32, &data) == 4) {
+ extents = (gulong *)data; /* left, right, top, bottom */
+ *x += extents[0];
+ *y += extents[2];
+ *w -= extents[0] + extents[1];
+ *h -= extents[2] + extents[3];
+
+ XFree(data);
+ }
+}
+
+/* Determine whether window is visible. */
+static gboolean
+is_visible(Display *display, Window window)
+{
+ Atom atom, type;
+ XWindowAttributes attributes;
+
+ /* Visible window must have window type specified. */
+ type = get_window_type(display, window);
+ if (type == None)
+ return FALSE;
+
+ /* Window must be on current desktop if it isn't popup menu. */
+ atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE_POPUP_MENU", 0);
+ if (type != atom) {
+ gint current;
+
+ current = get_current_desktop(display);
+ if (get_desktop(display, window) != current)
+ return FALSE;
+ }
+
+ /* Window must be viewable. */
+ XGetWindowAttributes(display, window, &attributes);
+ if (attributes.map_state != IsViewable)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Get list of visible windows. */
+static GList *
+get_window_list(struct vdagent_x11 *x11, Window window)
+{
+ Window root, parent;
+ Window *list;
+ guint n;
+ GList *window_list = NULL;
+
+ vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler);
+
+ if (XQueryTree(x11->display, window, &root, &parent, &list, &n)) {
+ guint i;
+ for (i = 0; i < n; ++i) {
+ SpiceWindow *spice_window;
+
+ if (is_visible(x11->display, list[i])) {
+ spice_window = g_new0(SpiceWindow, 1);
+ get_geometry(x11->display, list[i],
+ &spice_window->x, &spice_window->y,
+ &spice_window->w, &spice_window->h);
+
+ if (vdagent_x11_restore_error_handler(x11) != 0) {
+ vdagent_x11_set_error_handler(x11,
+ vdagent_x11_ignore_bad_window_handler);
+ g_free(spice_window);
+ continue;
+ }
+
+ window_list = g_list_append(window_list, spice_window);
+ }
+
+ window_list = g_list_concat(window_list,
+ get_window_list(x11, list[i]));
+ }
+
+ XFree(list);
+ }
+
+ vdagent_x11_restore_error_handler(x11);
+
+ return window_list;
+}
+
+void
+vdagent_x11_seamless_mode_send_list(struct vdagent_x11 *x11)
+{
+ VDAgentSeamlessModeList *list;
+ GList *window_list, *l;
+ size_t size;
+
+ if (!x11->seamless_mode)
+ return;
+
+ // TODO: Check if it is neccesary to send the list...
+ window_list = get_window_list(x11, DefaultRootWindow(x11->display));
+
+ size = sizeof(VDAgentSeamlessModeList) +
+ sizeof(VDAgentSeamlessModeWindow) * g_list_length(window_list);
+ list = g_malloc0(size);
+
+ for (l = window_list; l != NULL; l = l->next) {
+ SpiceWindow *window;
+
+ window = l->data;
+ list->windows[list->num_of_windows].x = window->x;
+ list->windows[list->num_of_windows].y = window->y;
+ list->windows[list->num_of_windows].w = window->w;
+ list->windows[list->num_of_windows].h = window->h;
+
+ list->num_of_windows++;
+ }
+
+ g_list_free_full(window_list, (GDestroyNotify)g_free);
+
+ udscs_write(x11->vdagentd, VDAGENTD_SEAMLESS_MODE_LIST, 0, 0,
+ (uint8_t *)list, size);
+}
diff --git a/src/vdagent/x11.c b/src/vdagent/x11.c
index 6e47ea1..5441792 100644
--- a/src/vdagent/x11.c
+++ b/src/vdagent/x11.c
@@ -74,7 +74,7 @@ static const char *vdagent_x11_sel_to_str(uint8_t selection) {
}
}
-static int vdagent_x11_debug_error_handler(
+int vdagent_x11_debug_error_handler(
Display *display, XErrorEvent *error)
{
abort();
@@ -82,7 +82,7 @@ static int vdagent_x11_debug_error_handler(
/* With the clipboard we're sometimes dealing with Properties on another apps
Window. which can go away at any time. */
-static int vdagent_x11_ignore_bad_window_handler(
+int vdagent_x11_ignore_bad_window_handler(
Display *display, XErrorEvent *error)
{
if (error->error_code == BadWindow)
@@ -287,6 +287,11 @@ struct vdagent_x11 *vdagent_x11_create(struct udscs_connection *vdagentd,
syslog(LOG_DEBUG, "net_wm_name: \"%s\", has icons: %d",
x11->net_wm_name, vdagent_x11_has_icons_on_desktop(x11));
+ /* Catch all windows changes due to seamless mode. */
+ XSelectInput(x11->display, DefaultRootWindow(x11->display),
+ SubstructureNotifyMask);
+ vdagent_x11_seamless_mode_send_list(x11);
+
/* Flush output buffers and consume any pending events */
vdagent_x11_do_read(x11);
@@ -511,14 +516,28 @@ static void vdagent_x11_handle_event(struct vdagent_x11 *x11, XEvent event)
for (i = 0; i < x11->screen_count; i++)
if (event.xconfigure.window == x11->root_window[i])
break;
- if (i == x11->screen_count)
- break;
+ if (i != x11->screen_count) {
+ vdagent_x11_randr_handle_root_size_change(x11, i,
+ event.xconfigure.width, event.xconfigure.height);
+ }
+
+ vdagent_x11_seamless_mode_send_list(x11);
handled = 1;
- vdagent_x11_randr_handle_root_size_change(x11, i,
- event.xconfigure.width, event.xconfigure.height);
break;
case MappingNotify:
+ case CreateNotify:
+ case CirculateNotify:
+ case DestroyNotify:
+ case GravityNotify:
+ case MapNotify:
+ case ReparentNotify:
+ case UnmapNotify:
+ vdagent_x11_seamless_mode_send_list(x11);
+
+ handled = 1;
+ break;
+ case ClientMessage:
/* These are uninteresting */
handled = 1;
break;
@@ -541,6 +560,9 @@ static void vdagent_x11_handle_event(struct vdagent_x11 *x11, XEvent event)
}
/* Always mark as handled, since we cannot unselect input for property
notifications once we are done with handling the incr transfer. */
+
+ vdagent_x11_seamless_mode_send_list(x11);
+
handled = 1;
break;
case SelectionClear:
@@ -1354,3 +1376,10 @@ int vdagent_x11_has_icons_on_desktop(struct vdagent_x11 *x11)
return 0;
}
+
+void vdagent_x11_set_seamless_mode(struct vdagent_x11 *x11,
+ VDAgentSeamlessMode *msg)
+{
+ x11->seamless_mode = msg->enabled;
+ vdagent_x11_seamless_mode_send_list(x11);
+}
diff --git a/src/vdagent/x11.h b/src/vdagent/x11.h
index 4fd0380..46dcfba 100644
--- a/src/vdagent/x11.h
+++ b/src/vdagent/x11.h
@@ -50,4 +50,7 @@ void vdagent_x11_client_disconnected(struct vdagent_x11 *x11);
int vdagent_x11_has_icons_on_desktop(struct vdagent_x11 *x11);
+void vdagent_x11_set_seamless_mode(struct vdagent_x11 *x11,
+ VDAgentSeamlessMode *msg);
+
#endif
diff --git a/src/vdagentd-proto-strings.h b/src/vdagentd-proto-strings.h
index 6e7bcee..8f8a58b 100644
--- a/src/vdagentd-proto-strings.h
+++ b/src/vdagentd-proto-strings.h
@@ -35,6 +35,8 @@ static const char * const vdagentd_messages[] = {
"file xfer status",
"file xfer data",
"file xfer disable",
+ "seamless mode",
+ "seamless mode list",
"client disconnected",
};
diff --git a/src/vdagentd-proto.h b/src/vdagentd-proto.h
index f72a890..4657a19 100644
--- a/src/vdagentd-proto.h
+++ b/src/vdagentd-proto.h
@@ -43,6 +43,8 @@ enum {
VDAGENTD_FILE_XFER_STATUS,
VDAGENTD_FILE_XFER_DATA,
VDAGENTD_FILE_XFER_DISABLE,
+ VDAGENTD_SEAMLESS_MODE,
+ VDAGENTD_SEAMLESS_MODE_LIST,
VDAGENTD_CLIENT_DISCONNECTED, /* daemon -> client */
VDAGENTD_NO_MESSAGES /* Must always be last */
};
diff --git a/src/vdagentd/vdagentd.c b/src/vdagentd/vdagentd.c
index 7ffb890..14d15b5 100644
--- a/src/vdagentd/vdagentd.c
+++ b/src/vdagentd/vdagentd.c
@@ -130,7 +130,7 @@ static void send_capabilities(struct vdagent_virtio_port *vport,
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_GUEST_LINEEND_LF);
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MAX_CLIPBOARD);
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_AUDIO_VOLUME_SYNC);
- virtio_msg_uint32_to_le((uint8_t *)caps, size, 0);
+ VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SEAMLESS_MODE);
vdagent_virtio_port_write(vport, VDP_CLIENT_PORT,
VD_AGENT_ANNOUNCE_CAPABILITIES, 0,
@@ -370,6 +370,20 @@ static void do_client_file_xfer(struct vdagent_virtio_port *vport,
udscs_write(conn, msg_type, 0, 0, data, message_header->size);
}
+static void do_seamless_mode(struct vdagent_virtio_port *vport,
+ VDAgentMessage *message_header,
+ uint8_t *data)
+{
+ if (active_session_conn == NULL) {
+ syslog(LOG_DEBUG, "Could not find an agent connection belonging to the "
+ "active session, ignoring seamless mode request");
+ return;
+ }
+
+ udscs_write(active_session_conn, VDAGENTD_SEAMLESS_MODE, 0, 0,
+ data, message_header->size);
+}
+
static gsize vdagent_message_min_size[] =
{
-1, /* Does not exist */
@@ -388,6 +402,8 @@ static gsize vdagent_message_min_size[] =
0, /* VD_AGENT_CLIENT_DISCONNECTED */
sizeof(VDAgentMaxClipboard), /* VD_AGENT_MAX_CLIPBOARD */
sizeof(VDAgentAudioVolumeSync), /* VD_AGENT_AUDIO_VOLUME_SYNC */
+ sizeof(VDAgentSeamlessMode), /* VD_AGENT_SEAMLESS_MODE */
+ sizeof(VDAgentSeamlessModeList), /* VD_AGENT_SEAMLESS_MODE_LIST */
};
static void vdagent_message_clipboard_from_le(VDAgentMessage *message_header,
@@ -472,6 +488,7 @@ static gboolean vdagent_message_check_size(const VDAgentMessage *message_header)
case VD_AGENT_CLIPBOARD_GRAB:
case VD_AGENT_AUDIO_VOLUME_SYNC:
case VD_AGENT_ANNOUNCE_CAPABILITIES:
+ case VD_AGENT_SEAMLESS_MODE:
if (message_header->size < min_size) {
syslog(LOG_ERR, "read: invalid message size: %u for message type: %u",
message_header->size, message_header->type);
@@ -553,6 +570,9 @@ static int virtio_port_read_complete(
do_client_volume_sync(vport, port_nr, message_header, vdata);
break;
}
+ case VD_AGENT_SEAMLESS_MODE:
+ do_seamless_mode(vport, message_header, data);
+ break;
default:
g_warn_if_reached();
}
@@ -918,6 +938,11 @@ static void agent_read_complete(struct udscs_connection **connp,
g_hash_table_remove(active_xfers, GUINT_TO_POINTER(status.id));
break;
}
+ case VDAGENTD_SEAMLESS_MODE_LIST:
+ vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
+ VD_AGENT_SEAMLESS_MODE_LIST, 0,
+ data, header->size);
+ break;
default:
syslog(LOG_ERR, "unknown message from vdagent: %u, ignoring",
--
2.13.4
More information about the Spice-devel
mailing list