summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOndrej Holy <oholy@redhat.com>2015-09-30 18:00:29 +0200
committerOndrej Holy <oholy@redhat.com>2015-11-04 16:46:32 +0100
commit5cd0129bf2aad3b9eec70ec8303439c95f60d619 (patch)
tree778fbddee4e72fa2063f845f669367953dc16062
parent75253b7a8fb8fa59c317abeb01b4fa3981597d9e (diff)
Add initial seamless mode supportseamless-mode
Seamless mode is a way to use guest applications directly on the client system desktop side-by-side with client applications. Add toggle button to enable/disable seamless mode in spicy. If seamless mode is enabled, client receive list of visible areas periodicaly. Spice widget draw only visible areas (rest is transparent). Spicy hide window decorations and show only maximized spice widget. Spicy also set window shape to match visible areas. Window shape is slightly bigger then visible areas to avoid losing focus when resizing. https://bugs.freedesktop.org/show_bug.cgi?id=39238
-rw-r--r--src/Makefile.am2
-rw-r--r--src/channel-main.c129
-rw-r--r--src/channel-main.h4
-rw-r--r--src/map-file2
-rw-r--r--src/spice-glib-sym-file2
-rw-r--r--src/spice-session.c1
-rw-r--r--src/spice-widget-cairo.c17
-rw-r--r--src/spicy.c153
8 files changed, 308 insertions, 2 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index cf02198..02090fd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -646,7 +646,7 @@ gtk_introspection_files = \
$(NULL)
SpiceClientGLib-2.0.gir: libspice-client-glib-2.0.la
-SpiceClientGLib_2_0_gir_INCLUDES = GObject-2.0 Gio-2.0
+SpiceClientGLib_2_0_gir_INCLUDES = GObject-2.0 Gio-2.0 Gdk-3.0
SpiceClientGLib_2_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS)
SpiceClientGLib_2_0_gir_LIBS = libspice-client-glib-2.0.la
SpiceClientGLib_2_0_gir_FILES = $(glib_introspection_files)
diff --git a/src/channel-main.c b/src/channel-main.c
index 2af9e64..4361c86 100644
--- a/src/channel-main.c
+++ b/src/channel-main.c
@@ -21,6 +21,7 @@
#include <spice/vd_agent.h>
#include <common/rect.h>
#include <glib/gstdio.h>
+#include <gdk/gdk.h>
#include "glib-compat.h"
#include "spice-client.h"
@@ -121,6 +122,10 @@ struct _SpiceMainChannelPrivate {
gboolean agent_volume_playback_sync;
gboolean agent_volume_record_sync;
GCancellable *cancellable_volume_info;
+
+ GMutex seamless_mode_lock;
+ GList *seamless_mode_list;
+ gboolean seamless_mode;
};
struct spice_migrate {
@@ -153,6 +158,8 @@ enum {
PROP_DISABLE_DISPLAY_POSITION,
PROP_DISABLE_DISPLAY_ALIGN,
PROP_MAX_CLIPBOARD,
+ PROP_SEAMLESS_MODE,
+ PROP_SEAMLESS_MODE_LIST,
};
/* Signals */
@@ -200,6 +207,8 @@ static const char *agent_msg_types[] = {
[ VD_AGENT_CLIPBOARD_REQUEST ] = "clipboard request",
[ VD_AGENT_CLIPBOARD_RELEASE ] = "clipboard release",
[ VD_AGENT_AUDIO_VOLUME_SYNC ] = "volume-sync",
+ [ VD_AGENT_SEAMLESS_MODE ] = "seamless mode",
+ [ VD_AGENT_SEAMLESS_MODE_LIST ] = "seamless mode list",
};
static const char *agent_caps[] = {
@@ -215,6 +224,7 @@ static const char *agent_caps[] = {
[ VD_AGENT_CAP_GUEST_LINEEND_CRLF ] = "line-end crlf",
[ VD_AGENT_CAP_MAX_CLIPBOARD ] = "max-clipboard",
[ VD_AGENT_CAP_AUDIO_VOLUME_SYNC ] = "volume-sync",
+ [ VD_AGENT_CAP_SEAMLESS_MODE ] = "seamless mode",
[ VD_AGENT_CAP_MONITORS_CONFIG_POSITION ] = "monitors config position",
};
#define NAME(_a, _i) ((_i) < SPICE_N_ELEMENTS(_a) ? (_a[(_i)] ?: "?") : "?")
@@ -247,6 +257,7 @@ static void spice_main_channel_init(SpiceMainChannel *channel)
c->agent_msg_queue = g_queue_new();
c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
c->cancellable_volume_info = g_cancellable_new();
+ g_mutex_init(&c->seamless_mode_lock);
spice_main_channel_reset_capabilties(SPICE_CHANNEL(channel));
}
@@ -300,6 +311,12 @@ static void spice_main_get_property(GObject *object,
case PROP_MAX_CLIPBOARD:
g_value_set_int(value, spice_main_get_max_clipboard(self));
break;
+ case PROP_SEAMLESS_MODE:
+ g_value_set_boolean(value, c->seamless_mode);
+ break;
+ case PROP_SEAMLESS_MODE_LIST:
+ g_value_set_pointer(value, spice_main_get_seamless_mode_list(self));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -337,6 +354,9 @@ static void spice_main_set_property(GObject *gobject, guint prop_id,
case PROP_MAX_CLIPBOARD:
spice_main_set_max_clipboard(self, g_value_get_int(value));
break;
+ case PROP_SEAMLESS_MODE:
+ spice_main_set_seamless_mode(self, g_value_get_boolean(value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
break;
@@ -365,6 +385,10 @@ static void spice_main_channel_dispose(GObject *obj)
g_cancellable_cancel(c->cancellable_volume_info);
g_clear_object(&c->cancellable_volume_info);
+ g_mutex_clear(&c->seamless_mode_lock);
+ g_list_free_full(c->seamless_mode_list, g_free);
+ c->seamless_mode_list = NULL;
+
if (G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose)
G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose(obj);
}
@@ -597,6 +621,23 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property
+ (gobject_class, PROP_SEAMLESS_MODE,
+ g_param_spec_boolean("seamless-mode",
+ "Seamless mode",
+ "Seamless mode",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property
+ (gobject_class, PROP_SEAMLESS_MODE_LIST,
+ g_param_spec_pointer ("seamless-mode-list",
+ "Seamless mode list",
+ "Seamless mode window list",
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
/* TODO use notify instead */
/**
* SpiceMainChannel::main-mouse-update:
@@ -1282,6 +1323,7 @@ static void agent_announce_caps(SpiceMainChannel *channel)
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG);
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
+ VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SEAMLESS_MODE);
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG_POSITION);
agent_msg_queue(channel, VD_AGENT_ANNOUNCE_CAPABILITIES, size, caps);
@@ -2054,6 +2096,33 @@ static void main_agent_handle_msg(SpiceChannel *channel,
case VD_AGENT_FILE_XFER_STATUS:
file_xfer_handle_status(self, payload);
break;
+ case VD_AGENT_SEAMLESS_MODE_LIST:
+ {
+ VDAgentSeamlessModeList *list = payload;
+ int i;
+
+ g_mutex_lock(&self->priv->seamless_mode_lock);
+
+ g_list_free_full(c->seamless_mode_list, g_free);
+ c->seamless_mode_list = NULL;
+
+ for (i = 0; i < list->num_of_windows; i++) {
+ GdkRectangle *r;
+
+ r = g_new0(GdkRectangle, 1);
+ r->x = list->windows[i].x;
+ r->y = list->windows[i].y;
+ r->width = list->windows[i].w;
+ r->height = list->windows[i].h;
+
+ c->seamless_mode_list = g_list_append(c->seamless_mode_list, r);
+ }
+
+ g_mutex_unlock(&self->priv->seamless_mode_lock);
+
+ g_coroutine_object_notify(G_OBJECT(self), "seamless-mode-list");
+ break;
+ }
default:
g_warning("unhandled agent message type: %u (%s), size %u",
msg->type, NAME(agent_msg_types, msg->type), msg->size);
@@ -3088,3 +3157,63 @@ gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
return g_simple_async_result_get_op_res_gboolean(simple);
}
+
+/**
+ * spice_main_set_seamless_mode:
+ * @channel: a #SpiceMainChannel
+ * @enabled: whether seamless mode is enabled
+ *
+ * Sent message to agent to enable/disable seamless mode list updates.
+ **/
+void spice_main_set_seamless_mode(SpiceMainChannel *channel,
+ gboolean enabled)
+{
+ SpiceMainChannelPrivate *c = channel->priv;
+ VDAgentSeamlessMode msg;
+
+ g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+ g_return_if_fail(spice_main_get_seamless_mode_supported(channel));
+
+ if (c->seamless_mode == enabled)
+ return;
+
+ c->seamless_mode = msg.enabled = enabled;
+ agent_msg_queue(channel, VD_AGENT_SEAMLESS_MODE, sizeof(msg), &msg);
+ spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+/**
+ * spice_main_get_seamless_mode_supported:
+ * @channel: a #SpiceMainChannel
+ *
+ * Returns: %TRUE if seamless mode is supported by the agent.
+ **/
+gboolean spice_main_get_seamless_mode_supported(SpiceMainChannel *channel)
+{
+ g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
+
+ return test_agent_cap(channel, VD_AGENT_CAP_SEAMLESS_MODE);
+}
+
+/**
+ * spice_main_get_seamless_mode_list:
+ * @channel: a #SpiceMainChannel
+ *
+ * Seamless mode has to be enabled using spice_main_set_seamless_mode() to get
+ * updated list of visible areas.
+ *
+ * Returns: (transfer full) (element-type GdkRectangle): a newly allocated
+ * list of GdkRectangle structs.
+ **/
+GList *spice_main_get_seamless_mode_list(SpiceMainChannel *channel)
+{
+ GList *list;
+
+ g_mutex_lock(&channel->priv->seamless_mode_lock);
+ list = g_list_copy_deep(channel->priv->seamless_mode_list,
+ (GCopyFunc)g_memdup,
+ (gpointer)sizeof(GdkRectangle));
+ g_mutex_unlock(&channel->priv->seamless_mode_lock);
+
+ return list;
+}
diff --git a/src/channel-main.h b/src/channel-main.h
index 86bb46b..1a083bc 100644
--- a/src/channel-main.h
+++ b/src/channel-main.h
@@ -105,6 +105,10 @@ SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_request)
void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type);
#endif
+void spice_main_set_seamless_mode(SpiceMainChannel *channel, gboolean enabled);
+gboolean spice_main_get_seamless_mode_supported(SpiceMainChannel *channel);
+GList *spice_main_get_seamless_mode_list(SpiceMainChannel *channel);
+
G_END_DECLS
#endif /* __SPICE_CLIENT_MAIN_CHANNEL_H__ */
diff --git a/src/map-file b/src/map-file
index a9abc61..1692ff3 100644
--- a/src/map-file
+++ b/src/map-file
@@ -67,6 +67,8 @@ spice_main_clipboard_selection_release;
spice_main_clipboard_selection_request;
spice_main_file_copy_async;
spice_main_file_copy_finish;
+spice_main_get_seamless_mode_list;
+spice_main_get_seamless_mode_supported;
spice_main_send_monitor_config;
spice_main_set_display;
spice_main_set_display_enabled;
diff --git a/src/spice-glib-sym-file b/src/spice-glib-sym-file
index 1d62716..f2f0591 100644
--- a/src/spice-glib-sym-file
+++ b/src/spice-glib-sym-file
@@ -44,6 +44,8 @@ spice_main_clipboard_selection_release
spice_main_clipboard_selection_request
spice_main_file_copy_async
spice_main_file_copy_finish
+spice_main_get_seamless_mode_list
+spice_main_get_seamless_mode_supported
spice_main_send_monitor_config
spice_main_set_display
spice_main_set_display_enabled
diff --git a/src/spice-session.c b/src/spice-session.c
index e1d9997..ce62d0a 100644
--- a/src/spice-session.c
+++ b/src/spice-session.c
@@ -22,6 +22,7 @@
#ifdef G_OS_UNIX
#include <gio/gunixsocketaddress.h>
#endif
+#include <spice/vd_agent.h>
#include "common/ring.h"
#include "spice-client.h"
diff --git a/src/spice-widget-cairo.c b/src/spice-widget-cairo.c
index 96af076..6f3cf16 100644
--- a/src/spice-widget-cairo.c
+++ b/src/spice-widget-cairo.c
@@ -104,12 +104,27 @@ void spicex_draw_event(SpiceDisplay *display, cairo_t *cr)
/* Need to set a real solid color, because the default is usually
transparent these days, and non-double buffered windows can't
render transparently */
- cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0);
cairo_fill(cr);
/* Draw the display */
if (d->ximage) {
+ gboolean seamless_mode;
+ GList *list, *l;
+
cairo_translate(cr, x, y);
+
+ list = spice_main_get_seamless_mode_list(d->main);
+ g_object_get(d->main, "seamless-mode", &seamless_mode, NULL);
+ if (seamless_mode && list) {
+ for (l = list; l != NULL; l = l->next) {
+ GdkRectangle *r = l->data;
+ cairo_rectangle(cr, r->x, r->y, r->width, r->height);
+ }
+ cairo_clip(cr);
+ }
+ g_list_free_full(list, g_free);
+
cairo_rectangle(cr, 0, 0, w, h);
cairo_scale(cr, s, s);
if (!d->convert)
diff --git a/src/spicy.c b/src/spicy.c
index f48a220..c7c5c88 100644
--- a/src/spicy.c
+++ b/src/spicy.c
@@ -71,6 +71,7 @@ struct _SpiceWindow {
GtkActionGroup *ag;
GtkUIManager *ui;
bool fullscreen;
+ bool seamless_mode;
bool mouse_grabbed;
SpiceChannel *display_channel;
#ifdef G_OS_WIN32
@@ -90,6 +91,7 @@ G_DEFINE_TYPE (SpiceWindow, spice_window, G_TYPE_OBJECT);
#define CHANNELID_MAX 4
#define MONITORID_MAX 4
+#define SEAMLESS_MODE_BORDER 50
// FIXME: turn this into an object, get rid of fixed wins array, use
// signals to replace the various callback that iterate over wins array
@@ -116,6 +118,7 @@ static void usb_connect_failed(GObject *object,
gpointer data);
static gboolean is_gtk_session_property(const gchar *property);
static void del_window(spice_connection *conn, SpiceWindow *win);
+static void window_set_seamless_mode(SpiceWindow *win, gboolean enabled);
/* options */
static gboolean fullscreen = false;
@@ -216,6 +219,7 @@ static void update_edit_menu_window(SpiceWindow *win)
{
int i;
GtkAction *toggle;
+ gboolean state;
if (win == NULL) {
return;
@@ -229,6 +233,14 @@ static void update_edit_menu_window(SpiceWindow *win)
gtk_action_set_sensitive(toggle, win->conn->agent_connected);
}
}
+
+ toggle = gtk_action_group_get_action(win->ag, "SeamlessMode");
+ state = spice_main_get_seamless_mode_supported(win->conn->main) &&
+ win->conn->agent_connected;
+ gtk_action_set_sensitive(toggle, state);
+
+ if (!state && win->seamless_mode)
+ window_set_seamless_mode(win, FALSE);
}
static void update_edit_menu(struct spice_connection *conn)
@@ -293,6 +305,129 @@ static void menu_cb_fullscreen(GtkAction *action, void *data)
window_set_fullscreen(win, !win->fullscreen);
}
+static gboolean draw_cb(GtkWidget *widget, GdkEventExpose *event, gpointer userdata)
+{
+ cairo_t *cr;
+
+ cr = gdk_cairo_create(gtk_widget_get_window(widget));
+
+ cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.0);
+
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(cr);
+
+ cairo_destroy(cr);
+
+ return FALSE;
+}
+
+static void update_seamless_mode_window(SpiceWindow *win)
+{
+ GList *list, *l;
+ cairo_region_t *region = NULL;
+
+ list = spice_main_get_seamless_mode_list(win->conn->main);
+ if (win->seamless_mode && list) {
+ region = cairo_region_create();
+ for (l = list; l != NULL; l = l->next) {
+ GdkRectangle rectangle, *r;
+
+ r = (GdkRectangle *)l->data;
+ rectangle.x = r->x - SEAMLESS_MODE_BORDER;
+ rectangle.y = r->y - SEAMLESS_MODE_BORDER;
+ rectangle.width = r->width + 2 * SEAMLESS_MODE_BORDER;
+ rectangle.height = r->height + 2 * SEAMLESS_MODE_BORDER;
+
+ cairo_region_union_rectangle(region, &rectangle);
+ }
+ }
+ g_list_free_full(list, g_free);
+
+ gdk_window_shape_combine_region(gtk_widget_get_window(win->toplevel), region, 0, 0);
+}
+
+static void main_seamless_mode_update(SpiceChannel *channel,
+ GParamSpec *pspec,
+ SpiceWindow *win)
+{
+ update_seamless_mode_window(win);
+}
+
+static void window_set_seamless_mode(SpiceWindow *win, gboolean enabled)
+{
+ gboolean state;
+ GError *error = NULL;
+
+ win->seamless_mode = enabled;
+ g_object_set(win->conn->main, "seamless-mode", win->seamless_mode, NULL);
+ update_seamless_mode_window(win);
+
+ gtk_window_set_decorated(GTK_WINDOW(win->toplevel), !win->seamless_mode);
+ gtk_widget_set_visible(win->menubar, !win->seamless_mode);
+ gtk_widget_set_app_paintable(win->toplevel, win->seamless_mode);
+
+ if (win->seamless_mode) {
+ gtk_window_maximize(GTK_WINDOW(win->toplevel));
+
+ gtk_widget_set_visible(win->toolbar, FALSE);
+ gtk_widget_set_visible(win->statusbar, FALSE);
+
+ g_object_set (win->spice, "grab-keyboard", FALSE, NULL);
+ g_object_set (win->spice, "resize-guest", TRUE, NULL);
+ g_object_set (win->spice, "scaling", FALSE, NULL);
+
+ g_signal_connect(G_OBJECT(win->toplevel), "draw",
+ G_CALLBACK(draw_cb), NULL);
+
+ g_signal_connect(win->conn->main, "notify::seamless-mode-list",
+ G_CALLBACK(main_seamless_mode_update), win);
+ update_seamless_mode_window(win);
+
+ gtk_window_present(GTK_WINDOW(win->toplevel));
+ } else {
+ // TODO: Restore window geometry properly
+ gtk_window_unmaximize(GTK_WINDOW(win->toplevel));
+
+ state = g_key_file_get_boolean(keyfile, "ui", "toolbar", &error);
+ gtk_widget_set_visible(win->toolbar, error ? TRUE : state);
+ g_clear_error (&error);
+
+ state = g_key_file_get_boolean(keyfile, "ui", "statusbar", &error);
+ gtk_widget_set_visible(win->statusbar, error ? TRUE : state);
+ g_clear_error (&error);
+
+ state = g_key_file_get_boolean(keyfile, "general", "grab-keyboard", &error);
+ if (!error)
+ g_object_set (win->spice, "grab-keyboard", state, NULL);
+ g_clear_error (&error);
+
+ state = g_key_file_get_boolean(keyfile, "general", "resize-guest", &error);
+ if (!error)
+ g_object_set (win->spice, "resize-guest", state, NULL);
+ g_clear_error (&error);
+
+ state = g_key_file_get_boolean(keyfile, "general", "scaling", &error);
+ if (!error)
+ g_object_set (win->spice, "scaling", state, NULL);
+ g_clear_error (&error);
+
+ g_signal_handlers_disconnect_by_func(win->conn->main,
+ G_CALLBACK(draw_cb),
+ NULL);
+
+ g_signal_handlers_disconnect_by_func(win->conn->main,
+ G_CALLBACK(main_seamless_mode_update),
+ win);
+ }
+}
+
+static void menu_cb_seamless_mode(GtkAction *action, void *data)
+{
+ SpiceWindow *win = data;
+
+ window_set_seamless_mode (win, !win->seamless_mode);
+}
+
#ifdef USE_SMARTCARD
static void enable_smartcard_actions(SpiceWindow *win, VReader *reader,
gboolean can_insert, gboolean can_remove)
@@ -666,6 +801,12 @@ static const GtkActionEntry entries[] = {
.callback = G_CALLBACK(menu_cb_fullscreen),
.accelerator = "<shift>F11",
},{
+ .name = "SeamlessMode",
+ .label = "_Seamless mode",
+ .callback = G_CALLBACK(menu_cb_seamless_mode),
+ .accelerator = "<control><shift>S",
+ },{
+
#ifdef USE_SMARTCARD
.name = "InsertSmartcard",
.label = "_Insert Smartcard",
@@ -763,6 +904,7 @@ static char ui_xml[] =
" </menu>\n"
" <menu action='ViewMenu'>\n"
" <menuitem action='Fullscreen'/>\n"
+" <menuitem action='SeamlessMode'/>\n"
" <menuitem action='Toolbar'/>\n"
" <menuitem action='Statusbar'/>\n"
" </menu>\n"
@@ -795,6 +937,7 @@ static char ui_xml[] =
" <toolitem action='PasteFromGuest'/>\n"
" <separator/>\n"
" <toolitem action='Fullscreen'/>\n"
+" <toolitem action='SeamlessMode'/>\n"
" </toolbar>\n"
"</ui>\n";
@@ -864,6 +1007,8 @@ static SpiceWindow *create_spice_window(spice_connection *conn, SpiceChannel *ch
GError *err = NULL;
int i;
SpiceGrabSequence *seq;
+ GdkScreen *screen;
+ GdkVisual *visual;
win = g_object_new(SPICE_TYPE_WINDOW, NULL);
win->id = id;
@@ -879,6 +1024,14 @@ static SpiceWindow *create_spice_window(spice_connection *conn, SpiceChannel *ch
snprintf(title, sizeof(title), "%s", spicy_title);
}
+ screen = gtk_widget_get_screen(win->toplevel);
+ visual = gdk_screen_get_rgba_visual(screen);
+ if (visual)
+ gtk_widget_set_visual(win->toplevel, visual);
+ else
+ // TODO: Hide seamless mode toggle
+ g_warning ("Transparency is not supported!");
+
gtk_window_set_title(GTK_WINDOW(win->toplevel), title);
g_signal_connect(G_OBJECT(win->toplevel), "window-state-event",
G_CALLBACK(window_state_cb), win);