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