Implement internal SpiceCursorChannelGtk class. Its purpose is to handle the graphical (GTK/GDK) representation of the remote cursor. The intention behind it is sharing of the cursor between multiple SpiceDisplay widgets (case of a linux guest which has a single display channel serving multiple monitors). Related: rhbz#1411380 --- src/Makefile.am | 2 + src/channel-cursor-gtk.c | 263 +++++++++++++++++++++++++++++++++++++++++++++++ src/channel-cursor-gtk.h | 51 +++++++++ src/spice-widget.c | 26 ++--- 4 files changed, 327 insertions(+), 15 deletions(-) create mode 100644 src/channel-cursor-gtk.c create mode 100644 src/channel-cursor-gtk.h diff --git a/src/Makefile.am b/src/Makefile.am index 4fa7357..f816fc2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -134,6 +134,8 @@ SPICE_GTK_SOURCES_COMMON = \ desktop-integration.c \ desktop-integration.h \ usb-device-widget.c \ + channel-cursor-gtk.c \ + channel-cursor-gtk.h \ $(NULL) nodist_SPICE_GTK_SOURCES_COMMON = \ diff --git a/src/channel-cursor-gtk.c b/src/channel-cursor-gtk.c new file mode 100644 index 0000000..26b5319 --- /dev/null +++ b/src/channel-cursor-gtk.c @@ -0,0 +1,263 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2017 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#include "config.h" + +#include <glib.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdk.h> + +#include "spice-client.h" +#include "spice-util.h" +#include "channel-cursor-gtk.h" + +/* since GLib 2.38 */ +#ifndef g_assert_true +#define g_assert_true g_assert +#endif + +struct _SpiceCursorChannelGtk { + GObject parent; + + SpiceCursorChannel *cursor; + + GdkPoint hotspot; + GdkPixbuf *cursor_pixbuf; +}; + +struct _SpiceCursorChannelGtkClass { + GObjectClass parent_class; +}; + +/* properties */ +enum { + PROP_0, + PROP_CHANNEL, + PROP_CURSOR_PIXBUF, +}; + +/* ------------------------------------------------------------------ */ +/* Prototypes for private functions */ +static void cursor_set(SpiceCursorChannel *channel, + gint width, gint height, gint hot_x, gint hot_y, + gpointer rgba, gpointer data); + +/* ------------------------------------------------------------------ */ +/* gobject glue */ + +G_DEFINE_TYPE (SpiceCursorChannelGtk, spice_cursor_channel_gtk, G_TYPE_OBJECT); + +static void spice_cursor_channel_gtk_init(G_GNUC_UNUSED SpiceCursorChannelGtk *self) +{ +} + +static GObject * +spice_cursor_channel_gtk_constructor(GType gtype, + guint n_properties, + GObjectConstructParam *properties) +{ + GObject *obj; + SpiceCursorChannelGtk *self; + + { + /* Always chain up to the parent constructor */ + GObjectClass *parent_class; + parent_class = G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class); + obj = parent_class->constructor(gtype, n_properties, properties); + } + + self = SPICE_CURSOR_CHANNEL_GTK(obj); + if (self->cursor == NULL) { + g_error("SpiceCursorChannelGtk constructed without an cursor channel"); + } + + g_signal_connect(self->cursor, "cursor-set", G_CALLBACK(cursor_set), self); + + return obj; +} + +static void spice_cursor_channel_gtk_finalize(GObject *gobject) +{ + SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject); + + g_clear_object(&self->cursor_pixbuf); + + /* Chain up to the parent class */ + if (G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->finalize) + G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->finalize(gobject); +} + +static void spice_cursor_channel_gtk_dispose(GObject *gobject) +{ + SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject); + + if (self->cursor != NULL) { + g_signal_handlers_disconnect_by_func(self->cursor, + G_CALLBACK(cursor_set), + self); + self->cursor = NULL; + } + + /* Chain up to the parent class */ + if (G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->dispose) + G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->dispose(gobject); +} + +static void spice_cursor_channel_gtk_get_property(GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject); + + switch (prop_id) { + case PROP_CHANNEL: + g_value_set_object(value, self->cursor); + break; + case PROP_CURSOR_PIXBUF: + g_value_set_object(value, self->cursor_pixbuf); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); + break; + } +} + +static void spice_cursor_channel_gtk_set_property(GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject); + + switch (prop_id) { + case PROP_CHANNEL: + self->cursor = g_value_get_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); + break; + } +} + +static void spice_cursor_channel_gtk_class_init(SpiceCursorChannelGtkClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->constructor = spice_cursor_channel_gtk_constructor; + gobject_class->dispose = spice_cursor_channel_gtk_dispose; + gobject_class->finalize = spice_cursor_channel_gtk_finalize; + gobject_class->get_property = spice_cursor_channel_gtk_get_property; + gobject_class->set_property = spice_cursor_channel_gtk_set_property; + + /** + * SpiceCursorChannelGtk:cursor-pixbuf: + * + * Represents the current #GdkPixbuf to be used for creating a cursor + * using gdk_cursor_new_from_pixbuf + * + **/ + g_object_class_install_property + (gobject_class, PROP_CURSOR_PIXBUF, + g_param_spec_object("cursor-pixbuf", + "Pixbuf of the cursor", + "GdkPixbuf to be used for creating a cursor", + GDK_TYPE_PIXBUF, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * SpiceCursorChannelGtk:channel: + * + * #SpiceCursorChannel this #SpiceCursorChannelGtk is associated with + * + **/ + g_object_class_install_property + (gobject_class, PROP_CHANNEL, + g_param_spec_object("channel", + "Pixbuf of the cursor", + "GdkPixbuf to be used for creating a cursor", + SPICE_TYPE_CHANNEL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + + +static void cursor_set(SpiceCursorChannel *channel, + gint width, gint height, gint x_hot, gint y_hot, + gpointer rgba, gpointer user_data) +{ + gchar *hotspot_str; + SpiceCursorChannelGtk *self = user_data; + + self->hotspot.x = x_hot; + self->hotspot.y = y_hot; + + g_return_if_fail(rgba != NULL); + + g_clear_object(&self->cursor_pixbuf); + self->cursor_pixbuf = gdk_pixbuf_new_from_data(g_memdup(rgba, width * height * 4), + GDK_COLORSPACE_RGB, + TRUE, + 8, + width, + height, + width * 4, + (GdkPixbufDestroyNotify) g_free, + NULL); + /* + It is possible to set cursor cordinates using pixbuf options and use them + when creating the cursor. + See gdk_cursor_new_from_pixbuf documentation for more information. + */ + hotspot_str = g_strdup_printf("%d", x_hot); + g_assert_true(gdk_pixbuf_set_option(self->cursor_pixbuf, "x_hot", hotspot_str)); + g_free(hotspot_str); + + hotspot_str = g_strdup_printf("%d", y_hot); + g_assert_true(gdk_pixbuf_set_option(self->cursor_pixbuf, "y_hot", hotspot_str)); + g_free(hotspot_str); + + g_object_notify(G_OBJECT(self), "cursor-pixbuf"); +} + +SpiceCursorChannelGtk *spice_cursor_channel_gtk_get(SpiceCursorChannel *channel) +{ + SpiceCursorChannelGtk *self; + static GMutex mutex; + + g_return_val_if_fail(SPICE_IS_CURSOR_CHANNEL(channel), NULL); + + g_mutex_lock(&mutex); + self = g_object_get_data(G_OBJECT(channel), "spice-channel-cursor-gtk"); + if (self == NULL) { + self = g_object_new(SPICE_TYPE_CURSOR_CHANNEL_GTK, "channel", channel, NULL); + g_object_set_data_full(G_OBJECT(channel), "spice-channel-cursor-gtk", self, g_object_unref); + } + g_mutex_unlock(&mutex); + + return self; +} + +void spice_cursor_channel_gtk_get_hotspot(SpiceCursorChannelGtk *self, GdkPoint *hotspot) +{ + g_return_if_fail(SPICE_IS_CURSOR_CHANNEL_GTK(self)); + g_return_if_fail(hotspot != NULL); + + *hotspot = self->hotspot; +} diff --git a/src/channel-cursor-gtk.h b/src/channel-cursor-gtk.h new file mode 100644 index 0000000..63d3321 --- /dev/null +++ b/src/channel-cursor-gtk.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2017 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __SPICE_CLIENT_GTK_CHANNEL_CURSOR_H__ +#define __SPICE_CLIENT_GTK_CHANNEL_CURSOR_H__ + +#include <glib-object.h> +#include <gdk/gdk.h> + +G_BEGIN_DECLS + +#define SPICE_TYPE_CURSOR_CHANNEL_GTK (spice_cursor_channel_gtk_get_type()) +#define SPICE_CURSOR_CHANNEL_GTK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + SPICE_TYPE_CURSOR_CHANNEL_GTK, \ + SpiceCursorChannelGtk)) +#define SPICE_CURSOR_CHANNEL_GTK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + SPICE_TYPE_CURSOR_CHANNEL_GTK, \ + SpiceCursorChannelGtkClass)) +#define SPICE_IS_CURSOR_CHANNEL_GTK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + SPICE_TYPE_CURSOR_CHANNEL_GTK)) +#define SPICE_IS_CURSOR_CHANNEL_GTK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + SPICE_TYPE_CURSOR_CHANNEL_GTK)) +#define SPICE_CURSOR_CHANNEL_GTK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + SPICE_TYPE_CURSOR_CHANNEL_GTK, \ + SpiceCursorChannelGtkClass)) + +typedef struct _SpiceCursorChannelGtk SpiceCursorChannelGtk; +typedef struct _SpiceCursorChannelGtkClass SpiceCursorChannelGtkClass; + +GType spice_cursor_channel_gtk_get_type(void); + +SpiceCursorChannelGtk *spice_cursor_channel_gtk_get(SpiceCursorChannel *channel); +void spice_cursor_channel_gtk_get_hotspot(SpiceCursorChannelGtk *self, GdkPoint *hotspot); + +G_END_DECLS + +#endif /* __SPICE_CLIENT_GTK_CHANNEL_CURSOR_H__ */ diff --git a/src/spice-widget.c b/src/spice-widget.c index 5bbba8f..503644b 100644 --- a/src/spice-widget.c +++ b/src/spice-widget.c @@ -43,6 +43,7 @@ #include "spice-gtk-session-priv.h" #include "vncdisplaykeymap.h" #include "spice-grabsequence-priv.h" +#include "channel-cursor-gtk.h" /** @@ -2634,9 +2635,9 @@ static void mark(SpiceDisplay *display, gint mark) update_ready(display); } -static void cursor_set(SpiceCursorChannel *channel, - gint width, gint height, gint hot_x, gint hot_y, - gpointer rgba, gpointer data) +static void cursor_set(SpiceCursorChannelGtk *channel_gtk, + G_GNUC_UNUSED GParamSpec *pspec, + gpointer data) { SpiceDisplay *display = data; SpiceDisplayPrivate *d = display->priv; @@ -2646,18 +2647,11 @@ static void cursor_set(SpiceCursorChannel *channel, g_clear_object(&d->mouse_pixbuf); - if (rgba != NULL) { - d->mouse_pixbuf = gdk_pixbuf_new_from_data(g_memdup(rgba, width * height * 4), - GDK_COLORSPACE_RGB, - TRUE, 8, - width, - height, - width * 4, - (GdkPixbufDestroyNotify)g_free, NULL); - d->mouse_hotspot.x = hot_x; - d->mouse_hotspot.y = hot_y; + g_object_get(G_OBJECT(channel_gtk), "cursor-pixbuf", &d->mouse_pixbuf, NULL); + if (d->mouse_pixbuf != NULL) { + spice_cursor_channel_gtk_get_hotspot(channel_gtk, &d->mouse_hotspot); cursor = gdk_cursor_new_from_pixbuf(gtk_widget_get_display(GTK_WIDGET(display)), - d->mouse_pixbuf, hot_x, hot_y); + d->mouse_pixbuf, -1, -1); } else g_warn_if_reached(); @@ -2955,10 +2949,12 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data) } if (SPICE_IS_CURSOR_CHANNEL(channel)) { + SpiceCursorChannelGtk *channel_gtk; if (id != d->channel_id) return; d->cursor = SPICE_CURSOR_CHANNEL(channel); - spice_g_signal_connect_object(channel, "cursor-set", + channel_gtk = spice_cursor_channel_gtk_get(d->cursor); + spice_g_signal_connect_object(channel_gtk, "notify::cursor-pixbuf", G_CALLBACK(cursor_set), display, 0); spice_g_signal_connect_object(channel, "cursor-move", G_CALLBACK(cursor_move), display, 0); -- 2.12.2 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel