From 26c24328d57aa0c7d727008706c5d4aa46dac9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1?= =?UTF-8?q?=D1=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= Date: Wed, 13 May 2015 07:45:40 +0000 Subject: [PATCH] GDK: Add cursor theme support to W32 backend Load themed cursors from the same places they are loaded on freedesktop systems, but use W32 API functions to do so (works for .cur/.ani cursors instead of X cursors). Refactor the code for cursor handling. Prefer loading cursors by name. Do not load actual cursors when loading the theme. Find the files and remember the arguments/calls for loading them instead. Keeping HCURSOR instance in the hashmap would result in multiple GdkCursors using the same HCURSOR. Given that we use DestroyCursor() to off them, this would cause problems (at the very least - DestroyCursor() would fail). Store GdkCursor instances in a cache. Update cached cursors when theme changes. Recognize "system" theme as a special (and default) case. When it is set, prefer system cursors and fall back to Adwaita cursors and (as a last resort) built-in X cursors. Otherwise prefer theme cursors and fall back to system and X cursors. Force GTK to use "left_ptr" cursor when no cursor is set. Using NULL makes it use the system default "arrow", which is not the intended behaviour when a non-system theme is selected. Ignore cursor size setting and query the OS for the required cursor size, as Windows (almost) does not allow setting cursors of arbitrary size. https://bugzilla.gnome.org/show_bug.cgi?id=749287 --- docs/reference/gtk/windows.sgml | 27 ++ gdk/win32/gdkcursor-win32.c | 641 ++++++++++++++++++++++++++++---- gdk/win32/gdkdisplay-win32.c | 102 ++++- gdk/win32/gdkdisplay-win32.h | 5 + gdk/win32/gdkprivate-win32.h | 39 ++ gdk/win32/gdkwin32display.h | 5 + gdk/win32/gdkwindow-win32.c | 5 +- gtk/gtksettings.c | 11 +- 8 files changed, 759 insertions(+), 76 deletions(-) diff --git a/docs/reference/gtk/windows.sgml b/docs/reference/gtk/windows.sgml index f779b86d8a..e00bdbbe74 100644 --- a/docs/reference/gtk/windows.sgml +++ b/docs/reference/gtk/windows.sgml @@ -107,6 +107,33 @@ in 256 color mode. + +Windows-specific handling of cursors + + +By default the "system" cursor theme is used. This makes GTK prefer cursors +that Windows currently uses, falling back to Adwaita cursors and (as the last +resort) built-in X cursors. + + +When any other cursor theme is used, GTK will prefer cursors from that theme, +falling back to Windows cursors and built-in X cursors. + + +Theme can be changed by setting gtk-cursor-theme-name GTK+ setting. Users can override GTK+ settings in the settings.ini file or at runtime in the GTK+ Inspector. + + +Themes are loaded from normal Windows variants of the XDG locations: +%HOME%/icons/THEME/cursors, +%APPDATA%/icons/THEME/cursors, +RUNTIME_PREFIX/share/icons/THEME/cursors. + + +The gtk-cursor-theme-size setting is ignored, GTK will use the cursor size that Windows tells it to use. + + + + More information about GTK+ on Windows, including detailed build instructions, binary downloads, etc, can be found diff --git a/gdk/win32/gdkcursor-win32.c b/gdk/win32/gdkcursor-win32.c index c88d899000..290389b210 100644 --- a/gdk/win32/gdkcursor-win32.c +++ b/gdk/win32/gdkcursor-win32.c @@ -23,34 +23,58 @@ #include "gdkcursor.h" #include "gdkwin32.h" +#include "gdkdisplay-win32.h" + #ifdef __MINGW32__ #include #endif #include "xcursors.h" +typedef struct _DefaultCursor { + char *name; + char *id; +} DefaultCursor; + +static DefaultCursor default_cursors[] = { + { "appstarting", IDC_APPSTARTING }, + { "arrow", IDC_ARROW }, + { "cross", IDC_CROSS }, + { "hand", IDC_HAND }, + { "help", IDC_HELP }, + { "ibeam", IDC_IBEAM }, + /* an X cursor name, for compatibility with GTK: */ + { "left_ptr_watch", IDC_APPSTARTING }, + { "sizeall", IDC_SIZEALL }, + { "sizenesw", IDC_SIZENESW }, + { "sizens", IDC_SIZENS }, + { "sizenwse", IDC_SIZENWSE }, + { "sizewe", IDC_SIZEWE }, + { "uparrow", IDC_UPARROW }, + { "wait", IDC_WAIT }, + /* css cursor names: */ + { "default", IDC_ARROW }, + { "pointer", IDC_HAND }, + { "progress", IDC_APPSTARTING }, + { "crosshair", IDC_CROSS }, + { "text", IDC_IBEAM }, + { "move", IDC_SIZEALL }, + { "not-allowed", IDC_NO }, + { "ew-resize", IDC_SIZEWE }, + { "ns-resize", IDC_SIZENS }, + { "nesw-resize", IDC_SIZENESW }, + { "nwse-resize", IDC_SIZENWSE } +}; + static HCURSOR -hcursor_from_type (GdkCursorType cursor_type) +hcursor_from_x_cursor (gint i, + GdkCursorType cursor_type) { - gint i, j, x, y, ofs; + gint j, x, y, ofs; HCURSOR rv; gint w, h; guchar *and_plane, *xor_plane; - if (cursor_type != GDK_BLANK_CURSOR) - { - for (i = 0; i < G_N_ELEMENTS (cursors); i++) - if (cursors[i].type == cursor_type) - break; - - if (i >= G_N_ELEMENTS (cursors) || !cursors[i].name) - return NULL; - - /* Use real Win32 cursor if possible */ - if (cursors[i].builtin) - return LoadCursor (NULL, cursors[i].builtin); - } - w = GetSystemMetrics (SM_CXCURSOR); h = GetSystemMetrics (SM_CYCURSOR); @@ -79,6 +103,7 @@ hcursor_from_type (GdkCursorType cursor_type) if (data) { RESET_BIT (and_plane[pofs], bit); + if (data == 1) SET_BIT (xor_plane[pofs], bit); } @@ -96,14 +121,307 @@ hcursor_from_type (GdkCursorType cursor_type) rv = CreateCursor (_gdk_app_hmodule, 0, 0, w, h, and_plane, xor_plane); } + if (rv == NULL) WIN32_API_FAILED ("CreateCursor"); + g_free (and_plane); g_free (xor_plane); return rv; } +static HCURSOR +win32_cursor_create_hcursor (Win32Cursor *cursor) +{ + HCURSOR result; + + switch (cursor->load_type) + { + case GDK_WIN32_CURSOR_LOAD_FROM_FILE: + result = LoadImageW (NULL, + cursor->resource_name, + IMAGE_CURSOR, + cursor->width, + cursor->height, + cursor->load_flags); + break; + case GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL: + result = LoadImageA (NULL, + (const gchar *) cursor->resource_name, + IMAGE_CURSOR, + cursor->width, + cursor->height, + cursor->load_flags); + break; + case GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_THIS: + result = LoadImageA (_gdk_app_hmodule, + (const gchar *) cursor->resource_name, + IMAGE_CURSOR, + cursor->width, + cursor->height, + cursor->load_flags); + break; + case GDK_WIN32_CURSOR_CREATE: + result = hcursor_from_x_cursor (cursor->xcursor_number, + cursor->cursor_type); + break; + default: + result = NULL; + } + + return result; +} + +static Win32Cursor * +win32_cursor_new (GdkWin32CursorLoadType load_type, + gpointer resource_name, + gint width, + gint height, + guint load_flags, + gint xcursor_number, + GdkCursorType cursor_type) +{ + Win32Cursor *result; + + result = g_new (Win32Cursor, 1); + result->load_type = load_type; + result->resource_name = resource_name; + result->width = width; + result->height = height; + result->load_flags = load_flags; + result->xcursor_number = xcursor_number; + result->cursor_type = cursor_type; + + return result; +} + + +static void +win32_cursor_destroy (gpointer data) +{ + Win32Cursor *cursor = data; + + /* resource_name could be a resource ID (uint16_t stored as a pointer), + * which shouldn't be freed. + */ + if (cursor->load_type == GDK_WIN32_CURSOR_LOAD_FROM_FILE) + g_free (cursor->resource_name); + + g_free (cursor); +} + +static void +win32_cursor_theme_load_from (Win32CursorTheme *theme, + gint size, + const gchar *dir) +{ + GDir *gdir; + const gchar *filename; + HCURSOR hcursor; + + gdir = g_dir_open (dir, 0, NULL); + + if (gdir == NULL) + return; + + while ((filename = g_dir_read_name (gdir)) != NULL) + { + gchar *fullname; + gunichar2 *filenamew; + gchar *cursor_name; + gchar *dot; + Win32Cursor *cursor; + + fullname = g_strconcat (dir, "/", filename, NULL); + filenamew = g_utf8_to_utf16 (fullname, -1, NULL, NULL, NULL); + g_free (fullname); + + if (filenamew == NULL) + continue; + + hcursor = LoadImageW (NULL, filenamew, IMAGE_CURSOR, size, size, + LR_LOADFROMFILE | (size == 0 ? LR_DEFAULTSIZE : 0)); + + if (hcursor == NULL) + { + g_free (filenamew); + continue; + } + + DestroyCursor (hcursor); + dot = strchr (filename, '.'); + + cursor_name = dot ? g_strndup (filename, dot - filename) : g_strdup (filename); + + cursor = win32_cursor_new (GDK_WIN32_CURSOR_LOAD_FROM_FILE, + filenamew, + size, + size, + LR_LOADFROMFILE | (size == 0 ? LR_DEFAULTSIZE : 0), + 0, + 0); + g_hash_table_insert (theme->named_cursors, cursor_name, cursor); + } +} + +static void +win32_cursor_theme_load_from_dirs (Win32CursorTheme *theme, + const gchar *name, + gint size) +{ + gchar *theme_dir; + const gchar * const *dirs; + gint i; + + dirs = g_get_system_data_dirs (); + + /* /share/icons */ + for (i = 0; dirs[i]; i++) + { + theme_dir = g_strconcat (dirs[i], "/icons/", name, "/cursors", NULL); + win32_cursor_theme_load_from (theme, size, theme_dir); + g_free (theme_dir); + } + + /* ~/.icons */ + theme_dir = g_strconcat (g_get_home_dir (), "/icons/", name, "/cursors", NULL); + win32_cursor_theme_load_from (theme, size, theme_dir); + g_free (theme_dir); +} + +static void +win32_cursor_theme_load_system (Win32CursorTheme *theme, + gint size) +{ + gint i; + HCURSOR hcursor; + Win32Cursor *cursor; + + for (i = 0; i < G_N_ELEMENTS (cursors); i++) + { + + if (cursors[i].name == NULL) + break; + + hcursor = NULL; + + /* Prefer W32 cursors */ + if (cursors[i].builtin) + hcursor = LoadImageA (NULL, cursors[i].builtin, IMAGE_CURSOR, + size, size, + LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0)); + + /* Fall back to X cursors, but only if we've got no theme cursor */ + if (hcursor == NULL && g_hash_table_lookup (theme->named_cursors, cursors[i].name) == NULL) + hcursor = hcursor_from_x_cursor (i, cursors[i].type); + + if (hcursor == NULL) + continue; + + DestroyCursor (hcursor); + cursor = win32_cursor_new (GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL, + (gpointer) cursors[i].builtin, + size, + size, + LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0), + 0, + 0); + g_hash_table_insert (theme->named_cursors, + g_strdup (cursors[i].name), + cursor); + } + + for (i = 0; i < G_N_ELEMENTS (default_cursors); i++) + { + if (default_cursors[i].name == NULL) + break; + + hcursor = LoadImageA (NULL, default_cursors[i].id, IMAGE_CURSOR, size, size, + LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0)); + + if (hcursor == NULL) + continue; + + DestroyCursor (hcursor); + cursor = win32_cursor_new (GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL, + (gpointer) default_cursors[i].id, + size, + size, + LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0), + 0, + 0); + g_hash_table_insert (theme->named_cursors, + g_strdup (default_cursors[i].name), + cursor); + } +} + +Win32CursorTheme * +win32_cursor_theme_load (const gchar *name, + gint size) +{ + Win32CursorTheme *result = g_new0 (Win32CursorTheme, 1); + + result->named_cursors = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + win32_cursor_destroy); + + if (strcmp (name, "system") == 0) + { + win32_cursor_theme_load_from_dirs (result, "Adwaita", size); + win32_cursor_theme_load_system (result, size); + } + else + { + win32_cursor_theme_load_from_dirs (result, name, size); + } + + if (g_hash_table_size (result->named_cursors) > 0) + return result; + + win32_cursor_theme_destroy (result); + return NULL; +} + +void +win32_cursor_theme_destroy (Win32CursorTheme *theme) +{ + g_hash_table_destroy (theme->named_cursors); + g_free (theme); +} + +Win32Cursor * +win32_cursor_theme_get_cursor (Win32CursorTheme *theme, + const gchar *name) +{ + return g_hash_table_lookup (theme->named_cursors, name); +} + +static HCURSOR +hcursor_from_type (GdkCursorType cursor_type) +{ + gint i = 0; + + if (cursor_type != GDK_BLANK_CURSOR) + { + for (i = 0; i < G_N_ELEMENTS (cursors); i++) + if (cursors[i].type == cursor_type) + break; + + if (i >= G_N_ELEMENTS (cursors) || !cursors[i].name) + return NULL; + + /* Use real Win32 cursor if possible */ + if (cursors[i].builtin) + return LoadImageA (NULL, cursors[i].builtin, IMAGE_CURSOR, 0, 0, + LR_SHARED | LR_DEFAULTSIZE); + } + + return hcursor_from_x_cursor (i, cursor_type); +} + struct _GdkWin32CursorClass { GdkCursorClass cursor_class; @@ -120,14 +438,91 @@ _gdk_win32_cursor_finalize (GObject *object) SetCursor (NULL); if (!DestroyCursor (private->hcursor)) - WIN32_API_FAILED ("DestroyCursor"); + g_warning (G_STRLOC ": DestroyCursor (%p) failed: %lu", private->hcursor, GetLastError ()); + + g_free (private->name); G_OBJECT_CLASS (gdk_win32_cursor_parent_class)->finalize (object); } +static HCURSOR +hcursor_idc_from_name (const gchar *name) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (default_cursors); i++) + { + if (strcmp (default_cursors[i].name, name) != 0) + continue; + + return LoadImageA (NULL, default_cursors[i].id, IMAGE_CURSOR, 0, 0, + LR_SHARED | LR_DEFAULTSIZE); + } + + return NULL; +} + +static HCURSOR +hcursor_x_from_name (const gchar *name) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (cursors); i++) + if (cursors[i].name == NULL || strcmp (cursors[i].name, name) == 0) + return hcursor_from_x_cursor (i, cursors[i].type); + + return NULL; +} + +static HCURSOR +hcursor_from_theme (GdkDisplay *display, + const gchar *name) +{ + Win32CursorTheme *theme; + Win32Cursor *theme_cursor; + GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display); + + if (name == NULL) + return NULL; + + theme = _gdk_win32_display_get_cursor_theme (win32_display); + theme_cursor = win32_cursor_theme_get_cursor (theme, name); + + if (theme_cursor == NULL) + return NULL; + + return win32_cursor_create_hcursor (theme_cursor); +} + +static HCURSOR +hcursor_from_name (GdkDisplay *display, + const gchar *name) +{ + HCURSOR hcursor; + + if (strcmp (name, "none") == 0) + return hcursor_from_type (GDK_BLANK_CURSOR); + + /* Try current theme first */ + hcursor = hcursor_from_theme (display, name); + + if (hcursor != NULL) + return hcursor; + + hcursor = hcursor_idc_from_name (name); + + if (hcursor != NULL) + return hcursor; + + hcursor = hcursor_x_from_name (name); + + return hcursor; +} + static GdkCursor* -cursor_new_from_hcursor (HCURSOR hcursor, - GdkCursorType cursor_type) +cursor_new_from_hcursor (HCURSOR hcursor, + const gchar *name, + GdkCursorType cursor_type) { GdkWin32Cursor *private; GdkCursor *cursor; @@ -136,21 +531,120 @@ cursor_new_from_hcursor (HCURSOR hcursor, "cursor-type", cursor_type, "display", _gdk_display, NULL); + + private->name = g_strdup (name); + private->hcursor = hcursor; cursor = (GdkCursor*) private; return cursor; } +static gboolean +_gdk_win32_cursor_update (GdkWin32Display *win32_display, + GdkWin32Cursor *cursor) +{ + HCURSOR hcursor = NULL; + Win32CursorTheme *theme; + Win32Cursor *theme_cursor; + + /* Do nothing if this is not a named cursor. */ + if (cursor->name == NULL) + return FALSE; + + theme = _gdk_win32_display_get_cursor_theme (win32_display); + theme_cursor = win32_cursor_theme_get_cursor (theme, cursor->name); + + if (theme_cursor != NULL) + hcursor = win32_cursor_create_hcursor (theme_cursor); + + if (hcursor == NULL) + { + g_warning (G_STRLOC ": Unable to load %s from the cursor theme", cursor->name); + + hcursor = hcursor_idc_from_name (cursor->name); + + if (hcursor == NULL) + hcursor = hcursor_x_from_name (cursor->name); + + if (hcursor == NULL) + return FALSE; + } + + if (GetCursor () == cursor->hcursor) + SetCursor (hcursor); + + if (!DestroyCursor (cursor->hcursor)) + g_warning (G_STRLOC ": DestroyCursor (%p) failed: %lu", cursor->hcursor, GetLastError ()); + + cursor->hcursor = hcursor; + + return TRUE; +} + +void +_gdk_win32_display_update_cursors (GdkWin32Display *display) +{ + GHashTableIter iter; + const char *name; + GdkWin32Cursor *cursor; + + g_hash_table_iter_init (&iter, display->cursor_cache); + + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &cursor)) + _gdk_win32_cursor_update (display, cursor); +} + +void +_gdk_win32_display_init_cursors (GdkWin32Display *display) +{ + display->cursor_cache = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); + display->cursor_theme_name = g_strdup ("system"); +} + +void +_gdk_win32_display_finalize_cursors (GdkWin32Display *display) +{ + g_free (display->cursor_theme_name); + + if (display->cursor_theme) + win32_cursor_theme_destroy (display->cursor_theme); + + g_hash_table_destroy (display->cursor_cache); +} + + GdkCursor* _gdk_win32_display_get_cursor_for_type (GdkDisplay *display, GdkCursorType cursor_type) { + GEnumClass *enum_class; + GEnumValue *enum_value; + gchar *cursor_name; HCURSOR hcursor; + GdkCursor *result; + GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display); - g_return_val_if_fail (display == _gdk_display, NULL); + enum_class = g_type_class_ref (GDK_TYPE_CURSOR_TYPE); + enum_value = g_enum_get_value (enum_class, cursor_type); + cursor_name = g_strdup (enum_value->value_nick); + g_strdelimit (cursor_name, "-", '_'); + g_type_class_unref (enum_class); - hcursor = hcursor_from_type (cursor_type); + result = g_hash_table_lookup (win32_display->cursor_cache, cursor_name); + if (result) + { + g_free (cursor_name); + return g_object_ref (result); + } + + hcursor = hcursor_from_name (display, cursor_name); + + if (hcursor == NULL) + hcursor = hcursor_from_type (cursor_type); if (hcursor == NULL) g_warning ("gdk_cursor_new_for_display: no cursor %d found", cursor_type); @@ -158,67 +652,62 @@ _gdk_win32_display_get_cursor_for_type (GdkDisplay *display, GDK_NOTE (CURSOR, g_print ("gdk_cursor_new_for_display: %d: %p\n", cursor_type, hcursor)); - return cursor_new_from_hcursor (hcursor, cursor_type); + result = cursor_new_from_hcursor (hcursor, cursor_name, cursor_type); + + if (result == NULL) + return result; + + /* Blank cursor case */ + if (cursor_type == GDK_BLANK_CURSOR || + !cursor_name || + g_str_equal (cursor_name, "none") || + g_str_equal (cursor_name, "blank_cursor")) + { + g_free (cursor_name); + return result; + } + + g_hash_table_insert (win32_display->cursor_cache, + cursor_name, + g_object_ref (result)); + + return result; } -/* FIXME: The named cursors below are presumably not really useful, as - * the names are Win32-specific. No GTK+ application developed on Unix - * (and most cross-platform GTK+ apps are developed on Unix) is going - * to look for cursors under these Win32 names anyway. - * - * Would the following make any sense: The ms-windows theme engine - * calls some (to-be-defined private) API here in gdk/win32 to - * register the relevant cursors used by the currently active XP - * visual style under the names that libgtk uses to look for them - * ("color-picker", "dnd-ask", "dnd-copy", etc), and then when libgtk - * asks for those we return the ones registered by the ms-windows - * theme engine, if any. - */ - -static struct { - char *name; - char *id; -} default_cursors[] = { - { "appstarting", IDC_APPSTARTING }, - { "arrow", IDC_ARROW }, - { "cross", IDC_CROSS }, -#ifdef IDC_HAND - { "hand", IDC_HAND }, -#endif - { "help", IDC_HELP }, - { "ibeam", IDC_IBEAM }, - { "left_ptr_watch", IDC_APPSTARTING }, - { "sizeall", IDC_SIZEALL }, - { "sizenesw", IDC_SIZENESW }, - { "sizens", IDC_SIZENS }, - { "sizenwse", IDC_SIZENWSE }, - { "sizewe", IDC_SIZEWE }, - { "uparrow", IDC_UPARROW }, - { "wait", IDC_WAIT } -}; - GdkCursor* _gdk_win32_display_get_cursor_for_name (GdkDisplay *display, const gchar *name) { HCURSOR hcursor = NULL; - int i; + GdkCursor *result; + GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display); - g_return_val_if_fail (display == _gdk_display, NULL); + result = g_hash_table_lookup (win32_display->cursor_cache, name); + if (result) + return g_object_ref (result); + + hcursor = hcursor_from_name (display, name); - for (i = 0; i < G_N_ELEMENTS(default_cursors); i++) - { - if (0 == strcmp(default_cursors[i].name, name)) - hcursor = LoadCursor (NULL, default_cursors[i].id); - } /* allow to load named cursor resources linked into the executable */ if (!hcursor) hcursor = LoadCursor (_gdk_app_hmodule, name); - if (hcursor) - return cursor_new_from_hcursor (hcursor, GDK_X_CURSOR); + if (hcursor == NULL) + return NULL; - return NULL; + result = cursor_new_from_hcursor (hcursor, name, GDK_X_CURSOR); + + /* Blank cursor case */ + if (!name || + g_str_equal (name, "none") || + g_str_equal (name, "blank_cursor")) + return result; + + g_hash_table_insert (win32_display->cursor_cache, + g_strdup (name), + g_object_ref (result)); + + return result; } GdkPixbuf * @@ -419,10 +908,10 @@ _gdk_win32_cursor_get_surface (GdkCursor *cursor, } GdkCursor * -_gdk_win32_display_get_cursor_for_surface (GdkDisplay *display, - cairo_surface_t *surface, - gdouble x, - gdouble y) +_gdk_win32_display_get_cursor_for_surface (GdkDisplay *display, + cairo_surface_t *surface, + gdouble x, + gdouble y) { HCURSOR hcursor; GdkPixbuf *pixbuf; @@ -448,7 +937,7 @@ _gdk_win32_display_get_cursor_for_surface (GdkDisplay *display, g_object_unref (pixbuf); if (!hcursor) return NULL; - return cursor_new_from_hcursor (hcursor, GDK_CURSOR_IS_PIXMAP); + return cursor_new_from_hcursor (hcursor, NULL, GDK_CURSOR_IS_PIXMAP); } gboolean @@ -474,6 +963,12 @@ _gdk_win32_display_get_default_cursor_size (GdkDisplay *display, { g_return_if_fail (display == _gdk_display); + /* TODO: Use per-monitor DPI functions (8.1 and newer) or + * calculate DPI ourselves and use that, assuming that 72 dpi + * corresponds to 32x32 cursors. Take into account that DPI + * can be artificially increased by the user to make stuff bigger. + */ + if (width) *width = GetSystemMetrics (SM_CXCURSOR); if (height) diff --git a/gdk/win32/gdkdisplay-win32.c b/gdk/win32/gdkdisplay-win32.c index 8a7ca4c48f..906f0bd63f 100644 --- a/gdk/win32/gdkdisplay-win32.c +++ b/gdk/win32/gdkdisplay-win32.c @@ -26,6 +26,100 @@ #include "gdkwin32window.h" #include "gdkwin32.h" +/** + * gdk_win32_display_set_cursor_theme: + * @display: (type GdkWin32Display): a #GdkDisplay + * @theme: (allow-none) 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 gdk_cursor_new(), gdk_cursor_new_for_display() and + * gdk_cursor_new_from_name() are updated to reflect the theme + * change. Custom cursors constructed with + * gdk_cursor_new_from_pixbuf() 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). + * + * Since: 3.18 + */ +void +gdk_win32_display_set_cursor_theme (GdkDisplay *display, + const gchar *name, + const gint size) +{ + gint cursor_size; + gint w, h; + Win32CursorTheme *theme; + GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display); + + g_assert (win32_display); + + if (name == NULL) + name = "system"; + + w = GetSystemMetrics (SM_CXCURSOR); + h = GetSystemMetrics (SM_CYCURSOR); + + /* We can load cursors of any size, but SetCursor() will scale them back + * to this value. It's possible to break that restrictions with SetSystemCursor(), + * but that will override cursors for the whole desktop session. + */ + cursor_size = (w == h) ? w : size; + + if (win32_display->cursor_theme_name != NULL && + g_strcmp0 (name, win32_display->cursor_theme_name) == 0 && + win32_display->cursor_theme_size == cursor_size) + return; + + theme = win32_cursor_theme_load (name, cursor_size); + if (theme == NULL) + { + g_warning ("Failed to load cursor theme %s", name); + return; + } + + if (win32_display->cursor_theme) + { + win32_cursor_theme_destroy (win32_display->cursor_theme); + win32_display->cursor_theme = NULL; + } + + win32_display->cursor_theme = theme; + g_free (win32_display->cursor_theme_name); + win32_display->cursor_theme_name = g_strdup (name); + win32_display->cursor_theme_size = cursor_size; + + _gdk_win32_display_update_cursors (win32_display); +} + +Win32CursorTheme * +_gdk_win32_display_get_cursor_theme (GdkWin32Display *win32_display) +{ + Win32CursorTheme *theme; + + g_assert (win32_display->cursor_theme_name); + + theme = win32_display->cursor_theme; + if (!theme) + { + theme = win32_cursor_theme_load (win32_display->cursor_theme_name, + win32_display->cursor_theme_size); + if (theme == NULL) + { + g_warning ("Failed to load cursor theme %s", + win32_display->cursor_theme_name); + return NULL; + } + win32_display->cursor_theme = theme; + } + + return theme; +} static gulong gdk_win32_display_get_next_serial (GdkDisplay *display) @@ -542,11 +636,17 @@ gdk_win32_display_dispose (GObject *object) static void gdk_win32_display_finalize (GObject *object) { + GdkWin32Display *display_win32 = GDK_WIN32_DISPLAY (object); + + _gdk_win32_display_finalize_cursors (display_win32); + + G_OBJECT_CLASS (gdk_win32_display_parent_class)->finalize (object); } static void -gdk_win32_display_init(GdkWin32Display *display) +gdk_win32_display_init (GdkWin32Display *display) { + _gdk_win32_display_init_cursors (display); } static void diff --git a/gdk/win32/gdkdisplay-win32.h b/gdk/win32/gdkdisplay-win32.h index 1623c7eb72..585d958890 100644 --- a/gdk/win32/gdkdisplay-win32.h +++ b/gdk/win32/gdkdisplay-win32.h @@ -26,6 +26,11 @@ struct _GdkWin32Display { GdkDisplay display; + Win32CursorTheme *cursor_theme; + gchar *cursor_theme_name; + int cursor_theme_size; + GHashTable *cursor_cache; + /* WGL/OpenGL Items */ guint have_wgl : 1; guint gl_version; diff --git a/gdk/win32/gdkprivate-win32.h b/gdk/win32/gdkprivate-win32.h index 98b64a3edb..a94a1885e2 100644 --- a/gdk/win32/gdkprivate-win32.h +++ b/gdk/win32/gdkprivate-win32.h @@ -37,6 +37,7 @@ #include #include #include +#include #include "gdkinternals.h" @@ -114,6 +115,8 @@ typedef struct _GdkWin32SingleFont GdkWin32SingleFont; struct _GdkWin32Cursor { GdkCursor cursor; + + gchar *name; HCURSOR hcursor; }; @@ -374,6 +377,42 @@ HICON _gdk_win32_pixbuf_to_hcursor (GdkPixbuf *pixbuf, gint x_hotspot, gint y_hotspot); +void _gdk_win32_display_init_cursors (GdkWin32Display *display); +void _gdk_win32_display_finalize_cursors (GdkWin32Display *display); +void _gdk_win32_display_update_cursors (GdkWin32Display *display); + +typedef struct _Win32CursorTheme Win32CursorTheme; + +struct _Win32CursorTheme { + GHashTable *named_cursors; +}; + +typedef enum GdkWin32CursorLoadType { + GDK_WIN32_CURSOR_LOAD_FROM_FILE = 0, + GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL = 1, + GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_THIS = 2, + GDK_WIN32_CURSOR_CREATE = 3, +} GdkWin32CursorLoadType; + +typedef struct _Win32Cursor Win32Cursor; + +struct _Win32Cursor { + GdkWin32CursorLoadType load_type; + gunichar2 *resource_name; + gint width; + gint height; + guint load_flags; + gint xcursor_number; + GdkCursorType cursor_type; +}; + +Win32CursorTheme *win32_cursor_theme_load (const gchar *name, + gint size); +Win32Cursor * win32_cursor_theme_get_cursor (Win32CursorTheme *theme, + const gchar *name); +void win32_cursor_theme_destroy (Win32CursorTheme *theme); +Win32CursorTheme *_gdk_win32_display_get_cursor_theme (GdkWin32Display *win32_display); + /* GdkDisplay member functions */ GdkCursor *_gdk_win32_display_get_cursor_for_type (GdkDisplay *display, GdkCursorType cursor_type); diff --git a/gdk/win32/gdkwin32display.h b/gdk/win32/gdkwin32display.h index 916c6fa0ff..84d51fde37 100644 --- a/gdk/win32/gdkwin32display.h +++ b/gdk/win32/gdkwin32display.h @@ -50,6 +50,11 @@ typedef struct _GdkWin32DisplayClass GdkWin32DisplayClass; GDK_AVAILABLE_IN_ALL GType gdk_win32_display_get_type (void); +GDK_AVAILABLE_IN_3_18 +void gdk_win32_display_set_cursor_theme (GdkDisplay *display, + const gchar *theme, + gint size); + G_END_DECLS #endif /* __GDK_WIN32_DISPLAY_H__ */ diff --git a/gdk/win32/gdkwindow-win32.c b/gdk/win32/gdkwindow-win32.c index 765ae01071..88a9478bde 100644 --- a/gdk/win32/gdkwindow-win32.c +++ b/gdk/win32/gdkwindow-win32.c @@ -1972,7 +1972,10 @@ gdk_win32_window_set_device_cursor (GdkWindow *window, if (cursor) impl->cursor = g_object_ref (cursor); else - impl->cursor = NULL; + /* Use default cursor otherwise. Setting it to NULL will make it use + * system-default cursor, which is not controlled by GTK cursor theming. + */ + impl->cursor = _gdk_win32_display_get_cursor_for_type (_gdk_display, GDK_LEFT_PTR); /* Destroy the previous cursor */ if (previous_cursor != NULL) diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c index 2356715cec..e754e8712f 100644 --- a/gtk/gtksettings.c +++ b/gtk/gtksettings.c @@ -51,6 +51,10 @@ #include "quartz/gdkquartz.h" #endif +#ifdef GDK_WINDOWING_WIN32 +#include "win32/gdkwin32.h" +#endif + #ifdef G_OS_WIN32 #include "gtkwin32themeprivate.h" #endif @@ -2918,7 +2922,7 @@ settings_update_cursor_theme (GtkSettings *settings) { gchar *theme = NULL; gint size = 0; -#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND) +#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND) || defined(GDK_WINDOWING_WIN32) GdkDisplay *display = gdk_screen_get_display (settings->priv->screen); #endif @@ -2937,6 +2941,11 @@ settings_update_cursor_theme (GtkSettings *settings) if (GDK_IS_WAYLAND_DISPLAY (display)) gdk_wayland_display_set_cursor_theme (display, theme, size); else +#endif +#ifdef GDK_WINDOWING_WIN32 + if (GDK_IS_WIN32_DISPLAY (display)) + gdk_win32_display_set_cursor_theme (display, theme, size); + else #endif g_warning ("GtkSettings Cursor Theme: Unsupported GDK backend\n"); g_free (theme);