/* GDK - The GIMP Drawing Kit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * 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 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 . */ /* * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include "config.h" /* needs to be first because any header might include gdk-pixbuf.h otherwise */ #define GDK_PIXBUF_ENABLE_BACKEND #include #include "gdkcursor.h" #include "gdkcursorprivate.h" #include "gdkprivate-x11.h" #include "gdkdisplay-x11.h" #include #include #ifdef HAVE_XCURSOR #include #endif #ifdef HAVE_XFIXES #include #endif #include static void gdk_x11_cursor_remove_from_cache (gpointer data, GObject *cursor) { GdkDisplay *display = data; Cursor xcursor; xcursor = GDK_POINTER_TO_XID (g_hash_table_lookup (GDK_X11_DISPLAY (display)->cursors, cursor)); XFreeCursor (GDK_DISPLAY_XDISPLAY (display), xcursor); g_hash_table_remove (GDK_X11_DISPLAY (display)->cursors, cursor); } void _gdk_x11_cursor_display_finalize (GdkDisplay *display) { GHashTableIter iter; gpointer cursor; if (GDK_X11_DISPLAY (display)->cursors) { g_hash_table_iter_init (&iter, GDK_X11_DISPLAY (display)->cursors); while (g_hash_table_iter_next (&iter, &cursor, NULL)) g_object_weak_unref (G_OBJECT (cursor), gdk_x11_cursor_remove_from_cache, display); g_hash_table_unref (GDK_X11_DISPLAY (display)->cursors); } } static Cursor get_blank_cursor (GdkDisplay *display) { Pixmap pixmap; XColor color; Cursor cursor; cairo_surface_t *surface; cairo_t *cr; surface = _gdk_x11_display_create_bitmap_surface (display, 1, 1); /* Clear surface */ cr = cairo_create (surface); cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); cairo_paint (cr); cairo_destroy (cr); pixmap = cairo_xlib_surface_get_drawable (surface); color.pixel = 0; color.red = color.blue = color.green = 0; if (gdk_display_is_closed (display)) cursor = None; else cursor = XCreatePixmapCursor (GDK_DISPLAY_XDISPLAY (display), pixmap, pixmap, &color, &color, 1, 1); cairo_surface_destroy (surface); return cursor; } static const struct { const char *css_name; const char *traditional_name; int cursor_glyph; } name_map[] = { { "default", "left_ptr", XC_left_ptr, }, { "help", "question_arrow", XC_question_arrow }, { "context-menu", "left_ptr", XC_left_ptr }, { "pointer", "hand", XC_hand1 }, { "progress", "left_ptr_watch", XC_watch }, { "wait", "watch", XC_watch }, { "cell", "crosshair", XC_plus }, { "crosshair", "cross", XC_crosshair }, { "text", "xterm", XC_xterm }, { "vertical-text","xterm", XC_xterm }, { "alias", "dnd-link", XC_target }, { "copy", "dnd-copy", XC_target }, { "move", "dnd-move", XC_target }, { "no-drop", "dnd-none", XC_pirate }, { "dnd-ask", "dnd-copy", XC_target }, /* not CSS, but we want to guarantee it anyway */ { "not-allowed", "crossed_circle", XC_pirate }, { "grab", "hand2", XC_hand2 }, { "grabbing", "hand2", XC_hand2 }, { "all-scroll", "left_ptr", XC_left_ptr }, { "col-resize", "h_double_arrow", XC_sb_h_double_arrow }, { "row-resize", "v_double_arrow", XC_sb_v_double_arrow }, { "n-resize", "top_side", XC_top_side }, { "e-resize", "right_side", XC_right_side }, { "s-resize", "bottom_side", XC_bottom_side }, { "w-resize", "left_side", XC_left_side }, { "ne-resize", "top_right_corner", XC_top_right_corner }, { "nw-resize", "top_left_corner", XC_top_left_corner }, { "se-resize", "bottom_right_corner", XC_bottom_right_corner }, { "sw-resize", "bottom_left_corner", XC_bottom_left_corner }, { "ew-resize", "h_double_arrow", XC_sb_h_double_arrow }, { "ns-resize", "v_double_arrow", XC_sb_v_double_arrow }, { "nesw-resize", "fd_double_arrow", XC_X_cursor }, { "nwse-resize", "bd_double_arrow", XC_X_cursor }, { "zoom-in", "left_ptr", XC_draped_box }, { "zoom-out", "left_ptr", XC_draped_box } }; #ifdef HAVE_XCURSOR static XcursorImage* create_cursor_image (GdkTexture *texture, int x, int y, int scale) { XcursorImage *xcimage; xcimage = XcursorImageCreate (gdk_texture_get_width (texture), gdk_texture_get_height (texture)); xcimage->xhot = x; xcimage->yhot = y; gdk_texture_download (texture, (guchar *) xcimage->pixels, gdk_texture_get_width (texture) * 4); return xcimage; } static Cursor gdk_x11_cursor_create_for_texture (GdkDisplay *display, GdkTexture *texture, int x, int y) { XcursorImage *xcimage; Cursor xcursor; int target_scale; target_scale = gdk_monitor_get_scale_factor (gdk_x11_display_get_primary_monitor (display)); xcimage = create_cursor_image (texture, x, y, target_scale); xcursor = XcursorImageLoadCursor (GDK_DISPLAY_XDISPLAY (display), xcimage); XcursorImageDestroy (xcimage); return xcursor; } static const char * name_fallback (const char *name) { int i; for (i = 0; i < G_N_ELEMENTS (name_map); i++) { if (g_str_equal (name_map[i].css_name, name)) return name_map[i].traditional_name; } return NULL; } static Cursor gdk_x11_cursor_create_for_name (GdkDisplay *display, const char *name) { Cursor xcursor; Display *xdisplay; if (strcmp (name, "none") == 0) { xcursor = get_blank_cursor (display); } else { xdisplay = GDK_DISPLAY_XDISPLAY (display); xcursor = XcursorLibraryLoadCursor (xdisplay, name); if (xcursor == None) { const char *fallback; fallback = name_fallback (name); if (fallback) xcursor = XcursorLibraryLoadCursor (xdisplay, fallback); } } return xcursor; } #else static Cursor gdk_x11_cursor_create_for_texture (GdkDisplay *display, GdkTexture *texture, int x, int y) { return None; } static Cursor gdk_x11_cursor_create_for_name (GdkDisplay *display, const char *name) { int i; if (g_str_equal (name, "none")) return get_blank_cursor (display); for (i = 0; i < G_N_ELEMENTS (name_map); i++) { if (g_str_equal (name_map[i].css_name, name) || g_str_equal (name_map[i].traditional_name, name)) return XCreateFontCursor (GDK_DISPLAY_XDISPLAY (display), name_map[i].cursor_glyph); } return None; } #endif /** * gdk_x11_display_set_cursor_theme: * @display: (type GdkX11Display): a `GdkDisplay` * @theme: (nullable): the name of the cursor theme to use, or %NULL * to unset a previously set value * @size: the cursor size to use, or 0 to keep the previous size * * Sets the cursor theme from which the images for cursor * should be taken. * * If the windowing system supports it, existing cursors created * with [ctor@Gdk.Cursor.new_from_name] are updated to reflect the theme * change. Custom cursors constructed with [ctor@Gdk.Cursor.new_from_texture] * will have to be handled by the application (GTK applications can learn * about cursor theme changes by listening for change notification * for the corresponding `GtkSetting`). */ void gdk_x11_display_set_cursor_theme (GdkDisplay *display, const char *theme, const int size) { #if defined(HAVE_XCURSOR) && defined(HAVE_XFIXES) && XFIXES_MAJOR >= 2 GdkX11Screen *x11_screen; Display *xdisplay; char *old_theme; int real_size; int old_size; gpointer cursor, xcursor; GHashTableIter iter; g_return_if_fail (GDK_IS_DISPLAY (display)); x11_screen = gdk_x11_display_get_screen (display); xdisplay = GDK_DISPLAY_XDISPLAY (display); real_size = size * x11_screen->surface_scale; old_theme = XcursorGetTheme (xdisplay); old_size = XcursorGetDefaultSize (xdisplay); if (old_size == real_size && (old_theme == theme || (old_theme && theme && strcmp (old_theme, theme) == 0))) return; XcursorSetTheme (xdisplay, theme); if (real_size > 0) XcursorSetDefaultSize (xdisplay, real_size); if (GDK_X11_DISPLAY (display)->cursors == NULL) return; g_hash_table_iter_init (&iter, GDK_X11_DISPLAY (display)->cursors); while (g_hash_table_iter_next (&iter, &cursor, &xcursor)) { const char *name = gdk_cursor_get_name (cursor); if (name) { Cursor new_cursor = gdk_x11_cursor_create_for_name (display, name); if (new_cursor != None) { XFixesChangeCursor (xdisplay, new_cursor, GDK_POINTER_TO_XID (xcursor)); g_hash_table_iter_replace (&iter, GDK_XID_TO_POINTER (new_cursor)); } else { g_hash_table_iter_remove (&iter); } } } #endif } /** * gdk_x11_display_get_xcursor: * @display: (type GdkX11Display): a `GdkDisplay` * @cursor: a `GdkCursor` * * Returns the X cursor belonging to a `GdkCursor`, potentially * creating the cursor. * * Be aware that the returned cursor may not be unique to @cursor. * It may for example be shared with its fallback cursor. On old * X servers that don't support the XCursor extension, all cursors * may even fall back to a few default cursors. * * Returns: an Xlib Cursor. */ Cursor gdk_x11_display_get_xcursor (GdkDisplay *display, GdkCursor *cursor) { GdkX11Display *x11_display = GDK_X11_DISPLAY (display); Cursor xcursor; g_return_val_if_fail (cursor != NULL, None); if (gdk_display_is_closed (display)) return None; if (x11_display->cursors == NULL) x11_display->cursors = g_hash_table_new (gdk_cursor_hash, gdk_cursor_equal); xcursor = GDK_POINTER_TO_XID (g_hash_table_lookup (x11_display->cursors, cursor)); if (xcursor) return xcursor; if (gdk_cursor_get_name (cursor)) xcursor = gdk_x11_cursor_create_for_name (display, gdk_cursor_get_name (cursor)); else xcursor = gdk_x11_cursor_create_for_texture (display, gdk_cursor_get_texture (cursor), gdk_cursor_get_hotspot_x (cursor), gdk_cursor_get_hotspot_y (cursor)); if (xcursor != None) { g_object_weak_ref (G_OBJECT (cursor), gdk_x11_cursor_remove_from_cache, display); g_hash_table_insert (x11_display->cursors, cursor, GDK_XID_TO_POINTER (xcursor)); return xcursor; } if (gdk_cursor_get_fallback (cursor)) return gdk_x11_display_get_xcursor (display, gdk_cursor_get_fallback (cursor)); return None; }