1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20  * file for a list of people on the GTK+ Team.  See the ChangeLog
21  * files for a list of changes.  These files are distributed with
22  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23  */
24 
25 #include "config.h"
26 
27 /* needs to be first because any header might include gdk-pixbuf.h otherwise */
28 #define GDK_PIXBUF_ENABLE_BACKEND
29 #include <gdk-pixbuf/gdk-pixbuf.h>
30 
31 #include "gdkcursor.h"
32 #include "gdkcursorprivate.h"
33 #include "gdkprivate-x11.h"
34 #include "gdkdisplay-x11.h"
35 
36 #include <X11/Xlib.h>
37 #include <X11/cursorfont.h>
38 #ifdef HAVE_XCURSOR
39 #include <X11/Xcursor/Xcursor.h>
40 #endif
41 #ifdef HAVE_XFIXES
42 #include <X11/extensions/Xfixes.h>
43 #endif
44 #include <string.h>
45 
46 static void
gdk_x11_cursor_remove_from_cache(gpointer data,GObject * cursor)47 gdk_x11_cursor_remove_from_cache (gpointer data, GObject *cursor)
48 {
49   GdkDisplay *display = data;
50   Cursor xcursor;
51 
52   xcursor = GDK_POINTER_TO_XID (g_hash_table_lookup (GDK_X11_DISPLAY (display)->cursors, cursor));
53   XFreeCursor (GDK_DISPLAY_XDISPLAY (display), xcursor);
54   g_hash_table_remove (GDK_X11_DISPLAY (display)->cursors, cursor);
55 }
56 
57 void
_gdk_x11_cursor_display_finalize(GdkDisplay * display)58 _gdk_x11_cursor_display_finalize (GdkDisplay *display)
59 {
60   GHashTableIter iter;
61   gpointer cursor;
62 
63   if (GDK_X11_DISPLAY (display)->cursors)
64     {
65       g_hash_table_iter_init (&iter, GDK_X11_DISPLAY (display)->cursors);
66       while (g_hash_table_iter_next (&iter, &cursor, NULL))
67         g_object_weak_unref (G_OBJECT (cursor), gdk_x11_cursor_remove_from_cache, display);
68       g_hash_table_unref (GDK_X11_DISPLAY (display)->cursors);
69     }
70 }
71 
72 static Cursor
get_blank_cursor(GdkDisplay * display)73 get_blank_cursor (GdkDisplay *display)
74 {
75   Pixmap pixmap;
76   XColor color;
77   Cursor cursor;
78   cairo_surface_t *surface;
79   cairo_t *cr;
80 
81   surface = _gdk_x11_display_create_bitmap_surface (display, 1, 1);
82   /* Clear surface */
83   cr = cairo_create (surface);
84   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
85   cairo_paint (cr);
86   cairo_destroy (cr);
87 
88   pixmap = cairo_xlib_surface_get_drawable (surface);
89 
90   color.pixel = 0;
91   color.red = color.blue = color.green = 0;
92 
93   if (gdk_display_is_closed (display))
94     cursor = None;
95   else
96     cursor = XCreatePixmapCursor (GDK_DISPLAY_XDISPLAY (display),
97                                   pixmap, pixmap,
98                                   &color, &color, 1, 1);
99   cairo_surface_destroy (surface);
100 
101   return cursor;
102 }
103 
104 static const struct {
105   const char *css_name;
106   const char *traditional_name;
107   int cursor_glyph;
108 } name_map[] = {
109   { "default",      "left_ptr",            XC_left_ptr, },
110   { "help",         "question_arrow",      XC_question_arrow },
111   { "context-menu", "left_ptr",            XC_left_ptr },
112   { "pointer",      "hand",                XC_hand1 },
113   { "progress",     "left_ptr_watch",      XC_watch },
114   { "wait",         "watch",               XC_watch },
115   { "cell",         "crosshair",           XC_plus },
116   { "crosshair",    "cross",               XC_crosshair },
117   { "text",         "xterm",               XC_xterm },
118   { "vertical-text","xterm",               XC_xterm },
119   { "alias",        "dnd-link",            XC_target },
120   { "copy",         "dnd-copy",            XC_target },
121   { "move",         "dnd-move",            XC_target },
122   { "no-drop",      "dnd-none",            XC_pirate },
123   { "dnd-ask",      "dnd-copy",            XC_target }, /* not CSS, but we want to guarantee it anyway */
124   { "not-allowed",  "crossed_circle",      XC_pirate },
125   { "grab",         "hand2",               XC_hand2 },
126   { "grabbing",     "hand2",               XC_hand2 },
127   { "all-scroll",   "left_ptr",            XC_left_ptr },
128   { "col-resize",   "h_double_arrow",      XC_sb_h_double_arrow },
129   { "row-resize",   "v_double_arrow",      XC_sb_v_double_arrow },
130   { "n-resize",     "top_side",            XC_top_side },
131   { "e-resize",     "right_side",          XC_right_side },
132   { "s-resize",     "bottom_side",         XC_bottom_side },
133   { "w-resize",     "left_side",           XC_left_side },
134   { "ne-resize",    "top_right_corner",    XC_top_right_corner },
135   { "nw-resize",    "top_left_corner",     XC_top_left_corner },
136   { "se-resize",    "bottom_right_corner", XC_bottom_right_corner },
137   { "sw-resize",    "bottom_left_corner",  XC_bottom_left_corner },
138   { "ew-resize",    "h_double_arrow",      XC_sb_h_double_arrow },
139   { "ns-resize",    "v_double_arrow",      XC_sb_v_double_arrow },
140   { "nesw-resize",  "fd_double_arrow",     XC_X_cursor },
141   { "nwse-resize",  "bd_double_arrow",     XC_X_cursor },
142   { "zoom-in",      "left_ptr",            XC_draped_box },
143   { "zoom-out",     "left_ptr",            XC_draped_box }
144 };
145 
146 #ifdef HAVE_XCURSOR
147 
148 static XcursorImage*
create_cursor_image(GdkTexture * texture,int x,int y,int scale)149 create_cursor_image (GdkTexture *texture,
150                      int         x,
151                      int         y,
152 		     int         scale)
153 {
154   XcursorImage *xcimage;
155 
156   xcimage = XcursorImageCreate (gdk_texture_get_width (texture), gdk_texture_get_height (texture));
157 
158   xcimage->xhot = x;
159   xcimage->yhot = y;
160 
161   gdk_texture_download (texture,
162                         (guchar *) xcimage->pixels,
163                         gdk_texture_get_width (texture) * 4);
164 
165   return xcimage;
166 }
167 
168 static Cursor
gdk_x11_cursor_create_for_texture(GdkDisplay * display,GdkTexture * texture,int x,int y)169 gdk_x11_cursor_create_for_texture (GdkDisplay *display,
170                                    GdkTexture *texture,
171                                    int         x,
172                                    int         y)
173 {
174   XcursorImage *xcimage;
175   Cursor xcursor;
176   int target_scale;
177 
178   target_scale =
179     gdk_monitor_get_scale_factor (gdk_x11_display_get_primary_monitor (display));
180   xcimage = create_cursor_image (texture, x, y, target_scale);
181   xcursor = XcursorImageLoadCursor (GDK_DISPLAY_XDISPLAY (display), xcimage);
182   XcursorImageDestroy (xcimage);
183 
184   return xcursor;
185 }
186 
187 static const char *
name_fallback(const char * name)188 name_fallback (const char *name)
189 {
190   int i;
191 
192   for (i = 0; i < G_N_ELEMENTS (name_map); i++)
193     {
194       if (g_str_equal (name_map[i].css_name, name))
195         return name_map[i].traditional_name;
196     }
197 
198   return NULL;
199 }
200 
201 static Cursor
gdk_x11_cursor_create_for_name(GdkDisplay * display,const char * name)202 gdk_x11_cursor_create_for_name (GdkDisplay *display,
203                                 const char *name)
204 {
205   Cursor xcursor;
206   Display *xdisplay;
207 
208   if (strcmp (name, "none") == 0)
209     {
210       xcursor = get_blank_cursor (display);
211     }
212   else
213     {
214       xdisplay = GDK_DISPLAY_XDISPLAY (display);
215       xcursor = XcursorLibraryLoadCursor (xdisplay, name);
216       if (xcursor == None)
217         {
218           const char *fallback;
219 
220           fallback = name_fallback (name);
221           if (fallback)
222             xcursor = XcursorLibraryLoadCursor (xdisplay, fallback);
223         }
224     }
225 
226   return xcursor;
227 }
228 
229 #else
230 
231 static Cursor
gdk_x11_cursor_create_for_texture(GdkDisplay * display,GdkTexture * texture,int x,int y)232 gdk_x11_cursor_create_for_texture (GdkDisplay *display,
233                                    GdkTexture *texture,
234                                    int         x,
235                                    int         y)
236 {
237   return None;
238 }
239 
240 static Cursor
gdk_x11_cursor_create_for_name(GdkDisplay * display,const char * name)241 gdk_x11_cursor_create_for_name (GdkDisplay  *display,
242                                 const char *name)
243 {
244   int i;
245 
246   if (g_str_equal (name, "none"))
247     return get_blank_cursor (display);
248 
249   for (i = 0; i < G_N_ELEMENTS (name_map); i++)
250     {
251       if (g_str_equal (name_map[i].css_name, name) ||
252           g_str_equal (name_map[i].traditional_name, name))
253         return XCreateFontCursor (GDK_DISPLAY_XDISPLAY (display), name_map[i].cursor_glyph);
254     }
255 
256   return None;
257 }
258 
259 #endif
260 
261 /**
262  * gdk_x11_display_set_cursor_theme:
263  * @display: (type GdkX11Display): a `GdkDisplay`
264  * @theme: (nullable): the name of the cursor theme to use, or %NULL
265  *   to unset a previously set value
266  * @size: the cursor size to use, or 0 to keep the previous size
267  *
268  * Sets the cursor theme from which the images for cursor
269  * should be taken.
270  *
271  * If the windowing system supports it, existing cursors created
272  * with [ctor@Gdk.Cursor.new_from_name] are updated to reflect the theme
273  * change. Custom cursors constructed with [ctor@Gdk.Cursor.new_from_texture]
274  * will have to be handled by the application (GTK applications can learn
275  * about cursor theme changes by listening for change notification
276  * for the corresponding `GtkSetting`).
277  */
278 void
gdk_x11_display_set_cursor_theme(GdkDisplay * display,const char * theme,const int size)279 gdk_x11_display_set_cursor_theme (GdkDisplay  *display,
280                                   const char *theme,
281                                   const int    size)
282 {
283 #if defined(HAVE_XCURSOR) && defined(HAVE_XFIXES) && XFIXES_MAJOR >= 2
284   Display *xdisplay;
285   char *old_theme;
286   int old_size;
287   gpointer cursor, xcursor;
288   GHashTableIter iter;
289 
290   g_return_if_fail (GDK_IS_DISPLAY (display));
291 
292   xdisplay = GDK_DISPLAY_XDISPLAY (display);
293 
294   old_theme = XcursorGetTheme (xdisplay);
295   old_size = XcursorGetDefaultSize (xdisplay);
296 
297   if (old_size == size &&
298       (old_theme == theme ||
299        (old_theme && theme && strcmp (old_theme, theme) == 0)))
300     return;
301 
302   XcursorSetTheme (xdisplay, theme);
303   if (size > 0)
304     XcursorSetDefaultSize (xdisplay, size);
305 
306   if (GDK_X11_DISPLAY (display)->cursors == NULL)
307     return;
308 
309   g_hash_table_iter_init (&iter, GDK_X11_DISPLAY (display)->cursors);
310   while (g_hash_table_iter_next (&iter, &cursor, &xcursor))
311     {
312       const char *name = gdk_cursor_get_name (cursor);
313 
314       if (name)
315         {
316           Cursor new_cursor = gdk_x11_cursor_create_for_name (display, name);
317 
318           if (new_cursor != None)
319             {
320               XFixesChangeCursor (xdisplay, new_cursor, GDK_POINTER_TO_XID (xcursor));
321               g_hash_table_iter_replace (&iter, GDK_XID_TO_POINTER (new_cursor));
322             }
323           else
324             {
325               g_hash_table_iter_remove (&iter);
326             }
327         }
328     }
329 #endif
330 }
331 
332 /**
333  * gdk_x11_display_get_xcursor:
334  * @display: (type GdkX11Display): a `GdkDisplay`
335  * @cursor: a `GdkCursor`
336  *
337  * Returns the X cursor belonging to a `GdkCursor`, potentially
338  * creating the cursor.
339  *
340  * Be aware that the returned cursor may not be unique to @cursor.
341  * It may for example be shared with its fallback cursor. On old
342  * X servers that don't support the XCursor extension, all cursors
343  * may even fall back to a few default cursors.
344  *
345  * Returns: an Xlib Cursor.
346  */
347 Cursor
gdk_x11_display_get_xcursor(GdkDisplay * display,GdkCursor * cursor)348 gdk_x11_display_get_xcursor (GdkDisplay *display,
349                              GdkCursor  *cursor)
350 {
351   GdkX11Display *x11_display = GDK_X11_DISPLAY (display);
352   Cursor xcursor;
353 
354   g_return_val_if_fail (cursor != NULL, None);
355 
356   if (gdk_display_is_closed (display))
357     return None;
358 
359   if (x11_display->cursors == NULL)
360     x11_display->cursors = g_hash_table_new (gdk_cursor_hash, gdk_cursor_equal);
361 
362   xcursor = GDK_POINTER_TO_XID (g_hash_table_lookup (x11_display->cursors, cursor));
363   if (xcursor)
364     return xcursor;
365 
366   if (gdk_cursor_get_name (cursor))
367     xcursor = gdk_x11_cursor_create_for_name (display, gdk_cursor_get_name (cursor));
368   else
369     xcursor = gdk_x11_cursor_create_for_texture (display,
370                                                  gdk_cursor_get_texture (cursor),
371                                                  gdk_cursor_get_hotspot_x (cursor),
372                                                  gdk_cursor_get_hotspot_y (cursor));
373 
374   if (xcursor != None)
375     {
376       g_object_weak_ref (G_OBJECT (cursor), gdk_x11_cursor_remove_from_cache, display);
377       g_hash_table_insert (x11_display->cursors, cursor, GDK_XID_TO_POINTER (xcursor));
378       return xcursor;
379     }
380 
381   if (gdk_cursor_get_fallback (cursor))
382     return gdk_x11_display_get_xcursor (display, gdk_cursor_get_fallback (cursor));
383 
384   return None;
385 }
386 
387