[gtk+] GDK: Add cursor theme support to W32 backend



commit 26c24328d57aa0c7d727008706c5d4aa46dac9b1
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Wed May 13 07:45:40 2015 +0000

    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     |  639 ++++++++++++++++++++++++++++++++++-----
 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, 758 insertions(+), 75 deletions(-)
---
diff --git a/docs/reference/gtk/windows.sgml b/docs/reference/gtk/windows.sgml
index f779b86..e00bdbb 100644
--- a/docs/reference/gtk/windows.sgml
+++ b/docs/reference/gtk/windows.sgml
@@ -107,6 +107,33 @@ in 256 color mode.
 
 </refsect2>
 
+<refsect2 id="win32-cursors">
+<title>Windows-specific handling of cursors</title>
+
+<para>
+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.
+</para>
+<para>
+When any other cursor theme is used, GTK will prefer cursors from that theme,
+falling back to Windows cursors and built-in X cursors.
+</para>
+<para>
+Theme can be changed by setting <literal>gtk-cursor-theme-name</literal> GTK+ setting. Users can override 
GTK+ settings in the <filename>settings.ini</filename> file or at runtime in the GTK+ Inspector.
+</para>
+<para>
+Themes are loaded from normal Windows variants of the XDG locations:
+<filename>%HOME%/icons/THEME/cursors</filename>, 
+<filename>%APPDATA%/icons/THEME/cursors</filename>, 
+<filename>RUNTIME_PREFIX/share/icons/THEME/cursors</filename>.
+</para>
+<para>
+The <literal>gtk-cursor-theme-size</literal> setting is ignored, GTK will use the cursor size that Windows 
tells it to use.
+</para>
+
+</refsect2>
+
 <para>
 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 c88d899..290389b 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 <w32api.h>
 #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 ();
+
+  /* <prefix>/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);
+
+  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);
 
-  hcursor = hcursor_from_type (cursor_type);
+  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);
 
-/* 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.
- */
+  if (result == NULL)
+    return result;
 
-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 }
-};
+  /* 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;
+}
 
 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 8a7ca4c..906f0bd 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 1623c7e..585d958 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 98b64a3..a94a188 100644
--- a/gdk/win32/gdkprivate-win32.h
+++ b/gdk/win32/gdkprivate-win32.h
@@ -37,6 +37,7 @@
 #include <gdk/gdkprivate.h>
 #include <gdk/gdkcursorprivate.h>
 #include <gdk/win32/gdkwindow-win32.h>
+#include <gdk/win32/gdkwin32display.h>
 
 #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 916c6fa..84d51fd 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 765ae01..88a9478 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 2356715..e754e87 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
 
@@ -2938,6 +2942,11 @@ settings_update_cursor_theme (GtkSettings *settings)
     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);
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]