From d8da6d38db4732201bd961c8cc120197fbf1b1bd 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: Thu, 29 Mar 2018 23:38:05 +0000 Subject: [PATCH] GDK W32: New cursor class Instead of now-unused GdkWin32Cursor class (a subclass of GdkCursor), add a stand-alone GdkWin32HCursor class that is a wrapper around HCURSOR handle. On creation it's given a display instance, a HCURSOR handle and a boolean that indicates whether the HCURSOR handle can or cannot be destroyed (this depends on how the handle was obtained). That information is stored in a hash table inside the GdkWin32Display singleton, each entry of that table has reference count. When the GdkWin32HCursor object is finalized, it reduces the reference count on the table entry in the GdkWin32Display. When it's created, it either adds such an entry or refs an existing one. This way two pieces of code (or the same piece of code called multiple times) that independently obtain the same HCURSOR from the OS will get to different GdkWin32HCursor instances, but GdkWin32Display will know that both use the same handle. Once the reference count reaches 0 on the table entry, it is freed and the handle (if destroyable) is put on the destruction list, and an idle destruction function is queued. If the same handle is once again registered for use before the idle destructior is invoked (this happens, for example, when an old cursor is destroyed and then replaced with a new one), the handle gets removed from the destruction list. The destructor just calls DestroyCursor() on each handle, calling SetCursor(NULL) before doing that when the handle is in use. This ensures that SetCursor(NULL) (which will cause cursor to disappear, which is bad by itself, and which will also cause flickering if the cursor is set to a non-NULL again shortly afterward) is almost never called, unless GTK messes up and keeps using a cursor beyond its lifetime. This scheme also ensures that non-destructable cursors are not destroyed. It's also possible to call _gdk_win32_display_hcursor_ref() and _gdk_win32_display_hcursor_unref() manually instead of creating GdkWin32HCursor objects, but that is not recommended. --- gdk/win32/gdkcursor-win32.c | 304 +++++++++++++++++++++++++++++++++++ gdk/win32/gdkdisplay-win32.h | 8 + gdk/win32/gdkprivate-win32.h | 7 - gdk/win32/gdkwin32cursor.h | 57 +++++-- 4 files changed, 356 insertions(+), 20 deletions(-) diff --git a/gdk/win32/gdkcursor-win32.c b/gdk/win32/gdkcursor-win32.c index 791ec00e9b..5490322e42 100644 --- a/gdk/win32/gdkcursor-win32.c +++ b/gdk/win32/gdkcursor-win32.c @@ -22,6 +22,7 @@ #include "gdkcursor.h" #include "gdkwin32.h" #include "gdktextureprivate.h" +#include "gdkintl.h" #include "gdkdisplay-win32.h" @@ -76,6 +77,309 @@ static DefaultCursor default_cursors[] = { { "se-resize", IDC_SIZENWSE } }; +typedef struct _GdkWin32HCursorTableEntry GdkWin32HCursorTableEntry; + +struct _GdkWin32HCursorTableEntry +{ + HCURSOR handle; + guint64 refcount; + gboolean destroyable; +}; + +struct _GdkWin32HCursor +{ + GObject parent_instance; + + /* Do not do any modifications to the handle + * (i.e. do not call DestroyCursor() on it). + * It's a "read-only" copy, the original is stored + * in the display instance. + */ + HANDLE readonly_handle; + + /* This is a way to access the real handle stored + * in the display. + * TODO: make it a weak reference + */ + GdkWin32Display *display; + + /* A copy of the "destoyable" attribute of the handle */ + gboolean readonly_destroyable; +}; + +struct _GdkWin32HCursorClass +{ + GObjectClass parent_class; +}; + +enum +{ + PROP_0, + PROP_DISPLAY, + PROP_HANDLE, + PROP_DESTROYABLE, + NUM_PROPERTIES +}; + +G_DEFINE_TYPE (GdkWin32HCursor, gdk_win32_hcursor, G_TYPE_OBJECT) + +static void +gdk_win32_hcursor_init (GdkWin32HCursor *win32_hcursor) +{ +} + +static void +gdk_win32_hcursor_finalize (GObject *gobject) +{ + GdkWin32HCursor *win32_hcursor = GDK_WIN32_HCURSOR (gobject); + + if (win32_hcursor->display) + _gdk_win32_display_hcursor_unref (win32_hcursor->display, win32_hcursor->readonly_handle); + + g_clear_object (&win32_hcursor->display); + + G_OBJECT_CLASS (gdk_win32_hcursor_parent_class)->finalize (G_OBJECT (win32_hcursor)); +} + +static void +gdk_win32_hcursor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdkWin32HCursor *win32_hcursor; + + win32_hcursor = GDK_WIN32_HCURSOR (object); + + switch (prop_id) + { + case PROP_DISPLAY: + g_set_object (&win32_hcursor->display, g_value_get_object (value)); + break; + + case PROP_DESTROYABLE: + win32_hcursor->readonly_destroyable = g_value_get_boolean (value); + break; + + case PROP_HANDLE: + win32_hcursor->readonly_handle = g_value_get_pointer (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +gdk_win32_hcursor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdkWin32HCursor *win32_hcursor; + + win32_hcursor = GDK_WIN32_HCURSOR (object); + + switch (prop_id) + { + case PROP_DISPLAY: + g_value_set_object (value, win32_hcursor->display); + break; + + case PROP_DESTROYABLE: + g_value_set_boolean (value, win32_hcursor->readonly_destroyable); + break; + + case PROP_HANDLE: + g_value_set_pointer (value, win32_hcursor->readonly_handle); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdk_win32_hcursor_constructed (GObject *object) +{ + GdkWin32HCursor *win32_hcursor; + + win32_hcursor = GDK_WIN32_HCURSOR (object); + + g_assert_nonnull (win32_hcursor->display); + g_assert_nonnull (win32_hcursor->readonly_handle); + + _gdk_win32_display_hcursor_ref (win32_hcursor->display, + win32_hcursor->readonly_handle, + win32_hcursor->readonly_destroyable); +} + +static GParamSpec *hcursor_props[NUM_PROPERTIES] = { NULL, }; + +static void +gdk_win32_hcursor_class_init (GdkWin32HCursorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gdk_win32_hcursor_finalize; + object_class->constructed = gdk_win32_hcursor_constructed; + object_class->get_property = gdk_win32_hcursor_get_property; + object_class->set_property = gdk_win32_hcursor_set_property; + + hcursor_props[PROP_DISPLAY] = + g_param_spec_object ("display", + P_("Display"), + P_("The display that will use this cursor"), + GDK_TYPE_DISPLAY, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + hcursor_props[PROP_HANDLE] = + g_param_spec_pointer ("handle", + P_("Handle"), + P_("The HCURSOR handle for this cursor"), + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + hcursor_props[PROP_DESTROYABLE] = + g_param_spec_boolean ("destroyable", + P_("Destroyable"), + P_("Whether calling DestroyCursor() is allowed on this cursor"), + TRUE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, hcursor_props); +} + +GdkWin32HCursor * +gdk_win32_hcursor_new (GdkWin32Display *display, + HCURSOR handle, + gboolean destroyable) +{ + return g_object_new (GDK_TYPE_WIN32_HCURSOR, + "display", display, + "handle", handle, + "destroyable", destroyable, + NULL); +} + +void +_gdk_win32_display_hcursor_ref (GdkWin32Display *display, + HCURSOR handle, + gboolean destroyable) +{ + GdkWin32HCursorTableEntry *entry; + + entry = g_hash_table_lookup (display->cursor_reftable, handle); + + if (entry) + { + if (entry->destroyable != destroyable) + g_warning ("Destroyability metadata for cursor handle 0x%p does not match", handle); + + entry->refcount += 1; + + return; + } + + entry = g_new0 (GdkWin32HCursorTableEntry, 1); + entry->handle = handle; + entry->destroyable = destroyable; + entry->refcount = 1; + + g_hash_table_insert (display->cursor_reftable, handle, entry); + display->cursors_for_destruction = g_list_remove_all (display->cursors_for_destruction, handle); +} + +static gboolean +delayed_cursor_destruction (gpointer user_data) +{ + GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (user_data); + HANDLE current_hcursor = GetCursor (); + GList *p; + + win32_display->idle_cursor_destructor_id = 0; + + for (p = win32_display->cursors_for_destruction; p; p = p->next) + { + HCURSOR handle = (HCURSOR) p->data; + + if (handle == NULL) + continue; + + if (current_hcursor == handle) + { + SetCursor (NULL); + current_hcursor = NULL; + } + + if (!DestroyCursor (handle)) + g_warning (G_STRLOC ": DestroyCursor (%p) failed: %lu", handle, GetLastError ()); + } + + g_list_free (win32_display->cursors_for_destruction); + win32_display->cursors_for_destruction = NULL; + + return G_SOURCE_REMOVE; +} + +void +_gdk_win32_display_hcursor_unref (GdkWin32Display *display, + HCURSOR handle) +{ + GdkWin32HCursorTableEntry *entry; + gboolean destroyable; + + entry = g_hash_table_lookup (display->cursor_reftable, handle); + + if (!entry) + { + g_warning ("Trying to forget cursor handle 0x%p that is not in the table", handle); + + return; + } + + entry->refcount -= 1; + + if (entry->refcount > 0) + return; + + destroyable = entry->destroyable; + + g_hash_table_remove (display->cursor_reftable, handle); + g_free (entry); + + if (!destroyable) + return; + + /* GDK tends to destroy a cursor first, then set a new one. + * This results in repeated oscillations between SetCursor(NULL) + * and SetCursor(hcursor). To avoid that, delay cursor destruction a bit + * to let GDK set a new one first. That way cursors are switched + * seamlessly, without a NULL cursor between them. + * If GDK sets the new cursor to the same handle the old cursor had, + * the cursor handle is taken off the destruction list. + */ + if (g_list_find (display->cursors_for_destruction, handle) == NULL) + { + display->cursors_for_destruction = g_list_prepend (display->cursors_for_destruction, handle); + + if (display->idle_cursor_destructor_id == 0) + display->idle_cursor_destructor_id = g_idle_add (delayed_cursor_destruction, display); + } +} + +#ifdef gdk_win32_hcursor_get_handle +#undef gdk_win32_hcursor_get_handle +#endif + +HCURSOR +gdk_win32_hcursor_get_handle (GdkWin32HCursor *cursor) +{ + return cursor->readonly_handle; +} + static HCURSOR hcursor_from_x_cursor (gint i, const gchar *name) diff --git a/gdk/win32/gdkdisplay-win32.h b/gdk/win32/gdkdisplay-win32.h index 76e4544981..9190193c6b 100644 --- a/gdk/win32/gdkdisplay-win32.h +++ b/gdk/win32/gdkdisplay-win32.h @@ -23,6 +23,7 @@ #define __GDK_DISPLAY__WIN32_H__ #include "gdkwin32screen.h" +#include "gdkwin32cursor.h" /* Define values used to set DPI-awareness */ typedef enum _GdkWin32ProcessDpiAwareness { @@ -101,6 +102,13 @@ struct _GdkWin32Display /* Cursor Items (GdkCursor->HCURSOR) */ GHashTable *cursors; GdkCursor *grab_cursor; + /* HCURSOR -> GdkWin32HCursorTableEntry */ + GHashTable *cursor_reftable; + /* ID of the idle callback scheduled to destroy cursors */ + guint idle_cursor_destructor_id; + + /* A list of cursor handles slated for destruction. */ + GList *cursors_for_destruction; /* Message filters */ GList *filters; diff --git a/gdk/win32/gdkprivate-win32.h b/gdk/win32/gdkprivate-win32.h index 7cea29150f..b1ab8c9b3e 100644 --- a/gdk/win32/gdkprivate-win32.h +++ b/gdk/win32/gdkprivate-win32.h @@ -130,13 +130,6 @@ GdkWin32Screen *GDK_SURFACE_SCREEN(GObject *win); typedef struct _GdkWin32SingleFont GdkWin32SingleFont; -struct _GdkWin32Cursor -{ - GdkCursor cursor; - - HCURSOR hcursor; -}; - struct _GdkWin32SingleFont { HFONT hfont; diff --git a/gdk/win32/gdkwin32cursor.h b/gdk/win32/gdkwin32cursor.h index 19033d84ee..60d8a6ad5d 100644 --- a/gdk/win32/gdkwin32cursor.h +++ b/gdk/win32/gdkwin32cursor.h @@ -29,26 +29,57 @@ #error "Only can be included directly." #endif +#include #include +#include G_BEGIN_DECLS -#define GDK_TYPE_WIN32_CURSOR (gdk_win32_cursor_get_type ()) -#define GDK_WIN32_CURSOR(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_WIN32_CURSOR, GdkWin32Cursor)) -#define GDK_WIN32_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_WIN32_CURSOR, GdkWin32CursorClass)) -#define GDK_IS_WIN32_CURSOR(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WIN32_CURSOR)) -#define GDK_IS_WIN32_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_WIN32_CURSOR)) -#define GDK_WIN32_CURSOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_WIN32_CURSOR, GdkWin32CursorClass)) +typedef struct _GdkWin32HCursor GdkWin32HCursor; +typedef struct _GdkWin32HCursorClass GdkWin32HCursorClass; -#ifdef GDK_COMPILATION -typedef struct _GdkWin32Cursor GdkWin32Cursor; -#else -typedef GdkCursor GdkWin32Cursor; -#endif -typedef struct _GdkWin32CursorClass GdkWin32CursorClass; +#define GDK_TYPE_WIN32_HCURSOR (gdk_win32_hcursor_get_type()) +#define GDK_WIN32_HCURSOR(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_WIN32_HCURSOR, GdkWin32HCursor)) +#define GDK_WIN32_HCURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_WIN32_HCURSOR, GdkWin32HCursorClass)) +#define GDK_IS_WIN32_HCURSOR(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WIN32_HCURSOR)) +#define GDK_IS_WIN32_HCURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_WIN32_HCURSOR)) +#define GDK_WIN32_HCURSOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_WIN32_HCURSOR, GdkWin32HCursorClass)) GDK_AVAILABLE_IN_ALL -GType gdk_win32_cursor_get_type (void); +GType gdk_win32_hcursor_get_type (void); + +struct _GdkWin32HCursorFake +{ + GObject parent_instance; + HCURSOR readonly_handle; +}; + +#define gdk_win32_hcursor_get_handle_fast(x) (((struct _GdkWin32HCursorFake *) x)->readonly_handle) + +#if defined (GDK_COMPILATION) +#define gdk_win32_hcursor_get_handle gdk_win32_hcursor_get_handle_fast +#else +GDK_AVAILABLE_IN_ALL +HCURSOR gdk_win32_hcursor_get_handle (GdkWin32HCursor *cursor); +#endif + +GDK_AVAILABLE_IN_ALL +GdkWin32HCursor *gdk_win32_hcursor_new (GdkWin32Display *display, + HCURSOR handle, + gboolean destroyable); + +GDK_AVAILABLE_IN_ALL +GdkWin32HCursor *gdk_win32_display_get_win32hcursor (GdkWin32Display *display, + GdkCursor *cursor); + +GDK_AVAILABLE_IN_ALL +void _gdk_win32_display_hcursor_ref (GdkWin32Display *display, + HCURSOR handle, + gboolean destroyable); + +GDK_AVAILABLE_IN_ALL +void _gdk_win32_display_hcursor_unref (GdkWin32Display *display, + HCURSOR handle); G_END_DECLS