1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 #include <stdio.h>
3 #include <stdlib.h>
4
5 #include "shell-xfixes-cursor.h"
6
7 #include <clutter/x11/clutter-x11.h>
8 #include <X11/extensions/Xfixes.h>
9
10 /**
11 * SECTION:shell-xfixes-cursor
12 * @short_description: Capture/manipulate system mouse cursor.
13 *
14 * The #ShellXFixesCursor object uses the XFixes extension to show/hide the
15 * the system mouse pointer, to grab its image as it changes, and emit a
16 * notification when its image changes.
17 */
18
19 struct _ShellXFixesCursorClass
20 {
21 GObjectClass parent_class;
22 };
23
24 struct _ShellXFixesCursor {
25 GObject parent;
26
27 ClutterStage *stage;
28
29 gboolean have_xfixes;
30 int xfixes_event_base;
31
32 gboolean is_showing;
33
34 CoglHandle *cursor_sprite;
35 int cursor_hot_x;
36 int cursor_hot_y;
37 };
38
39 static void xfixes_cursor_show (ShellXFixesCursor *xfixes_cursor);
40 static void xfixes_cursor_hide (ShellXFixesCursor *xfixes_cursor);
41
42 static void xfixes_cursor_set_stage (ShellXFixesCursor *xfixes_cursor,
43 ClutterStage *stage);
44
45 static void xfixes_cursor_reset_image (ShellXFixesCursor *xfixes_cursor);
46
47 enum {
48 PROP_0,
49 PROP_STAGE,
50 };
51
52 G_DEFINE_TYPE(ShellXFixesCursor, shell_xfixes_cursor, G_TYPE_OBJECT);
53
54 enum {
55 CURSOR_CHANGED,
56 LAST_SIGNAL
57 };
58
59 static guint signals[LAST_SIGNAL] = { 0 };
60
61 static void
62 shell_xfixes_cursor_init (ShellXFixesCursor *xfixes_cursor)
63 {
64 // (JS) Best (?) that can be assumed since XFixes doesn't provide a way of
65 // detecting if the system mouse cursor is showing or not.
66 xfixes_cursor->is_showing = TRUE;
67 }
68
69 static void
70 shell_xfixes_cursor_finalize (GObject *object)
71 {
72 ShellXFixesCursor *xfixes_cursor = SHELL_XFIXES_CURSOR (object);
73
74 // Make sure the system cursor is showing before leaving the stage.
75 xfixes_cursor_show (xfixes_cursor);
76 xfixes_cursor_set_stage (xfixes_cursor, NULL);
77 if (xfixes_cursor->cursor_sprite != NULL)
78 cogl_handle_unref (xfixes_cursor->cursor_sprite);
79
80 G_OBJECT_CLASS (shell_xfixes_cursor_parent_class)->finalize (object);
81 }
82
83 static void
84 xfixes_cursor_on_stage_destroy (ClutterActor *actor,
85 ShellXFixesCursor *xfixes_cursor)
86 {
87 xfixes_cursor_set_stage (xfixes_cursor, NULL);
88 }
89
90 static ClutterX11FilterReturn
91 xfixes_cursor_event_filter (XEvent *xev,
92 ClutterEvent *cev,
93 gpointer data)
94 {
95 ShellXFixesCursor *xfixes_cursor = data;
96
97 if (xev->xany.window != clutter_x11_get_stage_window (xfixes_cursor->stage))
98 return CLUTTER_X11_FILTER_CONTINUE;
99
100 if (xev->xany.type == xfixes_cursor->xfixes_event_base + XFixesCursorNotify)
101 {
102 XFixesCursorNotifyEvent *notify_event = (XFixesCursorNotifyEvent *)xev;
103 if (notify_event->subtype == XFixesDisplayCursorNotify)
104 xfixes_cursor_reset_image (xfixes_cursor);
105 }
106 return CLUTTER_X11_FILTER_CONTINUE;
107 }
108
109 static void
110 xfixes_cursor_set_stage (ShellXFixesCursor *xfixes_cursor,
111 ClutterStage *stage)
112 {
113 if (xfixes_cursor->stage == stage)
114 return;
115
116 if (xfixes_cursor->stage)
117 {
118 g_signal_handlers_disconnect_by_func (xfixes_cursor->stage,
119 (void *)xfixes_cursor_on_stage_destroy,
120 xfixes_cursor);
121
122 clutter_x11_remove_filter (xfixes_cursor_event_filter, xfixes_cursor);
123 }
124 xfixes_cursor->stage = stage;
125 if (xfixes_cursor->stage)
126 {
127 int error_base;
128
129 xfixes_cursor->stage = stage;
130 g_signal_connect (xfixes_cursor->stage, "destroy",
131 G_CALLBACK (xfixes_cursor_on_stage_destroy), xfixes_cursor);
132
133 clutter_x11_add_filter (xfixes_cursor_event_filter, xfixes_cursor);
134
135 xfixes_cursor->have_xfixes = XFixesQueryExtension (clutter_x11_get_default_display (),
136 &xfixes_cursor->xfixes_event_base,
137 &error_base);
138 if (xfixes_cursor->have_xfixes)
139 XFixesSelectCursorInput (clutter_x11_get_default_display (),
140 clutter_x11_get_stage_window (stage),
141 XFixesDisplayCursorNotifyMask);
142
143 xfixes_cursor_reset_image (xfixes_cursor);
144 }
145 }
146
147 static void
148 xfixes_cursor_show (ShellXFixesCursor *xfixes_cursor)
149 {
150 int minor, major;
151 Display *xdisplay;
152 Window xwindow;
153
154 if (xfixes_cursor->is_showing == TRUE)
155 return;
156
157 if (!xfixes_cursor->have_xfixes || !xfixes_cursor->stage)
158 return;
159
160 xdisplay = clutter_x11_get_default_display ();
161 xwindow = clutter_x11_get_stage_window (xfixes_cursor->stage);
162 XFixesQueryVersion (xdisplay, &major, &minor);
163 if (major >= 4)
164 {
165 XFixesShowCursor (xdisplay, xwindow);
166 xfixes_cursor->is_showing = TRUE;
167 }
168 }
169
170 static void
171 xfixes_cursor_hide (ShellXFixesCursor *xfixes_cursor)
172 {
173 int minor, major;
174 Display *xdisplay;
175 Window xwindow;
176
177 if (xfixes_cursor->is_showing == FALSE)
178 return;
179
180 if (!xfixes_cursor->have_xfixes || !xfixes_cursor->stage)
181 return;
182
183 xdisplay = clutter_x11_get_default_display ();
184 xwindow = clutter_x11_get_stage_window (xfixes_cursor->stage);
185 XFixesQueryVersion (xdisplay, &major, &minor);
186 if (major >= 4)
187 {
188 XFixesHideCursor (xdisplay, xwindow);
189 xfixes_cursor->is_showing = FALSE;
190 }
191 }
192
193 static void
194 xfixes_cursor_reset_image (ShellXFixesCursor *xfixes_cursor)
195 {
196 XFixesCursorImage *cursor_image;
197 CoglHandle sprite = COGL_INVALID_HANDLE;
198 guint8 *cursor_data;
199 gboolean free_cursor_data;
200
201 if (!xfixes_cursor->have_xfixes)
202 return;
203
204 cursor_image = XFixesGetCursorImage (clutter_x11_get_default_display ());
205 if (!cursor_image)
206 return;
207
208 /* Like all X APIs, XFixesGetCursorImage() returns arrays of 32-bit
209 * quantities as arrays of long; we need to convert on 64 bit */
210 if (sizeof(long) == 4)
211 {
212 cursor_data = (guint8 *)cursor_image->pixels;
213 free_cursor_data = FALSE;
214 }
215 else
216 {
217 int i, j;
218 guint32 *cursor_words;
219 gulong *p;
220 guint32 *q;
221
222 cursor_words = g_new (guint32, cursor_image->width * cursor_image->height);
223 cursor_data = (guint8 *)cursor_words;
224
225 p = cursor_image->pixels;
226 q = cursor_words;
227 for (j = 0; j < cursor_image->height; j++)
228 for (i = 0; i < cursor_image->width; i++)
229 *(q++) = *(p++);
230
231 free_cursor_data = TRUE;
232 }
233
234 sprite = cogl_texture_new_from_data (cursor_image->width,
235 cursor_image->height,
236 COGL_TEXTURE_NONE,
237 CLUTTER_CAIRO_FORMAT_ARGB32,
238 COGL_PIXEL_FORMAT_ANY,
239 cursor_image->width * 4, /* stride */
240 cursor_data);
241
242 if (free_cursor_data)
243 g_free (cursor_data);
244
245 if (sprite != COGL_INVALID_HANDLE)
246 {
247 if (xfixes_cursor->cursor_sprite != NULL)
248 cogl_handle_unref (xfixes_cursor->cursor_sprite);
249
250 xfixes_cursor->cursor_sprite = sprite;
251 xfixes_cursor->cursor_hot_x = cursor_image->xhot;
252 xfixes_cursor->cursor_hot_y = cursor_image->yhot;
253 g_signal_emit (xfixes_cursor, signals[CURSOR_CHANGED], 0);
254 }
255 XFree (cursor_image);
256 }
257
258 static void
259 shell_xfixes_cursor_set_property (GObject *object,
260 guint prop_id,
261 const GValue *value,
262 GParamSpec *pspec)
263 {
264 ShellXFixesCursor *xfixes_cursor = SHELL_XFIXES_CURSOR (object);
265
266 switch (prop_id)
267 {
268 case PROP_STAGE:
269 xfixes_cursor_set_stage (xfixes_cursor, g_value_get_object (value));
270 break;
271 default:
272 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
273 break;
274 }
275 }
276
277 static void
278 shell_xfixes_cursor_get_property (GObject *object,
279 guint prop_id,
280 GValue *value,
281 GParamSpec *pspec)
282 {
283 ShellXFixesCursor *xfixes_cursor = SHELL_XFIXES_CURSOR (object);
284
285 switch (prop_id)
286 {
287 case PROP_STAGE:
288 g_value_set_object (value, G_OBJECT (xfixes_cursor->stage));
289 break;
290 default:
291 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
292 break;
293 }
294 }
295
296 static void
297 shell_xfixes_cursor_class_init (ShellXFixesCursorClass *klass)
298 {
299 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
300
301 gobject_class->finalize = shell_xfixes_cursor_finalize;
302
303 signals[CURSOR_CHANGED] = g_signal_new ("cursor-change",
304 G_TYPE_FROM_CLASS (klass),
305 G_SIGNAL_RUN_LAST,
306 0,
307 NULL, NULL, NULL,
308 G_TYPE_NONE, 0);
309
310 gobject_class->get_property = shell_xfixes_cursor_get_property;
311 gobject_class->set_property = shell_xfixes_cursor_set_property;
312
313 g_object_class_install_property (gobject_class,
314 PROP_STAGE,
315 g_param_spec_object ("stage",
316 "Stage",
317 "Stage for mouse cursor",
318 CLUTTER_TYPE_STAGE,
319 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
320 }
321
322 /**
323 * shell_xfixes_cursor_get_for_stage:
324 * @stage: (transfer none): The #ClutterStage to get the cursor for
325 *
326 * Return value: (transfer none): A #ShellXFixesCursor instance
327 */
328 ShellXFixesCursor *
329 shell_xfixes_cursor_get_for_stage (ClutterStage *stage)
330 {
331 ShellXFixesCursor *instance;
332 static GQuark xfixes_cursor_quark;
333
334 if (G_UNLIKELY (xfixes_cursor_quark == 0))
335 xfixes_cursor_quark = g_quark_from_static_string ("gnome-shell-xfixes-cursor");
336
337 instance = g_object_get_qdata (G_OBJECT (stage), xfixes_cursor_quark);
338
339 if (instance == NULL)
340 {
341 instance = g_object_new (SHELL_TYPE_XFIXES_CURSOR,
342 "stage", stage,
343 NULL);
344 g_object_set_qdata (G_OBJECT (stage), xfixes_cursor_quark, instance);
345 }
346
347 return instance;
348 }
349
350 /**
351 * shell_xfixes_cursor_hide:
352 * @xfixes_cursor: the #ShellXFixesCursor
353 *
354 * Hide the system mouse cursor.
355 */
356 void
357 shell_xfixes_cursor_hide (ShellXFixesCursor *xfixes_cursor)
358 {
359 g_return_if_fail (SHELL_IS_XFIXES_CURSOR (xfixes_cursor));
360
361 xfixes_cursor_hide (xfixes_cursor);
362 }
363
364 /**
365 * shell_xfixes_cursor_show:
366 * @xfixes_cursor: the #ShellXFixesCursor
367 *
368 * Show the system mouse cursor to show
369 */
370 void
371 shell_xfixes_cursor_show (ShellXFixesCursor *xfixes_cursor)
372 {
373 g_return_if_fail (SHELL_IS_XFIXES_CURSOR (xfixes_cursor));
374
375 xfixes_cursor_show (xfixes_cursor);
376 }
377
378 /**
379 * shell_xfixes_cursor_update_texture_image:
380 * @xfixes_cursor: the #ShellXFixesCursor
381 * @texture: ClutterTexture to update with the current sprite image.
382 */
383 void
384 shell_xfixes_cursor_update_texture_image (ShellXFixesCursor *xfixes_cursor,
385 ClutterTexture *texture)
386 {
387 CoglHandle *old_sprite;
388 g_return_if_fail (SHELL_IS_XFIXES_CURSOR (xfixes_cursor));
389
390 if (texture == NULL)
391 return;
392
393 old_sprite = clutter_texture_get_cogl_texture (texture);
'clutter_texture_get_cogl_texture' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:78)
(emitted by gcc)
394 if (xfixes_cursor->cursor_sprite == old_sprite)
395 return;
396
397 clutter_texture_set_cogl_texture (texture, xfixes_cursor->cursor_sprite);
'clutter_texture_set_cogl_texture' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:80)
(emitted by gcc)
398 }
399
400 /**
401 * shell_xfixes_cursor_get_hot_x:
402 * @xfixes_cursor: the #ShellXFixesCursor
403 *
404 * Returns: the current mouse cursor's hot x-coordinate.
405 */
406 int
407 shell_xfixes_cursor_get_hot_x (ShellXFixesCursor *xfixes_cursor)
408 {
409 g_return_val_if_fail (SHELL_IS_XFIXES_CURSOR (xfixes_cursor), 0);
410
411 return xfixes_cursor->cursor_hot_x;
412 }
413
414 /**
415 * shell_xfixes_cursor_get_hot_y:
416 * @xfixes_cursor: the #ShellXFixesCursor
417 *
418 * Returns: the current mouse cursor's hot y-coordinate.
419 */
420 int
421 shell_xfixes_cursor_get_hot_y (ShellXFixesCursor *xfixes_cursor)
422 {
423 g_return_val_if_fail (SHELL_IS_XFIXES_CURSOR (xfixes_cursor), 0);
424
425 return xfixes_cursor->cursor_hot_y;
426 }