[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