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
46static void
47gdk_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, key: cursor);
55}
56
57void
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: &iter, GDK_X11_DISPLAY (display)->cursors);
66 while (g_hash_table_iter_next (iter: &iter, key: &cursor, NULL))
67 g_object_weak_unref (G_OBJECT (cursor), notify: gdk_x11_cursor_remove_from_cache, data: display);
68 g_hash_table_unref (GDK_X11_DISPLAY (display)->cursors);
69 }
70}
71
72static Cursor
73get_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, width: 1, height: 1);
82 /* Clear surface */
83 cr = cairo_create (target: surface);
84 cairo_set_operator (cr, op: 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
104static 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
148static XcursorImage*
149create_cursor_image (GdkTexture *texture,
150 int x,
151 int y,
152 int scale)
153{
154 XcursorImage *xcimage;
155
156 xcimage = XcursorImageCreate (width: gdk_texture_get_width (texture), height: gdk_texture_get_height (texture));
157
158 xcimage->xhot = x;
159 xcimage->yhot = y;
160
161 gdk_texture_download (texture,
162 data: (guchar *) xcimage->pixels,
163 stride: gdk_texture_get_width (texture) * 4);
164
165 return xcimage;
166}
167
168static Cursor
169gdk_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 (monitor: gdk_x11_display_get_primary_monitor (display));
180 xcimage = create_cursor_image (texture, x, y, scale: target_scale);
181 xcursor = XcursorImageLoadCursor (GDK_DISPLAY_XDISPLAY (display), image: xcimage);
182 XcursorImageDestroy (image: xcimage);
183
184 return xcursor;
185}
186
187static const char *
188name_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 (v1: name_map[i].css_name, v2: name))
195 return name_map[i].traditional_name;
196 }
197
198 return NULL;
199}
200
201static Cursor
202gdk_x11_cursor_create_for_name (GdkDisplay *display,
203 const char *name)
204{
205 Cursor xcursor;
206 Display *xdisplay;
207
208 if (strcmp (s1: name, s2: "none") == 0)
209 {
210 xcursor = get_blank_cursor (display);
211 }
212 else
213 {
214 xdisplay = GDK_DISPLAY_XDISPLAY (display);
215 xcursor = XcursorLibraryLoadCursor (dpy: xdisplay, file: name);
216 if (xcursor == None)
217 {
218 const char *fallback;
219
220 fallback = name_fallback (name);
221 if (fallback)
222 xcursor = XcursorLibraryLoadCursor (dpy: xdisplay, file: fallback);
223 }
224 }
225
226 return xcursor;
227}
228
229#else
230
231static Cursor
232gdk_x11_cursor_create_for_texture (GdkDisplay *display,
233 GdkTexture *texture,
234 int x,
235 int y)
236{
237 return None;
238}
239
240static Cursor
241gdk_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 */
278void
279gdk_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 (dpy: xdisplay);
295 old_size = XcursorGetDefaultSize (dpy: xdisplay);
296
297 if (old_size == size &&
298 (old_theme == theme ||
299 (old_theme && theme && strcmp (s1: old_theme, s2: theme) == 0)))
300 return;
301
302 XcursorSetTheme (dpy: xdisplay, theme);
303 if (size > 0)
304 XcursorSetDefaultSize (dpy: xdisplay, size);
305
306 if (GDK_X11_DISPLAY (display)->cursors == NULL)
307 return;
308
309 g_hash_table_iter_init (iter: &iter, GDK_X11_DISPLAY (display)->cursors);
310 while (g_hash_table_iter_next (iter: &iter, key: &cursor, value: &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 (dpy: xdisplay, source: new_cursor, GDK_POINTER_TO_XID (xcursor));
321 g_hash_table_iter_replace (iter: &iter, GDK_XID_TO_POINTER (new_cursor));
322 }
323 else
324 {
325 g_hash_table_iter_remove (iter: &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 */
347Cursor
348gdk_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 (hash_func: gdk_cursor_hash, key_equal_func: 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, name: gdk_cursor_get_name (cursor));
368 else
369 xcursor = gdk_x11_cursor_create_for_texture (display,
370 texture: gdk_cursor_get_texture (cursor),
371 x: gdk_cursor_get_hotspot_x (cursor),
372 y: gdk_cursor_get_hotspot_y (cursor));
373
374 if (xcursor != None)
375 {
376 g_object_weak_ref (G_OBJECT (cursor), notify: gdk_x11_cursor_remove_from_cache, data: display);
377 g_hash_table_insert (hash_table: x11_display->cursors, key: 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, cursor: gdk_cursor_get_fallback (cursor));
383
384 return None;
385}
386
387

source code of gtk/gdk/x11/gdkcursor-x11.c