diff options
author | Ondrej Holy <oholy@redhat.com> | 2015-10-07 15:16:55 +0200 |
---|---|---|
committer | Lukas Venhoda <lvenhoda@redhat.com> | 2016-08-09 14:41:23 +0200 |
commit | 1fefcb47ac469f2cc882d5ddd18f5c69410650b6 (patch) | |
tree | 7463dc1854a653e4ff7ac31b55d7c92fed4683c4 | |
parent | 4d17241a7509948864ba39f696421283e00aee69 (diff) |
Add initial seamless mode supportfedpkg
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.
Sent 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
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | src/vdagent-x11-priv.h | 6 | ||||
-rw-r--r-- | src/vdagent-x11-seamless-mode.c | 289 | ||||
-rw-r--r-- | src/vdagent-x11.c | 41 | ||||
-rw-r--r-- | src/vdagent-x11.h | 3 | ||||
-rw-r--r-- | src/vdagent.c | 4 | ||||
-rw-r--r-- | src/vdagentd-proto-strings.h | 2 | ||||
-rw-r--r-- | src/vdagentd-proto.h | 2 | ||||
-rw-r--r-- | src/vdagentd.c | 26 |
9 files changed, 368 insertions, 6 deletions
diff --git a/Makefile.am b/Makefile.am index 680ef83..04a0e1e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,6 +9,7 @@ src_spice_vdagent_LDADD = $(X_LIBS) $(SPICE_LIBS) $(GLIB2_LIBS) $(ALSA_LIBS) src_spice_vdagent_SOURCES = src/vdagent.c \ src/vdagent-x11.c \ src/vdagent-x11-randr.c \ + src/vdagent-x11-seamless-mode.c \ src/vdagent-file-xfers.c \ src/vdagent-audio.c \ src/udscs.c diff --git a/src/vdagent-x11-priv.h b/src/vdagent-x11-priv.h index d60cc07..fd48e4c 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..1d5abaf --- /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@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 "vdagent-x11.h" +#include "vdagent-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 da27602..a1bc930 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 e67701e..e15616d 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/vdagent.c b/src/vdagent.c index a3cdd9b..2ef3eb8 100644 --- a/src/vdagent.c +++ b/src/vdagent.c @@ -147,6 +147,10 @@ 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); + free(data); + break; default: syslog(LOG_ERR, "Unknown message from vdagentd type: %d, ignoring", header->type); 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 9815488..fdea671 100644 --- a/src/vdagentd-proto.h +++ b/src/vdagentd-proto.h @@ -41,6 +41,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.c b/src/vdagentd.c index a699681..7e820b6 100644 --- a/src/vdagentd.c +++ b/src/vdagentd.c @@ -102,6 +102,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); + VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SEAMLESS_MODE); vdagent_virtio_port_write(vport, VDP_CLIENT_PORT, VD_AGENT_ANNOUNCE_CAPABILITIES, 0, @@ -316,6 +317,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 int virtio_port_read_complete( struct vdagent_virtio_port *vport, int port_nr, @@ -409,6 +424,12 @@ static int virtio_port_read_complete( do_client_volume_sync(vport, port_nr, message_header, (VDAgentAudioVolumeSync *)data); break; + case VD_AGENT_SEAMLESS_MODE: + if (message_header->size < sizeof(VDAgentSeamlessMode)) + goto size_error; + + do_seamless_mode(vport, message_header, data); + break; default: syslog(LOG_WARNING, "unknown message type %d, ignoring", message_header->type); @@ -778,6 +799,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", |