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);