From: Ondrej Holy <oholy@xxxxxxxxxx> 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@xxxxxxxxxx> + + 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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel