[Spice-devel] [PATCH spice-gtk 1/2] Add helper class to handle cursor image
Pavel Grunt
pgrunt at redhat.com
Thu Apr 27 17:21:38 UTC 2017
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
More information about the Spice-devel
mailing list