From 0e8d6a76b63c30a5897cae1b12d81709af23bfc1 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 08:27:49 +0200 Subject: [PATCH 01/56] tests: fix GtkBuilder signal callback prototypes Based on the Guix gtk2-fix-builder-test.patch. The callbacks are connected to notify-style signals and must take a GParamSpec pointer, not a GParamSpec by value. This avoids stack/register ABI problems on platforms such as ppc64el. --- gtk/tests/builder.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gtk/tests/builder.c b/gtk/tests/builder.c index ebba220e9..121012704 100644 --- a/gtk/tests/builder.c +++ b/gtk/tests/builder.c @@ -132,7 +132,7 @@ static int object = 0; static int object_after = 0; void /* exported for GtkBuilder */ -signal_normal (GtkWindow *window, GParamSpec spec) +signal_normal (GtkWindow *window, GParamSpec *spec) { g_assert (GTK_IS_WINDOW (window)); g_assert (normal == 0); @@ -142,7 +142,7 @@ signal_normal (GtkWindow *window, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_after (GtkWindow *window, GParamSpec spec) +signal_after (GtkWindow *window, GParamSpec *spec) { g_assert (GTK_IS_WINDOW (window)); g_assert (normal == 1); @@ -152,7 +152,7 @@ signal_after (GtkWindow *window, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_object (GtkButton *button, GParamSpec spec) +signal_object (GtkButton *button, GParamSpec *spec) { g_assert (GTK_IS_BUTTON (button)); g_assert (object == 0); @@ -162,7 +162,7 @@ signal_object (GtkButton *button, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_object_after (GtkButton *button, GParamSpec spec) +signal_object_after (GtkButton *button, GParamSpec *spec) { g_assert (GTK_IS_BUTTON (button)); g_assert (object == 1); @@ -172,28 +172,28 @@ signal_object_after (GtkButton *button, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_first (GtkButton *button, GParamSpec spec) +signal_first (GtkButton *button, GParamSpec *spec) { g_assert (normal == 0); normal = 10; } void /* exported for GtkBuilder */ -signal_second (GtkButton *button, GParamSpec spec) +signal_second (GtkButton *button, GParamSpec *spec) { g_assert (normal == 10); normal = 20; } void /* exported for GtkBuilder */ -signal_extra (GtkButton *button, GParamSpec spec) +signal_extra (GtkButton *button, GParamSpec *spec) { g_assert (normal == 20); normal = 30; } void /* exported for GtkBuilder */ -signal_extra2 (GtkButton *button, GParamSpec spec) +signal_extra2 (GtkButton *button, GParamSpec *spec) { g_assert (normal == 30); normal = 40; From 790b8c2b69c3e6d520e6b53f467fddc2f7bb80cf Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 08:34:50 +0200 Subject: [PATCH 02/56] gtkrc: search XDG data dirs for themes Based on the Guix gtk2-theme-paths.patch by Jookia. Keep the existing ~/.themes and gtk_rc_get_theme_dir() lookup paths, but check g_get_system_data_dirs() in between so profile- and prefix-based theme installations can be found. --- gtk/gtkrc.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gtk/gtkrc.c b/gtk/gtkrc.c index 7bfbea85c..d316a0d90 100644 --- a/gtk/gtkrc.c +++ b/gtk/gtkrc.c @@ -808,6 +808,8 @@ gtk_rc_parse_named (GtkRcContext *context, gchar *path = NULL; const gchar *home_dir; gchar *subpath; + const gchar * const *xdg_data_dirs; + gint i; if (type) subpath = g_strconcat ("gtk-2.0-", type, @@ -829,6 +831,22 @@ gtk_rc_parse_named (GtkRcContext *context, } } + if (!path) + { + xdg_data_dirs = g_get_system_data_dirs (); + for (i = 0; xdg_data_dirs[i]; i++) + { + path = g_build_filename (xdg_data_dirs[i], "themes", name, subpath, NULL); + if (g_file_test (path, G_FILE_TEST_EXISTS)) + break; + else + { + g_free (path); + path = NULL; + } + } + } + if (!path) { gchar *theme_dir = gtk_rc_get_theme_dir (); From d8b26dc0b746228343eb2f1532509b6ac567c041 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 11:25:19 +0200 Subject: [PATCH 03/56] gtktrayicon: use explicit void prototypes for reserved slots Based on the Mageia gtk+-2.24.32-warnings.patch. Declare the reserved GtkTrayIconClass function pointers with explicit void parameter lists to avoid strict-prototype warnings without changing the class layout. --- gtk/gtktrayicon.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gtk/gtktrayicon.h b/gtk/gtktrayicon.h index 9ad1c54f2..ab4eda2f5 100644 --- a/gtk/gtktrayicon.h +++ b/gtk/gtktrayicon.h @@ -46,12 +46,12 @@ struct _GtkTrayIconClass { GtkPlugClass parent_class; - void (*__gtk_reserved1); - void (*__gtk_reserved2); - void (*__gtk_reserved3); - void (*__gtk_reserved4); - void (*__gtk_reserved5); - void (*__gtk_reserved6); + void (*__gtk_reserved1) (void); + void (*__gtk_reserved2) (void); + void (*__gtk_reserved3) (void); + void (*__gtk_reserved4) (void); + void (*__gtk_reserved5) (void); + void (*__gtk_reserved6) (void); }; GType gtk_tray_icon_get_type (void) G_GNUC_CONST; From 292e512bc4a0ba9087dc2732ff51b103eeed2522 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:15:53 +0200 Subject: [PATCH 04/56] build: disable GNU visibility aliases on Apple platforms Based on the MacPorts patch-aliases.diff. Keep the existing GNU visibility alias generation on other platforms, but avoid it on Apple targets where this alias mechanism is not suitable. --- gdk/makegdkalias.pl | 2 +- gtk/makegtkalias.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gdk/makegdkalias.pl b/gdk/makegdkalias.pl index 97a6e6007..27646cc0a 100755 --- a/gdk/makegdkalias.pl +++ b/gdk/makegdkalias.pl @@ -14,7 +14,7 @@ print < -#ifdef G_HAVE_GNUC_VISIBILITY +#if defined(G_HAVE_GNUC_VISIBILITY) && !defined(__APPLE__) EOF diff --git a/gtk/makegtkalias.pl b/gtk/makegtkalias.pl index 3f956b05e..67ec7c973 100755 --- a/gtk/makegtkalias.pl +++ b/gtk/makegtkalias.pl @@ -14,7 +14,7 @@ print < -#ifdef G_HAVE_GNUC_VISIBILITY +#if defined(G_HAVE_GNUC_VISIBILITY) && !defined(__APPLE__) EOF From 72dcfc7ca5fc2a78b47b45e3fe67bfe0ed394911 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:16:45 +0200 Subject: [PATCH 05/56] quartz: add newer macOS version constants Based on the MacPorts patch-gdkquartz-osx-versions.diff. Extend the Quartz OS version enum so later macOS-specific code can distinguish Catalina, Big Sur, Monterey, Ventura, Sonoma, and Sequoia while preserving the existing older constants. --- gdk/quartz/gdkquartz.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gdk/quartz/gdkquartz.h b/gdk/quartz/gdkquartz.h index 8206db019..42aac50b9 100644 --- a/gdk/quartz/gdkquartz.h +++ b/gdk/quartz/gdkquartz.h @@ -54,7 +54,13 @@ typedef enum GDK_OSX_SIERRA = 12, GDK_OSX_HIGH_SIERRA = 13, GDK_OSX_MOJAVE = 14, - GDK_OSX_CURRENT = 14, + GDK_OSX_CATALINA = 15, + GDK_OSX_BIG_SUR = 16, + GDK_OSX_MONTEREY = 17, + GDK_OSX_VENTURA = 18, + GDK_OSX_SONOMA = 19, + GDK_OSX_SEQUOIA = 20, + GDK_OSX_CURRENT = 20, GDK_OSX_NEW = 99 } GdkOSXVersion; From 0bc8029032828499c3e8c98488b8c1c89d60254e Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:27:16 +0200 Subject: [PATCH 06/56] quartz: avoid focus locking on Mojave and newer Based on part of the MacPorts Big Sur performance patch for GIMP issue 5989. The NSView focus-locking API is deprecated on newer macOS releases and can cause expensive redraw behaviour. Keep the old path for older macOS versions, but avoid lockFocusIfCanDraw/unlockFocus on Mojave and newer. --- gdk/quartz/gdkdrawable-quartz.c | 3 ++- gdk/quartz/gdkwindow-quartz.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gdk/quartz/gdkdrawable-quartz.c b/gdk/quartz/gdkdrawable-quartz.c index 11dd12737..eb520f08d 100644 --- a/gdk/quartz/gdkdrawable-quartz.c +++ b/gdk/quartz/gdkdrawable-quartz.c @@ -893,7 +893,8 @@ gdk_quartz_drawable_release_context (GdkDrawable *drawable, if (window_impl->in_paint_rect_count == 0) { _gdk_quartz_drawable_flush (drawable); - [window_impl->view unlockFocus]; + if (gdk_quartz_osx_version () < GDK_OSX_MOJAVE) + [window_impl->view unlockFocus]; } } else if (GDK_IS_PIXMAP_IMPL_QUARTZ (drawable)) diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index 6c7a2facb..205fd85e6 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -104,7 +104,8 @@ gdk_window_impl_quartz_get_context (GdkDrawable *drawable, * and for widgets that send fake expose events like the arrow * buttons in spinbuttons or the position marker in rulers. */ - if (window_impl->in_paint_rect_count == 0) + if (window_impl->in_paint_rect_count == 0 && + gdk_quartz_osx_version () < GDK_OSX_MOJAVE) { if (![window_impl->view lockFocusIfCanDraw]) return NULL; From 8b7fda063e32d8fa920ff76d60ced2cc895ffb13 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:29:27 +0200 Subject: [PATCH 07/56] quartz: map macOS 11 and newer to internal OS versions Based on part of the MacPorts Big Sur performance patch for GIMP issue 5989. GTK2 represents macOS 10.x releases by their minor version. Read the major version as well and map macOS 11 and newer into the same sequence so Big Sur and later releases can be detected correctly. --- gdk/quartz/gdkglobals-quartz.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/gdk/quartz/gdkglobals-quartz.c b/gdk/quartz/gdkglobals-quartz.c index f013f2830..4e596eef5 100644 --- a/gdk/quartz/gdkglobals-quartz.c +++ b/gdk/quartz/gdkglobals-quartz.c @@ -34,9 +34,20 @@ gdk_quartz_osx_version (void) if (minor == GDK_OSX_UNSUPPORTED) { - OSErr err = Gestalt (gestaltSystemVersionMinor, (SInt32*)&minor); + gint32 major = GDK_OSX_UNSUPPORTED; + OSErr err = Gestalt (gestaltSystemVersionMajor, (SInt32*)&major); g_return_val_if_fail (err == noErr, GDK_OSX_UNSUPPORTED); + + err = Gestalt (gestaltSystemVersionMinor, (SInt32*)&minor); + g_return_val_if_fail (err == noErr, GDK_OSX_UNSUPPORTED); + + /* GTK2 encodes macOS 10.x releases by their minor version. + * Map macOS 11+ into the same sequence: + * 11 -> 16, 12 -> 17, 13 -> 18, ... + */ + if (major > 10) + minor = major + 5; } if (minor < GDK_OSX_MIN) From 21fdf2eeaaa02ee2fc18374593ae3d8b01d17b61 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:32:30 +0200 Subject: [PATCH 08/56] quartz: use CGContext accessor on Yosemite and newer Based on part of the MacPorts Big Sur performance patch for GIMP issue 5989. Use the modern NSGraphicsContext CGContext accessor on Yosemite and newer, matching the existing Quartz window code, while preserving graphicsPort for older macOS versions. --- gtk/gtkquartz.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gtk/gtkquartz.c b/gtk/gtkquartz.c index 87ba87745..caf8404f8 100644 --- a/gtk/gtkquartz.c +++ b/gtk/gtkquartz.c @@ -67,7 +67,10 @@ _gtk_quartz_create_image_from_pixbuf (GdkPixbuf *pixbuf) } [nsimage lockFocus]; - context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + if (gdk_quartz_osx_version () < GDK_OSX_YOSEMITE) + context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + else + context = [[NSGraphicsContext currentContext] CGContext]; CGContextDrawImage (context, CGRectMake (0, 0, pixbuf_width, pixbuf_height), image); [nsimage unlockFocus]; From fe45d0944c35268c817aca1be9a5f9d6077f90a7 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:41:36 +0200 Subject: [PATCH 09/56] quartz: avoid screen update suppression on Mojave and newer Based on part of the MacPorts Big Sur performance patch for GIMP issue 5989. NSDisableScreenUpdates and NSEnableScreenUpdates are deprecated on newer macOS releases. Keep using them for older macOS versions, but avoid them on Mojave and newer. --- gdk/quartz/gdkwindow-quartz.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index 205fd85e6..5c1bde0cd 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -469,7 +469,8 @@ _gdk_windowing_before_process_all_updates (void) { in_process_all_updates = TRUE; - NSDisableScreenUpdates (); + if (gdk_quartz_osx_version () < GDK_OSX_MOJAVE) + NSDisableScreenUpdates (); } void @@ -503,7 +504,8 @@ _gdk_windowing_after_process_all_updates (void) in_process_all_updates = FALSE; - NSEnableScreenUpdates (); + if (gdk_quartz_osx_version () < GDK_OSX_MOJAVE) + NSEnableScreenUpdates (); } static void From 9d7c7e63d614759cdeafd672dd3d53ccef258825 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:49:48 +0200 Subject: [PATCH 10/56] quartz: request 8-bit layer contents on Big Sur and newer Based on part of the MacPorts Big Sur performance patch for GIMP issue 5989. Use a dynamic Objective-C call to request 8-bit layer contents on macOS 11 and newer, avoiding a hard QuartzCore/CALayer compile-time dependency and preserving old behaviour on older macOS releases. --- gdk/quartz/GdkQuartzView.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index 368e040ec..bad737da2 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -599,6 +599,25 @@ gdk_screen_get_rgba_colormap (_gdk_screen); } +-(void)viewWillDraw +{ + /* macOS 11 (Big Sur) and newer may use a higher precision backing + * format for layer contents. For GTK2's 8-bit drawing this can add + * avoidable conversion overhead. Use a dynamic Objective-C call here + * instead of a hard QuartzCore/CALayer dependency, so old SDKs and old + * macOS targets keep building and keep their old behaviour. + */ + if (gdk_quartz_osx_version () >= GDK_OSX_BIG_SUR) + { + id layer = [self layer]; + + if (layer && [layer respondsToSelector: @selector(setContentsFormat:)]) + [layer setValue: @"RGBA8Uint" forKey: @"contentsFormat"]; + } + + [super viewWillDraw]; +} + -(void)drawRect: (NSRect)rect { GdkRectangle gdk_rect; From c1fc6ed6424828c3077a083423de93f4029fcbbf Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:54:51 +0200 Subject: [PATCH 11/56] quartz: avoid extra content invalidation on Big Sur and newer Based on part of the MacPorts Big Sur performance patch for GIMP issue 5989. Keep the existing Mojave-era setNeedsDisplay workaround for macOS 10.14 and 10.15, but avoid the extra full-window invalidation on Big Sur and newer where it can cause expensive redraw behaviour. --- gdk/quartz/gdkwindow-quartz.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index 5c1bde0cd..38ada80b2 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -491,8 +491,9 @@ _gdk_windowing_after_process_all_updates (void) /* 10.14 needs to be told that the view needs to be redrawn, see * https://gitlab.gnome.org/GNOME/gtk/issues/1479 */ - if (gdk_quartz_osx_version() >= GDK_OSX_MOJAVE) - [[nswindow contentView] setNeedsDisplay:YES]; + if (gdk_quartz_osx_version () >= GDK_OSX_MOJAVE && + gdk_quartz_osx_version () < GDK_OSX_BIG_SUR) + [[nswindow contentView] setNeedsDisplay:YES]; [nswindow enableFlushWindow]; [nswindow flushWindow]; [nswindow release]; From 8d4015ee54b85becc0f2935c3ca6f25905a06508 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 09:59:26 +0200 Subject: [PATCH 12/56] quartz: avoid extra resize-idle updates on Big Sur and newer Based on part of the MacPorts Big Sur performance patch for GIMP issue 5989. Keep the existing gdk_window_process_all_updates() call for non-Quartz targets and for older macOS releases, but avoid this extra resize-idle update path on Big Sur and newer where it can contribute to expensive full-window redraw behaviour. --- gtk/gtkcontainer.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c index 2f7007fd2..e4c98c8c2 100644 --- a/gtk/gtkcontainer.c +++ b/gtk/gtkcontainer.c @@ -40,6 +40,9 @@ #include "gtktoolbar.h" #include #include +#ifdef GDK_WINDOWING_QUARTZ +#include "gdk/quartz/gdkquartz.h" +#endif #include "gtkalias.h" @@ -1357,7 +1360,16 @@ gtk_container_idle_sizer (gpointer data) gtk_container_check_resize (GTK_CONTAINER (widget)); } +#ifdef GDK_WINDOWING_QUARTZ + /* macOS 11 and newer can trigger expensive full-window redraws here. + * Keep the traditional update processing path for older Quartz targets, + * but let the normal GDK update idle handle updates on Big Sur and newer. + */ + if (gdk_quartz_osx_version () < GDK_OSX_BIG_SUR) + gdk_window_process_all_updates (); +#else gdk_window_process_all_updates (); +#endif return FALSE; } From 65f949bcded5e103d370f3cba89027318490e6b9 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 10:06:34 +0200 Subject: [PATCH 13/56] win32: improve mouse window lookup through transparent children Based on the MSYS2 0013_fix_mouse_events.patch. Use CWP_SKIPTRANSPARENT while walking from the desktop window, but stop skipping transparent windows after the first level so transparent child windows are still considered correctly. --- gdk/win32/gdkwindow-win32.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gdk/win32/gdkwindow-win32.c b/gdk/win32/gdkwindow-win32.c index b7ea0404a..1a21a1b67 100644 --- a/gdk/win32/gdkwindow-win32.c +++ b/gdk/win32/gdkwindow-win32.c @@ -2767,6 +2767,10 @@ _gdk_windowing_window_at_pointer (GdkDisplay *display, * WindowFromPoint() can find our windows, we follow similar logic * here, and ignore invisible and disabled windows. */ + UINT cwp_flags = CWP_SKIPDISABLED | + CWP_SKIPINVISIBLE | + CWP_SKIPTRANSPARENT; + hwnd = GetDesktopWindow (); do { window = gdk_win32_handle_table_lookup ((GdkNativeWindow) hwnd); @@ -2777,8 +2781,7 @@ _gdk_windowing_window_at_pointer (GdkDisplay *display, break; screen_to_client (hwnd, screen_pt, &client_pt); - hwndc = ChildWindowFromPointEx (hwnd, client_pt, CWP_SKIPDISABLED | - CWP_SKIPINVISIBLE); + hwndc = ChildWindowFromPointEx (hwnd, client_pt, cwp_flags); /* Verify that we're really inside the client area of the window */ if (hwndc != hwnd) @@ -2789,6 +2792,8 @@ _gdk_windowing_window_at_pointer (GdkDisplay *display, hwndc = hwnd; } + /* Only ignore top-level transparent windows */ + cwp_flags &= ~CWP_SKIPTRANSPARENT; } while (hwndc != hwnd && (hwnd = hwndc, 1)); } From d181fe8fc3d62dab2bc9640c28bd2c44eae24f59 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 10:07:25 +0200 Subject: [PATCH 14/56] icontheme: detect SVG icons by extension on Win32 Based on the MSYS2 0022-icontheme-win32-detect-SVG-files-by-extension.patch. Some Windows systems report .svg files with a generic or incorrect content type. On Win32, fall back to the .svg extension so SVG icons can still be detected. --- gtk/gtkicontheme.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index 0d4435c72..f8aaf9a3b 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -2945,6 +2945,10 @@ icon_info_ensure_scale_and_pixbuf (GtkIconInfo *icon_info, if (mime_type && strcmp (mime_type, "image/svg+xml") == 0) is_svg = TRUE; +#ifdef G_OS_WIN32 + else if (content_type && strcmp (content_type, ".svg") == 0) + is_svg = TRUE; +#endif } g_object_unref (file_info); From 4d7fc9558334fc36c7783cbd96f3dcdef3b6846b Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 10:08:01 +0200 Subject: [PATCH 15/56] win32: initialize key state before updating keymap Based on the MSYS2 0023-gdkkeys-win32.c-fix-initialisation-of-key_state-in-u.patch. Clear the key_state array before using it during keymap updates so stale state cannot affect layout handling, including Japanese IME input. --- gdk/win32/gdkkeys-win32.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gdk/win32/gdkkeys-win32.c b/gdk/win32/gdkkeys-win32.c index d8ab30347..9a34083c4 100644 --- a/gdk/win32/gdkkeys-win32.c +++ b/gdk/win32/gdkkeys-win32.c @@ -692,6 +692,7 @@ update_keymap (GdkKeymap *gdk_keymap) if (hkls_len != keymap->layout_handles->len) keymap->keysym_tab = g_renew (guint, keymap->keysym_tab, keysym_tab_size); + memset (key_state, 0, sizeof(key_state)); memset (keymap->keysym_tab, 0, keysym_tab_size); g_array_set_size (keymap->layout_handles, hkls_len); g_array_set_size (keymap->options, hkls_len); From 757774345d1b942dfb056362bd45abfefff4e0df Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 10:09:36 +0200 Subject: [PATCH 16/56] win32: quote icon cache manifest in resource file Based on the MSYS2 0024-gtk-update-icon-cache-rc-quote.patch. Quote the manifest path when generating the gtk-update-icon-cache resource file so windres parses it correctly. --- gtk/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 8f5e496a3..c3c9a1493 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -1139,7 +1139,7 @@ $(GTK_UPDATE_ICON_CACHE_MANIFEST): echo '' ) >$@ $(GTK_UPDATE_ICON_CACHE_RC): - (echo -e '#include \nCREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST '$(GTK_UPDATE_ICON_CACHE_MANIFEST)) >$@ + (echo -e '#include \nCREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "'$(GTK_UPDATE_ICON_CACHE_MANIFEST)'"') >$@ $(GTK_UPDATE_ICON_CACHE_MANIFEST_OBJECT): $(GTK_UPDATE_ICON_CACHE_RC) $(GTK_UPDATE_ICON_CACHE_MANIFEST) $(WINDRES) --input $< --output $@ --output-format=coff From b622340b5b447d0094f5726127d9252278bf39cc Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 11:46:52 +0200 Subject: [PATCH 17/56] print: preserve default-printer wait during status updates Based on the openSUSE gtk2-default-printer.patch. Avoid treating printer status updates as user-driven printer selection changes, so the print dialog can continue waiting for a default printer that may appear later. --- gtk/gtkprintunixdialog.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtk/gtkprintunixdialog.c b/gtk/gtkprintunixdialog.c index da9010b96..af64a5a37 100644 --- a/gtk/gtkprintunixdialog.c +++ b/gtk/gtkprintunixdialog.c @@ -796,7 +796,9 @@ printer_status_cb (GtkPrintBackend *backend, /* When the pause state change then we need to update sensitive property * of GTK_RESPONSE_OK button inside of selected_printer_changed function. */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->printer_treeview)); + priv->internal_printer_change = TRUE; selected_printer_changed (selection, dialog); + priv->internal_printer_change = FALSE; if (gtk_print_backend_printer_list_is_done (backend) && gtk_printer_is_default (printer) && From 00177dd11c69e21e78ec92442cdf189cfc840a47 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 10:55:56 +0200 Subject: [PATCH 18/56] docs: use top_srcdir for GDK X11 header reference Based on the MSYS2 0005-Fix-header-ref.all.patch. Use an explicit top_srcdir-relative path for gdkx.h in the GDK reference documentation instead of a fragile relative path. --- docs/reference/gdk/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/gdk/Makefile.am b/docs/reference/gdk/Makefile.am index c0facc151..43a67508c 100644 --- a/docs/reference/gdk/Makefile.am +++ b/docs/reference/gdk/Makefile.am @@ -37,7 +37,7 @@ IGNORE_HFILES= \ # Extra files to add when scanning (relative to $srcdir) EXTRA_HFILES= \ - ../../../gdk/x11/gdkx.h + $(top_srcdir)/gdk/x11/gdkx.h # CFLAGS and LDFLAGS for compiling scan program. Only needed # if $(DOC_MODULE).types is non-empty. From 71023e03331f1b86bcc4af3aba0b9020d0aaf5b6 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 4 Aug 2026 15:34:12 +0200 Subject: [PATCH 19/56] gdk: add endian-aware 30-bit RGB converters Add specialized converters for both 10:10:10 channel layouts and both X image byte orders. Preserve depth-30 padding bits, avoid invalid negative shifts in the generic converter and select endian conversion at compile time. Validated with a real Xvfb 30-bit A/B pixel test and cross-target endian store checks. Derived from downstream commit 02a2c61f2dfc574a88343c81ddf10fc515136533. Co-authored-by: Daemonratte --- gdk/gdkrgb.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/gdk/gdkrgb.c b/gdk/gdkrgb.c index b0ff66833..dddf1991c 100644 --- a/gdk/gdkrgb.c +++ b/gdk/gdkrgb.c @@ -2242,6 +2242,68 @@ gdk_rgb_convert_8880_br (GdkRgbInfo *image_info, GdkImage *image, } } +#define DEFINE_GDK_RGB_CONVERT_101010(name, high, low, to_order) \ +static void \ +name (GdkRgbInfo *image_info, GdkImage *image, \ + gint x0, gint y0, gint width, gint height, \ + const guchar *buf, int rowstride, \ + gint x_align, gint y_align, GdkRgbCmap *cmap) \ +{ \ + int y, w; \ + guchar *obuf, *p; \ + gint bpl; \ + const guchar *bptr, *bp2; \ + guint32 r, g, b; \ + guint32 pixel; \ + guint32 alpha_mask; \ + \ + alpha_mask = image_info->visual->depth == 32 ? 0xc0000000U : 0; \ + \ + bptr = buf; \ + bpl = image->bpl; \ + obuf = ((guchar *) image->mem) + y0 * bpl + x0 * 4; \ + \ + for (y = 0; y < height; y++) \ + { \ + bp2 = bptr; \ + p = obuf; \ + w = width; \ + \ + while (w--) \ + { \ + r = *bp2++; \ + g = *bp2++; \ + b = *bp2++; \ + \ + r = (r << 2) | (r >> 6); \ + g = (g << 2) | (g >> 6); \ + b = (b << 2) | (b >> 6); \ + \ + pixel = alpha_mask | \ + (high << 20) | \ + (g << 10) | \ + low; \ + \ + *((guint32 *) p) = to_order (pixel); \ + p += 4; \ + } \ + \ + bptr += rowstride; \ + obuf += bpl; \ + } \ +} + +DEFINE_GDK_RGB_CONVERT_101010 (gdk_rgb_convert_101010_r_low_lsb, + b, r, GUINT32_TO_LE) +DEFINE_GDK_RGB_CONVERT_101010 (gdk_rgb_convert_101010_b_low_lsb, + r, b, GUINT32_TO_LE) +DEFINE_GDK_RGB_CONVERT_101010 (gdk_rgb_convert_101010_r_low_msb, + b, r, GUINT32_TO_BE) +DEFINE_GDK_RGB_CONVERT_101010 (gdk_rgb_convert_101010_b_low_msb, + r, b, GUINT32_TO_BE) + +#undef DEFINE_GDK_RGB_CONVERT_101010 + /* Generic truecolor/directcolor conversion function. Slow, but these are oddball modes. */ static void @@ -3195,6 +3257,34 @@ gdk_rgb_select_conv (GdkRgbInfo *image_info) #endif } #endif + else if (bpp == 32 && (depth == 30 || depth == 32) && + vtype == GDK_VISUAL_TRUE_COLOR && + byte_order == GDK_LSB_FIRST && + red_mask == 0x000003ff && + green_mask == 0x000ffc00 && + blue_mask == 0x3ff00000) + conv = gdk_rgb_convert_101010_r_low_lsb; + else if (bpp == 32 && (depth == 30 || depth == 32) && + vtype == GDK_VISUAL_TRUE_COLOR && + byte_order == GDK_LSB_FIRST && + red_mask == 0x3ff00000 && + green_mask == 0x000ffc00 && + blue_mask == 0x000003ff) + conv = gdk_rgb_convert_101010_b_low_lsb; + else if (bpp == 32 && (depth == 30 || depth == 32) && + vtype == GDK_VISUAL_TRUE_COLOR && + byte_order == GDK_MSB_FIRST && + red_mask == 0x000003ff && + green_mask == 0x000ffc00 && + blue_mask == 0x3ff00000) + conv = gdk_rgb_convert_101010_r_low_msb; + else if (bpp == 32 && (depth == 30 || depth == 32) && + vtype == GDK_VISUAL_TRUE_COLOR && + byte_order == GDK_MSB_FIRST && + red_mask == 0x3ff00000 && + green_mask == 0x000ffc00 && + blue_mask == 0x000003ff) + conv = gdk_rgb_convert_101010_b_low_msb; else if (vtype == GDK_VISUAL_TRUE_COLOR && byte_order == GDK_LSB_FIRST) { conv = gdk_rgb_convert_truecolor_lsb; From be64a902a5dfa76aaca4d66ad85505a76ef4016e Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 4 Aug 2026 17:55:45 +0200 Subject: [PATCH 20/56] gdk: optimize native big-endian RGB565 paths Add native big-endian four-pixel converters for RGB565, grayscale RGB565, and dithered RGB565. Select the block converters only where cross-target code generation is favorable: RGB on PowerPC, s390x, ARMv5+, and AArch64; grayscale on PowerPC, ARMv5+, AArch64, and m68k; and dither on PowerPC. Retain the scalar fallbacks everywhere else. The little-endian machine code remains byte-for-byte unchanged. No runtime endian checks, allocations, or additional image buffers are introduced. Validated with a complete build and test suite, deterministic and randomized big-endian formula tests, exact little-endian .text comparison, and cross-target code-generation checks. --- gdk/gdkrgb.c | 146 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 113 insertions(+), 33 deletions(-) diff --git a/gdk/gdkrgb.c b/gdk/gdkrgb.c index dddf1991c..4f2e0d44f 100644 --- a/gdk/gdkrgb.c +++ b/gdk/gdkrgb.c @@ -1464,19 +1464,33 @@ gdk_rgb_convert_gray8_gray (GdkRgbInfo *image_info, GdkImage *image, } } -#if G_BYTE_ORDER == G_LITTLE_ENDIAN -#define HAIRY_CONVERT_565 + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#if defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__ppc__) || defined(__ppc64__) || \ + defined(__PPC__) || defined(__PPC64__) || \ + defined(__POWERPC__) || defined(_ARCH_PPC) +#define GDK_RGB_FAST_BE565_RGB +#define GDK_RGB_FAST_BE565_GRAY +#define GDK_RGB_FAST_BE565_DITHER +#elif defined(__s390x__) +#define GDK_RGB_FAST_BE565_RGB +#elif defined(__aarch64__) +#define GDK_RGB_FAST_BE565_RGB +#define GDK_RGB_FAST_BE565_GRAY +#elif defined(__arm__) && defined(__ARM_ARCH) && \ + __ARM_ARCH >= 5 +#define GDK_RGB_FAST_BE565_RGB +#define GDK_RGB_FAST_BE565_GRAY +#elif defined(__m68k__) +#define GDK_RGB_FAST_BE565_GRAY +#endif #endif -#ifdef HAIRY_CONVERT_565 /* Render a 24-bit RGB image in buf into the GdkImage, without dithering. - This assumes native byte ordering - what should really be done is to - check whether the image byte_order is consistent with the _ENDIAN - config flag, and if not, use a different function. - - This one is even faster than the one below - its inner loop loads 3 - words (i.e. 4 24-bit pixels), does a lot of shifting and masking, - then writes 2 words. */ + The four-pixel inner loop uses compile-time-specialized native word + layouts for little- and big-endian systems. */ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN || defined(GDK_RGB_FAST_BE565_RGB) static void gdk_rgb_convert_565 (GdkRgbInfo *image_info, GdkImage *image, gint x0, gint y0, gint width, gint height, @@ -1520,6 +1534,7 @@ gdk_rgb_convert_565 (GdkRgbInfo *image_info, GdkImage *image, r1b0g0r0 = ((guint32 *)bp2)[0]; g2r2b1g1 = ((guint32 *)bp2)[1]; b3g3r3b2 = ((guint32 *)bp2)[2]; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN ((guint32 *)obptr)[0] = ((r1b0g0r0 & 0xf8) << 8) | ((r1b0g0r0 & 0xfc00) >> 5) | @@ -1534,6 +1549,22 @@ gdk_rgb_convert_565 (GdkRgbInfo *image_info, GdkImage *image, ((b3g3r3b2 & 0xf800) << 16) | ((b3g3r3b2 & 0xfc0000) << 3) | ((b3g3r3b2 & 0xf8000000) >> 11); +#else + ((guint32 *)obptr)[0] = + (r1b0g0r0 & 0xf8000000) | + ((r1b0g0r0 & 0x00fc0000) << 3) | + ((r1b0g0r0 & 0x0000f800) << 5) | + ((r1b0g0r0 & 0x000000f8) << 8) | + ((g2r2b1g1 & 0xfc000000) >> 21) | + ((g2r2b1g1 & 0x00f80000) >> 19); + ((guint32 *)obptr)[1] = + ((g2r2b1g1 & 0x0000f800) << 16) | + ((g2r2b1g1 & 0x000000fc) << 19) | + ((b3g3r3b2 & 0xf8000000) >> 11) | + ((b3g3r3b2 & 0x00f80000) >> 8) | + ((b3g3r3b2 & 0x0000fc00) >> 5) | + ((b3g3r3b2 & 0x000000f8) >> 3); +#endif bp2 += 12; obptr += 8; } @@ -1553,27 +1584,6 @@ gdk_rgb_convert_565 (GdkRgbInfo *image_info, GdkImage *image, } } #else -/* Render a 24-bit RGB image in buf into the GdkImage, without dithering. - This assumes native byte ordering - what should really be done is to - check whether the image byte_order is consistent with the _ENDIAN - config flag, and if not, use a different function. - - This routine is faster than the one included with Gtk 1.0 for a number - of reasons: - - 1. Shifting instead of lookup tables (less memory traffic). - - 2. Much less register pressure, especially because shifts are - in the code. - - 3. A memcpy is avoided (i.e. the transfer function). - - 4. On big-endian architectures, byte swapping is avoided. - - That said, it wouldn't be hard to make it even faster - just make an - inner loop that reads 3 words (i.e. 4 24-bit pixels), does a lot of - shifting and masking, then writes 2 words. -*/ static void gdk_rgb_convert_565 (GdkRgbInfo *image_info, GdkImage *image, gint x0, gint y0, gint width, gint height, @@ -1607,7 +1617,7 @@ gdk_rgb_convert_565 (GdkRgbInfo *image_info, GdkImage *image, } #endif -#ifdef HAIRY_CONVERT_565 +#if G_BYTE_ORDER == G_LITTLE_ENDIAN || defined(GDK_RGB_FAST_BE565_GRAY) static void gdk_rgb_convert_565_gray (GdkRgbInfo *image_info, GdkImage *image, gint x0, gint y0, gint width, gint height, @@ -1645,6 +1655,7 @@ gdk_rgb_convert_565_gray (GdkRgbInfo *image_info, GdkImage *image, guint32 g3g2g1g0; g3g2g1g0 = ((guint32 *)bp2)[0]; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN ((guint32 *)obptr)[0] = ((g3g2g1g0 & 0xf8) << 8) | ((g3g2g1g0 & 0xfc) << 3) | @@ -1659,6 +1670,22 @@ gdk_rgb_convert_565_gray (GdkRgbInfo *image_info, GdkImage *image, (g3g2g1g0 & 0xf8000000) | ((g3g2g1g0 & 0xfc000000) >> 5) | ((g3g2g1g0 & 0xf8000000) >> 11); +#else + ((guint32 *)obptr)[0] = + (g3g2g1g0 & 0xf8000000) | + ((g3g2g1g0 & 0xfc000000) >> 5) | + ((g3g2g1g0 & 0xf8000000) >> 11) | + ((g3g2g1g0 & 0x00f80000) >> 8) | + ((g3g2g1g0 & 0x00fc0000) >> 13) | + ((g3g2g1g0 & 0x00f80000) >> 19); + ((guint32 *)obptr)[1] = + ((g3g2g1g0 & 0x0000f800) << 16) | + ((g3g2g1g0 & 0x0000fc00) << 11) | + ((g3g2g1g0 & 0x0000f800) << 5) | + ((g3g2g1g0 & 0x000000f8) << 8) | + ((g3g2g1g0 & 0x000000fc) << 3) | + ((g3g2g1g0 & 0x000000f8) >> 3); +#endif bp2 += 4; obptr += 8; } @@ -1745,7 +1772,7 @@ gdk_rgb_convert_565_br (GdkRgbInfo *image_info, GdkImage *image, /* Thanks to Ray Lehtiniemi for a patch that resulted in a ~25% speedup in this mode. */ -#ifdef HAIRY_CONVERT_565 +#if G_BYTE_ORDER == G_LITTLE_ENDIAN || defined(GDK_RGB_FAST_BE565_DITHER) static void gdk_rgb_convert_565_d (GdkRgbInfo *image_info, GdkImage *image, gint x0, gint y0, gint width, gint height, @@ -1800,6 +1827,7 @@ gdk_rgb_convert_565_d (GdkRgbInfo *image_info, GdkImage *image, r1b0g0r0 = ((guint32 *)bp2)[0]; g2r2b1g1 = ((guint32 *)bp2)[1]; b3g3r3b2 = ((guint32 *)bp2)[2]; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN rgb02 = ((r1b0g0r0 & 0xff) << 20) + ((r1b0g0r0 & 0xff00) << 2) + @@ -1846,6 +1874,54 @@ gdk_rgb_convert_565_d (GdkRgbInfo *image_info, GdkImage *image, ((rgb13 & 0x0f800000) << 4) | ((rgb13 & 0x0003f000) << 9) | ((rgb13 & 0x000000f8) << 13); +#else + rgb02 = + ((r1b0g0r0 & 0xff000000) >> 4) + + ((r1b0g0r0 & 0x00ff0000) >> 6) + + ((r1b0g0r0 & 0x0000ff00) >> 8) + + dmp[x & (DM_WIDTH - 1)]; + rgb02 += 0x10040100 + - ((rgb02 & 0x1e0001e0) >> 5) + - ((rgb02 & 0x00070000) >> 6); + rgb13 = + ((r1b0g0r0 & 0x000000ff) << 20) + + ((g2r2b1g1 & 0xff000000) >> 14) + + ((g2r2b1g1 & 0x00ff0000) >> 16) + + dmp[(x + 1) & (DM_WIDTH - 1)]; + rgb13 += 0x10040100 + - ((rgb13 & 0x1e0001e0) >> 5) + - ((rgb13 & 0x00070000) >> 6); + ((guint32 *)obptr)[0] = + ((rgb02 & 0x0f800000) << 4) | + ((rgb02 & 0x0003f000) << 9) | + ((rgb02 & 0x000000f8) << 13) | + ((rgb13 & 0x0f800000) >> 12) | + ((rgb13 & 0x0003f000) >> 7) | + ((rgb13 & 0x000000f8) >> 3); + rgb02 = + ((g2r2b1g1 & 0x0000ff00) << 12) + + ((g2r2b1g1 & 0x000000ff) << 10) + + ((b3g3r3b2 & 0xff000000) >> 24) + + dmp[(x + 2) & (DM_WIDTH - 1)]; + rgb02 += 0x10040100 + - ((rgb02 & 0x1e0001e0) >> 5) + - ((rgb02 & 0x00070000) >> 6); + rgb13 = + ((b3g3r3b2 & 0x00ff0000) << 4) + + ((b3g3r3b2 & 0x0000ff00) << 2) + + (b3g3r3b2 & 0x000000ff) + + dmp[(x + 3) & (DM_WIDTH - 1)]; + rgb13 += 0x10040100 + - ((rgb13 & 0x1e0001e0) >> 5) + - ((rgb13 & 0x00070000) >> 6); + ((guint32 *)obptr)[1] = + ((rgb02 & 0x0f800000) << 4) | + ((rgb02 & 0x0003f000) << 9) | + ((rgb02 & 0x000000f8) << 13) | + ((rgb13 & 0x0f800000) >> 12) | + ((rgb13 & 0x0003f000) >> 7) | + ((rgb13 & 0x000000f8) >> 3); +#endif bp2 += 12; obptr += 8; } @@ -1916,6 +1992,10 @@ gdk_rgb_convert_565_d (GdkRgbInfo *image_info, GdkImage *image, } #endif +#undef GDK_RGB_FAST_BE565_RGB +#undef GDK_RGB_FAST_BE565_GRAY +#undef GDK_RGB_FAST_BE565_DITHER + static void gdk_rgb_convert_555 (GdkRgbInfo *image_info, GdkImage *image, gint x0, gint y0, gint width, gint height, From e92e770558e9ab5a44f8f7f50984412e444e01e9 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 4 Aug 2026 18:46:39 +0200 Subject: [PATCH 21/56] gdk: support 3-bit StaticColor visuals --- gdk/gdkrgb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/gdkrgb.c b/gdk/gdkrgb.c index 4f2e0d44f..cd848e902 100644 --- a/gdk/gdkrgb.c +++ b/gdk/gdkrgb.c @@ -3431,7 +3431,7 @@ gdk_rgb_select_conv (GdkRgbInfo *image_info) conv = gdk_rgb_convert_gray4_pack; conv_d = gdk_rgb_convert_gray4_d_pack; } - else if (bpp == 4 && depth == 4 && + else if (bpp == 4 && depth <= 4 && depth >= 3 && vtype == GDK_VISUAL_STATIC_COLOR) conv = gdk_rgb_convert_4_pack; From b58fc237658965b313abb751deb71574584161c2 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 4 Aug 2026 19:08:54 +0200 Subject: [PATCH 22/56] gdk: support 2-bit StaticColor visuals --- gdk/gdkrgb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/gdkrgb.c b/gdk/gdkrgb.c index cd848e902..edb88c6d8 100644 --- a/gdk/gdkrgb.c +++ b/gdk/gdkrgb.c @@ -3431,7 +3431,7 @@ gdk_rgb_select_conv (GdkRgbInfo *image_info) conv = gdk_rgb_convert_gray4_pack; conv_d = gdk_rgb_convert_gray4_d_pack; } - else if (bpp == 4 && depth <= 4 && depth >= 3 && + else if (bpp == 4 && depth <= 4 && depth >= 2 && vtype == GDK_VISUAL_STATIC_COLOR) conv = gdk_rgb_convert_4_pack; From a923aa3120628bec953ac12624417cd3378da8f3 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 4 Aug 2026 19:41:58 +0200 Subject: [PATCH 23/56] gdk: fix packed StaticColor dithering --- gdk/gdkrgb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdk/gdkrgb.c b/gdk/gdkrgb.c index edb88c6d8..3da8c820f 100644 --- a/gdk/gdkrgb.c +++ b/gdk/gdkrgb.c @@ -2697,7 +2697,7 @@ gdk_rgb_convert_4_pack (GdkRgbInfo *image_info, GdkImage *image, r = *bp2++; g = *bp2++; b = *bp2++; - dith = (dmp[(x_align + x + 1) & (DM_WIDTH - 1)] << 2) | 3; + dith = (dmp[(x_align + x) & (DM_WIDTH - 1)] << 2) | 3; ix = (((r + dith) & 0x100) >> 2) | (((g + 258 - dith) & 0x100) >> 5) | (((b + dith) & 0x100) >> 8); @@ -2724,7 +2724,7 @@ gdk_rgb_convert_4_pack (GdkRgbInfo *image_info, GdkImage *image, r = *bp2++; g = *bp2++; b = *bp2++; - dith = (dmp[(x_align + x + 1) & (DM_WIDTH - 1)] << 2) | 3; + dith = (dmp[(x_align + x) & (DM_WIDTH - 1)] << 2) | 3; ix = (((r + dith) & 0x100) >> 2) | (((g + 258 - dith) & 0x100) >> 5) | (((b + dith) & 0x100) >> 8); From 8984300bbaa4d8f77ebea668b8cbe416d6bc3546 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 4 Aug 2026 20:24:57 +0200 Subject: [PATCH 24/56] gdk: optimize packed StaticColor conversion --- gdk/gdkrgb.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/gdk/gdkrgb.c b/gdk/gdkrgb.c index 3da8c820f..e4f63ddaf 100644 --- a/gdk/gdkrgb.c +++ b/gdk/gdkrgb.c @@ -2706,7 +2706,7 @@ gdk_rgb_convert_4_pack (GdkRgbInfo *image_info, GdkImage *image, obptr++; x++; } - while (x < width) + while (x + 1 < width) { r = *bp2++; g = *bp2++; @@ -2717,9 +2717,6 @@ gdk_rgb_convert_4_pack (GdkRgbInfo *image_info, GdkImage *image, (((b + dith) & 0x100) >> 8); pix0 = (colorcube_d[ix]); x++; - if (x == width) - pix1 = (*obptr & 0x0F); - else { r = *bp2++; g = *bp2++; @@ -2733,6 +2730,18 @@ gdk_rgb_convert_4_pack (GdkRgbInfo *image_info, GdkImage *image, } *obptr++ = (pix0 << 4) | pix1; } + if (x < width) + { + r = *bp2++; + g = *bp2++; + b = *bp2++; + dith = (dmp[(x_align + x) & (DM_WIDTH - 1)] << 2) | 3; + ix = (((r + dith) & 0x100) >> 2) | + (((g + 258 - dith) & 0x100) >> 5) | + (((b + dith) & 0x100) >> 8); + pix0 = (colorcube_d[ix]); + *obptr = (pix0 << 4) | (*obptr & 0x0F); + } bptr += rowstride; obuf += bpl; } From 73183e1a30cfba4f065ab220b44183fa94e89455 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 5 Aug 2026 12:12:13 +0200 Subject: [PATCH 25/56] gdk: add high-depth RGB image drawing Add RGB48 and RGBX64 drawing APIs with direct conversion for common 15-, 16-, 24-, 30- and 32-bit TrueColor visuals. Preserve the existing GdkRGB path as a fallback for unsupported visuals and maximum dithering. --- docs/reference/gdk/gdk2-sections.txt | 4 + gdk/Makefile.am | 1 + gdk/gdk.symbols | 9 + gdk/gdkrgb.h | 42 ++ gdk/gdkrgb16.c | 742 +++++++++++++++++++++++++++ gdk/makefile.msc | 1 + 6 files changed, 799 insertions(+) create mode 100644 gdk/gdkrgb16.c diff --git a/docs/reference/gdk/gdk2-sections.txt b/docs/reference/gdk/gdk2-sections.txt index c3d6040b7..4987c31ee 100644 --- a/docs/reference/gdk/gdk2-sections.txt +++ b/docs/reference/gdk/gdk2-sections.txt @@ -335,6 +335,10 @@ gdk_draw_indexed_image gdk_draw_gray_image gdk_draw_rgb_32_image gdk_draw_rgb_32_image_dithalign +gdk_draw_rgb_48_image +gdk_draw_rgb_48_image_dithalign +gdk_draw_rgbx_64_image +gdk_draw_rgbx_64_image_dithalign GdkRgbDither diff --git a/gdk/Makefile.am b/gdk/Makefile.am index 1b186afa1..d83d40db8 100644 --- a/gdk/Makefile.am +++ b/gdk/Makefile.am @@ -145,6 +145,7 @@ gdk_c_sources = \ gdkrectangle.c \ gdkregion-generic.c \ gdkrgb.c \ + gdkrgb16.c \ gdkscreen.c \ gdkselection.c \ gdkvisual.c \ diff --git a/gdk/gdk.symbols b/gdk/gdk.symbols index ee1bc0c98..b48eac70b 100644 --- a/gdk/gdk.symbols +++ b/gdk/gdk.symbols @@ -1118,6 +1118,15 @@ gdk_draw_rgb_image_dithalign #endif #endif +#if IN_HEADER(__GDK_RGB_H__) +#if IN_FILE(__GDK_RGB16_C__) +gdk_draw_rgb_48_image +gdk_draw_rgb_48_image_dithalign +gdk_draw_rgbx_64_image +gdk_draw_rgbx_64_image_dithalign +#endif +#endif + #if IN_HEADER(__GDK_SCREEN_H__) #if IN_FILE(__GDK_SCREEN_C__) gdk_screen_get_type G_GNUC_CONST diff --git a/gdk/gdkrgb.h b/gdk/gdkrgb.h index dd71d403f..44c576fb5 100644 --- a/gdk/gdkrgb.h +++ b/gdk/gdkrgb.h @@ -147,6 +147,48 @@ gboolean gdk_rgb_colormap_ditherable (GdkColormap *cmap); #endif #endif /* GDK_DISABLE_DEPRECATED */ +void gdk_draw_rgb_48_image (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgb_buf, + gint rowstride); +void gdk_draw_rgb_48_image_dithalign (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgb_buf, + gint rowstride, + gint xdith, + gint ydith); +void gdk_draw_rgbx_64_image (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgbx_buf, + gint rowstride); +void gdk_draw_rgbx_64_image_dithalign + (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgbx_buf, + gint rowstride, + gint xdith, + gint ydith); + G_END_DECLS diff --git a/gdk/gdkrgb16.c b/gdk/gdkrgb16.c new file mode 100644 index 000000000..2564ef0d8 --- /dev/null +++ b/gdk/gdkrgb16.c @@ -0,0 +1,742 @@ +/* GTK - The GIMP Toolkit + * High-precision GdkRGB image conversion + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + */ + +#include "config.h" + +#include + +#include "gdkinternals.h" +#include "gdkrgb.h" +#include "gdkscreen.h" +#include "gdkalias.h" + +typedef void (*GdkRgb16ConvFunc) (GdkImage *image, + gint x0, + gint y0, + gint width, + gint height, + const guint16 *buf, + gint rowstride); + +#define GDK_RGB16_STORE_RGB(p, r, g, b, a) G_STMT_START { \ + (p)[0] = (r) >> 8; \ + (p)[1] = (g) >> 8; \ + (p)[2] = (b) >> 8; \ + (p) += 3; \ +} G_STMT_END + +#define GDK_RGB16_STORE_BGR(p, r, g, b, a) G_STMT_START { \ + (p)[0] = (b) >> 8; \ + (p)[1] = (g) >> 8; \ + (p)[2] = (r) >> 8; \ + (p) += 3; \ +} G_STMT_END + +#define GDK_RGB16_STORE_BGRA(p, r, g, b, a) G_STMT_START { \ + (p)[0] = (b) >> 8; \ + (p)[1] = (g) >> 8; \ + (p)[2] = (r) >> 8; \ + (p)[3] = 0xff; \ + (p) += 4; \ +} G_STMT_END + +#define GDK_RGB16_STORE_ARGB(p, r, g, b, a) G_STMT_START { \ + (p)[0] = 0xff; \ + (p)[1] = (r) >> 8; \ + (p)[2] = (g) >> 8; \ + (p)[3] = (b) >> 8; \ + (p) += 4; \ +} G_STMT_END + +#define GDK_RGB16_STORE_RGB0_NATIVE(p, r, g, b, a) G_STMT_START { \ + guint32 pixel__ = (((b) >> 8) << 16) | \ + (((g) >> 8) << 8) | \ + ((r) >> 8); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 4; \ +} G_STMT_END + +#define GDK_RGB16_STORE_565(p, r, g, b, a) G_STMT_START { \ + guint16 pixel__ = ((r) & 0xf800) | \ + (((g) & 0xfc00) >> 5) | \ + ((b) >> 11); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 2; \ +} G_STMT_END + +#define GDK_RGB16_STORE_565_BR(p, r, g, b, a) G_STMT_START { \ + guint16 pixel__ = ((r) & 0xf800) | \ + (((g) & 0xfc00) >> 5) | \ + ((b) >> 11); \ + pixel__ = (pixel__ >> 8) | (pixel__ << 8); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 2; \ +} G_STMT_END + +#define GDK_RGB16_STORE_555(p, r, g, b, a) G_STMT_START { \ + guint16 pixel__ = (((r) & 0xf800) >> 1) | \ + (((g) & 0xf800) >> 6) | \ + ((b) >> 11); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 2; \ +} G_STMT_END + +#define GDK_RGB16_STORE_555_BR(p, r, g, b, a) G_STMT_START { \ + guint16 pixel__ = (((r) & 0xf800) >> 1) | \ + (((g) & 0xf800) >> 6) | \ + ((b) >> 11); \ + pixel__ = (pixel__ >> 8) | (pixel__ << 8); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 2; \ +} G_STMT_END + +#define GDK_RGB16_STORE_101010_R_LOW_LE(p, r, g, b, a) \ +G_STMT_START { \ + guint32 pixel__ = (a) | \ + (((b) >> 6) << 20) | \ + (((g) >> 6) << 10) | \ + ((r) >> 6); \ + pixel__ = GUINT32_TO_LE (pixel__); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 4; \ +} G_STMT_END + +#define GDK_RGB16_STORE_101010_B_LOW_LE(p, r, g, b, a) \ +G_STMT_START { \ + guint32 pixel__ = (a) | \ + (((r) >> 6) << 20) | \ + (((g) >> 6) << 10) | \ + ((b) >> 6); \ + pixel__ = GUINT32_TO_LE (pixel__); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 4; \ +} G_STMT_END + +#define GDK_RGB16_STORE_101010_R_LOW_BE(p, r, g, b, a) \ +G_STMT_START { \ + guint32 pixel__ = (a) | \ + (((b) >> 6) << 20) | \ + (((g) >> 6) << 10) | \ + ((r) >> 6); \ + pixel__ = GUINT32_TO_BE (pixel__); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 4; \ +} G_STMT_END + +#define GDK_RGB16_STORE_101010_B_LOW_BE(p, r, g, b, a) \ +G_STMT_START { \ + guint32 pixel__ = (a) | \ + (((r) >> 6) << 20) | \ + (((g) >> 6) << 10) | \ + ((b) >> 6); \ + pixel__ = GUINT32_TO_BE (pixel__); \ + memcpy ((p), &pixel__, sizeof (pixel__)); \ + (p) += 4; \ +} G_STMT_END + +#define DEFINE_GDK_RGB16_CONVERTER(name, words, pixel_bytes, store) \ +static void \ +name (GdkImage *image, \ + gint x0, \ + gint y0, \ + gint width, \ + gint height, \ + const guint16 *buf, \ + gint rowstride) \ +{ \ + const guchar *row; \ + const guint16 *src; \ + guchar *dst_row; \ + guchar *dst; \ + guint32 r, g, b; \ + guint32 alpha_mask; \ + gint x, y; \ + \ + row = (const guchar *) buf; \ + dst_row = ((guchar *) image->mem) + \ + y0 * image->bpl + x0 * (pixel_bytes); \ + alpha_mask = image->depth == 32 ? 0xc0000000U : 0; \ + (void) alpha_mask; \ + \ + for (y = 0; y < height; y++) \ + { \ + src = (const guint16 *) row; \ + dst = dst_row; \ + \ + for (x = 0; x < width; x++) \ + { \ + r = src[0]; \ + g = src[1]; \ + b = src[2]; \ + src += (words); \ + store (dst, r, g, b, alpha_mask); \ + } \ + \ + row += rowstride; \ + dst_row += image->bpl; \ + } \ +} + +#define DEFINE_GDK_RGB16_PAIR(suffix, pixel_bytes, store) \ + DEFINE_GDK_RGB16_CONVERTER (gdk_rgb48_convert_ ## suffix, \ + 3, pixel_bytes, store) \ + DEFINE_GDK_RGB16_CONVERTER (gdk_rgbx64_convert_ ## suffix, \ + 4, pixel_bytes, store) + +DEFINE_GDK_RGB16_PAIR (rgb, 3, GDK_RGB16_STORE_RGB) +DEFINE_GDK_RGB16_PAIR (bgr, 3, GDK_RGB16_STORE_BGR) +DEFINE_GDK_RGB16_PAIR (bgra, 4, GDK_RGB16_STORE_BGRA) +DEFINE_GDK_RGB16_PAIR (argb, 4, GDK_RGB16_STORE_ARGB) +DEFINE_GDK_RGB16_PAIR (rgb0_native, 4, GDK_RGB16_STORE_RGB0_NATIVE) +DEFINE_GDK_RGB16_PAIR (565, 2, GDK_RGB16_STORE_565) +DEFINE_GDK_RGB16_PAIR (565_br, 2, GDK_RGB16_STORE_565_BR) +DEFINE_GDK_RGB16_PAIR (555, 2, GDK_RGB16_STORE_555) +DEFINE_GDK_RGB16_PAIR (555_br, 2, GDK_RGB16_STORE_555_BR) +DEFINE_GDK_RGB16_PAIR (101010_r_low_le, 4, + GDK_RGB16_STORE_101010_R_LOW_LE) +DEFINE_GDK_RGB16_PAIR (101010_b_low_le, 4, + GDK_RGB16_STORE_101010_B_LOW_LE) +DEFINE_GDK_RGB16_PAIR (101010_r_low_be, 4, + GDK_RGB16_STORE_101010_R_LOW_BE) +DEFINE_GDK_RGB16_PAIR (101010_b_low_be, 4, + GDK_RGB16_STORE_101010_B_LOW_BE) + +#undef DEFINE_GDK_RGB16_PAIR +#undef DEFINE_GDK_RGB16_CONVERTER + +#undef GDK_RGB16_STORE_RGB +#undef GDK_RGB16_STORE_BGR +#undef GDK_RGB16_STORE_BGRA +#undef GDK_RGB16_STORE_ARGB +#undef GDK_RGB16_STORE_RGB0_NATIVE +#undef GDK_RGB16_STORE_565 +#undef GDK_RGB16_STORE_565_BR +#undef GDK_RGB16_STORE_555 +#undef GDK_RGB16_STORE_555_BR +#undef GDK_RGB16_STORE_101010_R_LOW_LE +#undef GDK_RGB16_STORE_101010_B_LOW_LE +#undef GDK_RGB16_STORE_101010_R_LOW_BE +#undef GDK_RGB16_STORE_101010_B_LOW_BE + +static GdkRgb16ConvFunc +gdk_rgb16_select_converter (GdkDrawable *drawable, + gboolean is_rgbx, + GdkRgbDither dith) +{ + GdkVisual *visual; + GdkByteOrder byte_order; + GdkScreen *screen; + guint32 red_mask, green_mask, blue_mask; + gint depth; + gint bits_per_pixel; + gboolean byte_reversed; + gboolean mask_rgb; + gboolean mask_bgr; + +#define SELECT_PAIR(suffix) \ + return is_rgbx ? gdk_rgbx64_convert_ ## suffix \ + : gdk_rgb48_convert_ ## suffix + + /* + * Preserve the existing GdkRGB dither path for maximum dithering + * and for unknown future values. + */ + if (dith != GDK_RGB_DITHER_NONE && + dith != GDK_RGB_DITHER_NORMAL) + return NULL; + + visual = gdk_drawable_get_visual (drawable); + if (visual == NULL) + return NULL; + + if (gdk_visual_get_visual_type (visual) != GDK_VISUAL_TRUE_COLOR) + return NULL; + + screen = gdk_visual_get_screen (visual); + depth = gdk_visual_get_depth (visual); + byte_order = gdk_visual_get_byte_order (visual); + + gdk_visual_get_red_pixel_details (visual, &red_mask, NULL, NULL); + gdk_visual_get_green_pixel_details (visual, &green_mask, NULL, NULL); + gdk_visual_get_blue_pixel_details (visual, &blue_mask, NULL, NULL); + + bits_per_pixel = _gdk_windowing_get_bits_for_depth + (gdk_screen_get_display (screen), depth); + +#if G_BYTE_ORDER == G_BIG_ENDIAN + byte_reversed = byte_order == GDK_LSB_FIRST; +#else + byte_reversed = byte_order == GDK_MSB_FIRST; +#endif + + mask_rgb = red_mask == 0xff0000 && + green_mask == 0x00ff00 && + blue_mask == 0x0000ff; + + mask_bgr = red_mask == 0x0000ff && + green_mask == 0x00ff00 && + blue_mask == 0xff0000; + + if (bits_per_pixel == 16 && + depth == 16 && + red_mask == 0xf800 && + green_mask == 0x07e0 && + blue_mask == 0x001f) + { + if (byte_reversed) + SELECT_PAIR (565_br); + + SELECT_PAIR (565); + } + + if (bits_per_pixel == 16 && + depth == 15 && + red_mask == 0x7c00 && + green_mask == 0x03e0 && + blue_mask == 0x001f) + { + if (byte_reversed) + SELECT_PAIR (555_br); + + SELECT_PAIR (555); + } + + if (bits_per_pixel == 24 && + depth == 24 && + ((mask_rgb && byte_order == GDK_LSB_FIRST) || + (mask_bgr && byte_order == GDK_MSB_FIRST))) + SELECT_PAIR (bgr); + + if (bits_per_pixel == 24 && + depth == 24 && + ((mask_rgb && byte_order == GDK_MSB_FIRST) || + (mask_bgr && byte_order == GDK_LSB_FIRST))) + SELECT_PAIR (rgb); + + if (bits_per_pixel == 32 && + (depth == 24 || depth == 32) && + mask_rgb && + byte_order == GDK_MSB_FIRST) + SELECT_PAIR (argb); + + if (bits_per_pixel == 32 && + (depth == 24 || depth == 32) && + mask_rgb && + byte_order == GDK_LSB_FIRST) + SELECT_PAIR (bgra); + +#if G_BYTE_ORDER == G_BIG_ENDIAN + if (bits_per_pixel == 32 && + depth == 24 && + mask_bgr && + byte_order == GDK_MSB_FIRST) + SELECT_PAIR (rgb0_native); +#else + if (bits_per_pixel == 32 && + depth == 24 && + mask_bgr && + byte_order == GDK_LSB_FIRST) + SELECT_PAIR (rgb0_native); +#endif + + if (bits_per_pixel == 32 && + (depth == 30 || depth == 32) && + byte_order == GDK_LSB_FIRST && + red_mask == 0x000003ff && + green_mask == 0x000ffc00 && + blue_mask == 0x3ff00000) + SELECT_PAIR (101010_r_low_le); + + if (bits_per_pixel == 32 && + (depth == 30 || depth == 32) && + byte_order == GDK_LSB_FIRST && + red_mask == 0x3ff00000 && + green_mask == 0x000ffc00 && + blue_mask == 0x000003ff) + SELECT_PAIR (101010_b_low_le); + + if (bits_per_pixel == 32 && + (depth == 30 || depth == 32) && + byte_order == GDK_MSB_FIRST && + red_mask == 0x000003ff && + green_mask == 0x000ffc00 && + blue_mask == 0x3ff00000) + SELECT_PAIR (101010_r_low_be); + + if (bits_per_pixel == 32 && + (depth == 30 || depth == 32) && + byte_order == GDK_MSB_FIRST && + red_mask == 0x3ff00000 && + green_mask == 0x000ffc00 && + blue_mask == 0x000003ff) + SELECT_PAIR (101010_b_low_be); + +#undef SELECT_PAIR + + return NULL; +} + +static void +gdk_rgb16_draw_direct (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + const guint16 *buf, + gint pixel_words, + gint rowstride, + GdkRgb16ConvFunc converter) +{ + GdkScreen *screen; + GdkImage *image; + const guint16 *tile; + gint depth; + gint x0, y0; + gint xs0, ys0; + gint width1, height1; + + screen = gdk_drawable_get_screen (drawable); + depth = gdk_drawable_get_depth (drawable); + + for (y0 = 0; y0 < height; y0 += GDK_SCRATCH_IMAGE_HEIGHT) + { + height1 = MIN (height - y0, GDK_SCRATCH_IMAGE_HEIGHT); + + for (x0 = 0; x0 < width; x0 += GDK_SCRATCH_IMAGE_WIDTH) + { + width1 = MIN (width - x0, GDK_SCRATCH_IMAGE_WIDTH); + + tile = (const guint16 *) + ((const guchar *) buf + + y0 * rowstride + + x0 * pixel_words * sizeof (guint16)); + + image = _gdk_image_get_scratch (screen, + width1, + height1, + depth, + &xs0, + &ys0); + + converter (image, + xs0, + ys0, + width1, + height1, + tile, + rowstride); + + gdk_draw_image (drawable, + gc, + image, + xs0, + ys0, + x + x0, + y + y0, + width1, + height1); + } + } +} + +static void +gdk_rgb16_draw_fallback (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *buf, + gint pixel_words, + gint rowstride, + gint xdith, + gint ydith) +{ + const guchar *input_row; + const guint16 *input; + guchar *stage; + guchar *output; + gint stage_rowstride; + gint x0, y0; + gint x1, y1; + gint width1, height1; + + stage = g_malloc (MIN (width, GDK_SCRATCH_IMAGE_WIDTH) * + MIN (height, GDK_SCRATCH_IMAGE_HEIGHT) * 3); + + for (y0 = 0; y0 < height; y0 += GDK_SCRATCH_IMAGE_HEIGHT) + { + height1 = MIN (height - y0, GDK_SCRATCH_IMAGE_HEIGHT); + + for (x0 = 0; x0 < width; x0 += GDK_SCRATCH_IMAGE_WIDTH) + { + width1 = MIN (width - x0, GDK_SCRATCH_IMAGE_WIDTH); + stage_rowstride = width1 * 3; + + input_row = (const guchar *) buf + + y0 * rowstride + + x0 * pixel_words * sizeof (guint16); + output = stage; + + for (y1 = 0; y1 < height1; y1++) + { + input = (const guint16 *) input_row; + + for (x1 = 0; x1 < width1; x1++) + { + *output++ = input[0] >> 8; + *output++ = input[1] >> 8; + *output++ = input[2] >> 8; + input += pixel_words; + } + + input_row += rowstride; + } + + gdk_draw_rgb_image_dithalign (drawable, + gc, + x + x0, + y + y0, + width1, + height1, + dith, + stage, + stage_rowstride, + xdith, + ydith); + } + } + + g_free (stage); +} + +static void +gdk_draw_rgb16_image (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *buf, + gint rowstride, + gint xdith, + gint ydith, + gboolean is_rgbx) +{ + GdkRgb16ConvFunc converter; + gint pixel_words; + + g_return_if_fail (GDK_IS_DRAWABLE (drawable)); + g_return_if_fail (GDK_IS_GC (gc)); + g_return_if_fail (buf != NULL); + g_return_if_fail ((((guintptr) buf) & 1) == 0); + g_return_if_fail ((rowstride & 1) == 0); + + if (width <= 0 || height <= 0) + return; + + pixel_words = is_rgbx ? 4 : 3; + converter = gdk_rgb16_select_converter (drawable, is_rgbx, dith); + + if (converter != NULL) + gdk_rgb16_draw_direct (drawable, + gc, + x, + y, + width, + height, + buf, + pixel_words, + rowstride, + converter); + else + gdk_rgb16_draw_fallback (drawable, + gc, + x, + y, + width, + height, + dith, + buf, + pixel_words, + rowstride, + xdith, + ydith); +} + +/** + * gdk_draw_rgb_48_image: + * @drawable: a #GdkDrawable + * @gc: a #GdkGC + * @x: destination X coordinate + * @y: destination Y coordinate + * @width: image width + * @height: image height + * @dith: dithering mode + * @rgb_buf: host-endian RGB data with three 16-bit channels per pixel + * @rowstride: number of bytes between consecutive rows + * + * Draws 48-bit RGB image data. + **/ +void +gdk_draw_rgb_48_image (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgb_buf, + gint rowstride) +{ + gdk_draw_rgb16_image (drawable, + gc, + x, + y, + width, + height, + dith, + rgb_buf, + rowstride, + 0, + 0, + FALSE); +} + +/** + * gdk_draw_rgb_48_image_dithalign: + * @drawable: a #GdkDrawable + * @gc: a #GdkGC + * @x: destination X coordinate + * @y: destination Y coordinate + * @width: image width + * @height: image height + * @dith: dithering mode + * @rgb_buf: host-endian RGB data with three 16-bit channels per pixel + * @rowstride: number of bytes between consecutive rows + * @xdith: X dither offset + * @ydith: Y dither offset + * + * Draws 48-bit RGB image data with explicit dither alignment. + **/ +void +gdk_draw_rgb_48_image_dithalign (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgb_buf, + gint rowstride, + gint xdith, + gint ydith) +{ + gdk_draw_rgb16_image (drawable, + gc, + x, + y, + width, + height, + dith, + rgb_buf, + rowstride, + xdith, + ydith, + FALSE); +} + +/** + * gdk_draw_rgbx_64_image: + * @drawable: a #GdkDrawable + * @gc: a #GdkGC + * @x: destination X coordinate + * @y: destination Y coordinate + * @width: image width + * @height: image height + * @dith: dithering mode + * @rgbx_buf: host-endian RGBX data with four 16-bit channels per pixel + * @rowstride: number of bytes between consecutive rows + * + * Draws 64-bit RGBX image data. The X channel is ignored. + **/ +void +gdk_draw_rgbx_64_image (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgbx_buf, + gint rowstride) +{ + gdk_draw_rgb16_image (drawable, + gc, + x, + y, + width, + height, + dith, + rgbx_buf, + rowstride, + 0, + 0, + TRUE); +} + +/** + * gdk_draw_rgbx_64_image_dithalign: + * @drawable: a #GdkDrawable + * @gc: a #GdkGC + * @x: destination X coordinate + * @y: destination Y coordinate + * @width: image width + * @height: image height + * @dith: dithering mode + * @rgbx_buf: host-endian RGBX data with four 16-bit channels per pixel + * @rowstride: number of bytes between consecutive rows + * @xdith: X dither offset + * @ydith: Y dither offset + * + * Draws 64-bit RGBX image data with explicit dither alignment. + * The X channel is ignored. + **/ +void +gdk_draw_rgbx_64_image_dithalign (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + gint width, + gint height, + GdkRgbDither dith, + const guint16 *rgbx_buf, + gint rowstride, + gint xdith, + gint ydith) +{ + gdk_draw_rgb16_image (drawable, + gc, + x, + y, + width, + height, + dith, + rgbx_buf, + rowstride, + xdith, + ydith, + TRUE); +} + +#define __GDK_RGB16_C__ +#include "gdkaliasdef.c" diff --git a/gdk/makefile.msc b/gdk/makefile.msc index e9f816931..6175c3f21 100644 --- a/gdk/makefile.msc +++ b/gdk/makefile.msc @@ -92,6 +92,7 @@ gdk_OBJECTS = \ gdkrectangle.obj \ gdkregion-generic.obj \ gdkrgb.obj \ + gdkrgb16.obj \ gdkscreen.obj \ gdkselection.obj \ gdkvisual.obj \ From 450e7840372255cb9340465f1a9f2086d0667b65 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 5 Aug 2026 14:20:10 +0200 Subject: [PATCH 26/56] gtk: replace deprecated IM module parser helpers Use local compile-time selected parser helpers with a legacy Pango fallback. Avoid temporary string decompression allocations and use unlocked stdio where available. --- configure.ac | 2 +- gtk/gtkimmodule.c | 246 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 211 insertions(+), 37 deletions(-) diff --git a/configure.ac b/configure.ac index 29297423f..101007915 100644 --- a/configure.ac +++ b/configure.ac @@ -469,7 +469,7 @@ if test "x$enable_rebuilds" = "xyes" && \ fi AC_SUBST(REBUILD) -AC_CHECK_FUNCS(lstat mkstemp flockfile getc_unlocked) +AC_CHECK_FUNCS(lstat mkstemp flockfile getc_unlocked _getc_nolock) AC_CHECK_FUNCS(localtime_r) # _NL_TIME_FIRST_WEEKDAY is an enum and not a define diff --git a/gtk/gtkimmodule.c b/gtk/gtkimmodule.c index a3a77c474..07c38f0b4 100644 --- a/gtk/gtkimmodule.c +++ b/gtk/gtkimmodule.c @@ -309,11 +309,186 @@ add_builtin_module (const gchar *module_name, return module; } +/* + * Keep IM module parsing independent of Pango's deprecated helpers. + */ + +/* + * Use unlocked input for the privately owned module-cache stream. + */ +#if defined(HAVE_GETC_UNLOCKED) +#define GTK_IM_MODULE_GETC(stream) getc_unlocked (stream) +#elif defined(HAVE__GETC_NOLOCK) +#define GTK_IM_MODULE_GETC(stream) _getc_nolock (stream) +#else +#define GTK_IM_MODULE_GETC(stream) getc (stream) +#endif + +static gchar * +gtk_im_module_scan_string (const gchar **pos) +{ + const gchar *p; + const gchar *q; + gchar *result; + gchar *out; + gsize len; + + p = *pos; + + while (g_ascii_isspace (*p)) + p++; + + if (*p != '"') + return NULL; + + p++; + q = p; + len = 0; + + while (*q && *q != '"') + { + if (*q == '\\') + { + q++; + + if (!*q) + return NULL; + } + + q++; + len++; + } + + if (*q != '"') + return NULL; + + result = g_malloc (len + 1); + out = result; + + while (p < q) + { + gchar c; + + c = *p++; + + if (c == '\\') + { + c = *p++; + + switch (c) + { + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + default: break; + } + } + + *out++ = c; + } + + *out = '\0'; + *pos = q + 1; + + return result; +} + +static gboolean +gtk_im_module_skip_space (const gchar **pos) +{ + while (g_ascii_isspace (**pos)) + (*pos)++; + + return **pos != '\0'; +} + +static gint +gtk_im_module_read_line (FILE *stream, + GString *str) +{ + gboolean escaped; + gboolean comment; + gint n_read; + gint lines; + gint c; + gint next_c; + + escaped = FALSE; + comment = FALSE; + n_read = 0; + lines = 1; + + g_string_truncate (str, 0); + + while (TRUE) + { + c = GTK_IM_MODULE_GETC (stream); + + if (c == EOF) + { + if (escaped) + g_string_append_c (str, '\\'); + + break; + } + + n_read++; + + if (c == '\r' || c == '\n') + { + next_c = GTK_IM_MODULE_GETC (stream); + + if (next_c != EOF && + !((c == '\r' && next_c == '\n') || + (c == '\n' && next_c == '\r'))) + ungetc (next_c, stream); + + if (escaped) + { + escaped = FALSE; + lines++; + continue; + } + + break; + } + + if (escaped) + { + escaped = FALSE; + + if (c == '#') + g_string_append_c (str, '#'); + else + { + g_string_append_c (str, '\\'); + g_string_append_c (str, c); + } + } + else if (!comment) + { + if (c == '#') + comment = TRUE; + else if (c == '\\') + escaped = TRUE; + else + g_string_append_c (str, c); + } + } + + + return n_read > 0 ? lines : 0; +} + +#undef GTK_IM_MODULE_GETC + static void gtk_im_module_initialize (void) { GString *line_buf = g_string_new (NULL); - GString *tmp_buf = g_string_new (NULL); gchar *filename = gtk_rc_get_im_module_file(); FILE *file; gboolean have_error = FALSE; @@ -387,18 +562,17 @@ gtk_im_module_initialize (void) * we allow no file at all. */ g_string_free (line_buf, TRUE); - g_string_free (tmp_buf, TRUE); g_free (filename); return; } - while (!have_error && pango_read_line (file, line_buf)) + while (!have_error && gtk_im_module_read_line (file, line_buf)) { const char *p; p = line_buf->str; - if (!pango_skip_space (&p)) + if (!gtk_im_module_skip_space (&p)) { /* Blank line marking the end of a module */ @@ -413,61 +587,62 @@ gtk_im_module_initialize (void) } if (!module) - { - /* Read a module location - */ - module = g_object_new (GTK_TYPE_IM_MODULE, NULL); + { + /* Read a module location + */ + module = g_object_new (GTK_TYPE_IM_MODULE, NULL); + module->path = gtk_im_module_scan_string (&p); - if (!pango_scan_string (&p, tmp_buf) || - pango_skip_space (&p)) - { - g_warning ("Error parsing context info in '%s'\n %s", - filename, line_buf->str); - have_error = TRUE; - } + if (!module->path || gtk_im_module_skip_space (&p)) + { + g_warning ("Error parsing context info in '%s'\n %s", + filename, line_buf->str); + have_error = TRUE; + continue; + } - module->path = g_strdup (tmp_buf->str); #ifdef G_OS_WIN32 - correct_libdir_prefix (&module->path); + correct_libdir_prefix (&module->path); #endif - g_type_module_set_name (G_TYPE_MODULE (module), module->path); - } + g_type_module_set_name (G_TYPE_MODULE (module), module->path); + } else { GtkIMContextInfo *info = g_new0 (GtkIMContextInfo, 1); /* Read information about a context type */ - if (!pango_scan_string (&p, tmp_buf)) - goto context_error; - info->context_id = g_strdup (tmp_buf->str); + info->context_id = gtk_im_module_scan_string (&p); + if (!info->context_id) + goto context_error; - if (!pango_scan_string (&p, tmp_buf)) - goto context_error; - info->context_name = g_strdup (tmp_buf->str); + info->context_name = gtk_im_module_scan_string (&p); + if (!info->context_name) + goto context_error; - if (!pango_scan_string (&p, tmp_buf)) - goto context_error; - info->domain = g_strdup (tmp_buf->str); + info->domain = gtk_im_module_scan_string (&p); + if (!info->domain) + goto context_error; - if (!pango_scan_string (&p, tmp_buf)) - goto context_error; - info->domain_dirname = g_strdup (tmp_buf->str); + info->domain_dirname = gtk_im_module_scan_string (&p); + if (!info->domain_dirname) + goto context_error; #ifdef G_OS_WIN32 correct_localedir_prefix ((char **) &info->domain_dirname); #endif - if (!pango_scan_string (&p, tmp_buf)) - goto context_error; - info->default_locales = g_strdup (tmp_buf->str); + info->default_locales = gtk_im_module_scan_string (&p); + if (!info->default_locales) + goto context_error; - if (pango_skip_space (&p)) + if (gtk_im_module_skip_space (&p)) goto context_error; infos = g_slist_prepend (infos, info); continue; context_error: + free_info (info); g_warning ("Error parsing context info in '%s'\n %s", filename, line_buf->str); have_error = TRUE; @@ -491,7 +666,6 @@ gtk_im_module_initialize (void) fclose (file); g_string_free (line_buf, TRUE); - g_string_free (tmp_buf, TRUE); g_free (filename); } From 672b195592b5c7ea163a80ae10d4977b83930ce3 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 5 Aug 2026 14:20:10 +0200 Subject: [PATCH 27/56] gtk: reuse cached module path list Avoid computing and allocating the module path list a second time. --- gtk/gtkmodules.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtk/gtkmodules.c b/gtk/gtkmodules.c index da5568ffa..a325b3e56 100644 --- a/gtk/gtkmodules.c +++ b/gtk/gtkmodules.c @@ -192,7 +192,7 @@ _gtk_get_module_path (const gchar *type) result = g_new (gchar *, count * 5 + 1); count = 0; - for (path = get_module_path (); *path; path++) + for (path = paths; *path; path++) { gint use_version, use_host; From 0911aa2f2f67a1dd850bafdfa2d8024561d4fced Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 5 Aug 2026 14:20:10 +0200 Subject: [PATCH 28/56] gtk: remove unused IM module query function pointers Keep symbol validation while avoiding unused typed variables and assignments. --- gtk/queryimmodules.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gtk/queryimmodules.c b/gtk/queryimmodules.c index d20637e10..e118b9c19 100644 --- a/gtk/queryimmodules.c +++ b/gtk/queryimmodules.c @@ -159,9 +159,6 @@ query_module (const char *dir, const char *name, GString *contents) { void (*list) (const GtkIMContextInfo ***contexts, guint *n_contexts); - G_GNUC_UNUSED void (*init) (GTypeModule *type_module); - G_GNUC_UNUSED void (*exit) (void); - G_GNUC_UNUSED GtkIMContext *(*create) (const gchar *context_id); gpointer list_ptr; gpointer init_ptr; @@ -196,9 +193,6 @@ query_module (const char *dir, const char *name, GString *contents) int i; list = list_ptr; - init = init_ptr; - exit = exit_ptr; - create = create_ptr; print_escaped (contents, path); g_string_append_c (contents, '\n'); From d0c434639651b33e5a8e4b004b7871a0ad4ef6e6 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 5 Aug 2026 22:59:33 +0200 Subject: [PATCH 29/56] cups: use modern HTTP connection helpers when available Detect httpConnect2 and httpReconnect2 at configure time. Preserve blocking, encryption, timeout and reconnect semantics while retaining the legacy CUPS APIs as compile-time fallbacks. --- configure.ac | 1 + modules/printbackends/cups/gtkcupsutils.c | 44 ++++++++++++++---- .../printbackends/cups/gtkprintbackendcups.c | 46 +++++++++++++++++-- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index 101007915..c344f7d8a 100644 --- a/configure.ac +++ b/configure.ac @@ -1594,6 +1594,7 @@ else gtk_save_libs="$LIBS" LIBS="$CUPS_LIBS" AC_CHECK_FUNCS(httpGetAuthString) + AC_CHECK_FUNCS(httpConnect2 httpReconnect2) LIBS="$gtk_save_libs" fi fi diff --git a/modules/printbackends/cups/gtkcupsutils.c b/modules/printbackends/cups/gtkcupsutils.c index fdfe21e91..f96b7a5b5 100644 --- a/modules/printbackends/cups/gtkcupsutils.c +++ b/modules/printbackends/cups/gtkcupsutils.c @@ -34,6 +34,11 @@ typedef void (*GtkCupsRequestStateFunc) (GtkCupsRequest *request); +static http_t *gtk_cups_http_connect (const char *host, + int port, + http_encryption_t encryption); +static int gtk_cups_http_reconnect (http_t *http); + static void _connect (GtkCupsRequest *request); static void _post_send (GtkCupsRequest *request); static void _post_write_request (GtkCupsRequest *request); @@ -171,7 +176,7 @@ gtk_cups_request_new_with_username (http_t *connection, else { request->http = NULL; - request->http = httpConnectEncrypt (request->server, + request->http = gtk_cups_http_connect (request->server, ippPort (), cupsEncryption ()); @@ -681,6 +686,29 @@ gtk_cups_request_set_ipp_version (GtkCupsRequest *request, ippSetVersion (request->ipp_request, major, minor); } + +static http_t * +gtk_cups_http_connect (const char *host, + int port, + http_encryption_t encryption) +{ +#ifdef HAVE_HTTPCONNECT2 + return httpConnect2 (host, port, NULL, AF_UNSPEC, encryption, 1, 30000, NULL); +#else + return httpConnectEncrypt (host, port, encryption); +#endif +} + +static int +gtk_cups_http_reconnect (http_t *http) +{ +#ifdef HAVE_HTTPRECONNECT2 + return httpReconnect2 (http, 30000, NULL); +#else + return httpReconnect (http); +#endif +} + static void _connect (GtkCupsRequest *request) { @@ -689,7 +717,7 @@ _connect (GtkCupsRequest *request) if (request->http == NULL) { - request->http = httpConnectEncrypt (request->server, + request->http = gtk_cups_http_connect (request->server, ippPort (), cupsEncryption ()); @@ -744,7 +772,7 @@ _post_send (GtkCupsRequest *request) if (httpPost (request->http, request->resource)) { - if (httpReconnect (request->http)) + if (gtk_cups_http_reconnect (request->http)) { request->state = GTK_CUPS_POST_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; @@ -1046,7 +1074,7 @@ _post_check (GtkCupsRequest *request) } if (auth_result || - httpReconnect (request->http)) + gtk_cups_http_reconnect (request->http)) { /* if the password has been used, reset password_state * so that we ask for a new one next time around @@ -1105,7 +1133,7 @@ _post_check (GtkCupsRequest *request) request->state = GTK_CUPS_POST_CONNECT; /* Reconnect... */ - httpReconnect (request->http); + gtk_cups_http_reconnect (request->http); /* Upgrade with encryption... */ httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED); @@ -1232,7 +1260,7 @@ _get_send (GtkCupsRequest *request) if (httpGet (request->http, request->resource)) { - if (httpReconnect (request->http)) + if (gtk_cups_http_reconnect (request->http)) { request->state = GTK_CUPS_GET_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; @@ -1341,7 +1369,7 @@ _get_check (GtkCupsRequest *request) } if (auth_result || - httpReconnect (request->http)) + gtk_cups_http_reconnect (request->http)) { /* if the password has been used, reset password_state * so that we ask for a new one next time around @@ -1373,7 +1401,7 @@ _get_check (GtkCupsRequest *request) request->state = GTK_CUPS_GET_CONNECT; /* Reconnect... */ - httpReconnect (request->http); + gtk_cups_http_reconnect (request->http); /* Upgrade with encryption... */ httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED); diff --git a/modules/printbackends/cups/gtkprintbackendcups.c b/modules/printbackends/cups/gtkprintbackendcups.c index e17d97d21..d07d1bc7a 100644 --- a/modules/printbackends/cups/gtkprintbackendcups.c +++ b/modules/printbackends/cups/gtkprintbackendcups.c @@ -489,6 +489,44 @@ cups_free_print_stream_data (CupsPrintStreamData *data) g_free (data); } + +static http_t * +gtk_cups_backend_http_connect (const char *host, + int port) +{ +#ifdef HAVE_HTTPCONNECT2 + return httpConnect2 (host, + port, + NULL, + AF_UNSPEC, + HTTP_ENCRYPT_IF_REQUESTED, + 1, + 30000, + NULL); +#else + return httpConnect (host, port); +#endif +} + +static http_t * +gtk_cups_backend_http_connect_encrypt (const char *host, + int port, + http_encryption_t encryption) +{ +#ifdef HAVE_HTTPCONNECT2 + return httpConnect2 (host, + port, + NULL, + AF_UNSPEC, + encryption, + 1, + 30000, + NULL); +#else + return httpConnectEncrypt (host, port, encryption); +#endif +} + static void cups_print_cb (GtkPrintBackendCups *print_backend, GtkCupsResult *result, @@ -636,7 +674,9 @@ gtk_print_backend_cups_print_stream (GtkPrintBackend *print_backend, { http_t *http; - http = httpConnect (cups_printer->hostname, cups_printer->port); + http = gtk_cups_backend_http_connect ( + cups_printer->hostname, + cups_printer->port); if (http) { request = gtk_cups_request_new_with_username (http, @@ -2555,7 +2595,7 @@ cups_request_avahi_printer_info (const gchar *printer_uri, AvahiService *service; http_t *http; - http = httpConnect (host, port); + http = gtk_cups_backend_http_connect (host, port); if (http) { service = (AvahiService *) g_new0 (AvahiService, 1); @@ -3438,7 +3478,7 @@ cups_request_ppd (GtkPrinter *printer) } } - http = httpConnectEncrypt (cups_printer->hostname, + http = gtk_cups_backend_http_connect_encrypt (cups_printer->hostname, cups_printer->port, cupsEncryption ()); From a116a5d13b6f3a838af6d3c37287b649d4d303ef Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Thu, 6 Aug 2026 13:20:18 +0200 Subject: [PATCH 30/56] gail: implement modern AtkValue interface Add compile-time support for the modern AtkValue API available since ATK 2.12 while retaining compatibility with older ATK versions. Implement direct modern value, range, increment and setter methods for adjustments, paned widgets, progress bars, ranges, scale buttons and spin buttons. Keep the implementation private, avoid per-instance memory overhead and preserve the existing exported ABI. --- configure.ac | 5 ++ modules/other/gail/Makefile.am | 1 + modules/other/gail/gailadjustment.c | 82 ++++++++++++---------------- modules/other/gail/gailadjustment.h | 16 ++++++ modules/other/gail/gailpaned.c | 35 ++++++++++++ modules/other/gail/gailprogressbar.c | 18 +++++- modules/other/gail/gailrange.c | 39 +++++++++++-- modules/other/gail/gailscalebutton.c | 27 ++++++++- modules/other/gail/gailspinbutton.c | 31 +++++++++-- modules/other/gail/gailvalue.h | 56 +++++++++++++++++++ 10 files changed, 246 insertions(+), 64 deletions(-) create mode 100644 modules/other/gail/gailvalue.h diff --git a/configure.ac b/configure.ac index c344f7d8a..04536e910 100644 --- a/configure.ac +++ b/configure.ac @@ -399,6 +399,11 @@ PKG_CHECK_MODULES(BASE_DEPENDENCIES, gdk-pixbuf-2.0 >= gdk_pixbuf_required_version dnl fribidi >= fribidi_required_version]) +PKG_CHECK_EXISTS([atk >= 2.12], + [AC_DEFINE([HAVE_ATK_VALUE_2_12], [1], + [Define if ATK provides the 2.12 AtkValue interface])]) + + ## In addition to checking that cairo is present, we also need to ## check that the correct cairo backend is there. E.g. if the GDK ## target is win32 we need the cairo-win32 backend and so on. diff --git a/modules/other/gail/Makefile.am b/modules/other/gail/Makefile.am index 991512dea..1b9a4c6bd 100644 --- a/modules/other/gail/Makefile.am +++ b/modules/other/gail/Makefile.am @@ -72,6 +72,7 @@ libgailincludedir=$(includedir)/gail-1.0/gail gail_private_h_sources = \ gail.h \ + gailvalue.h \ gailadjustment.h \ gailarrow.h \ gailbooleancell.h \ diff --git a/modules/other/gail/gailadjustment.c b/modules/other/gail/gailadjustment.c index f1bb53f2b..021b79357 100644 --- a/modules/other/gail/gailadjustment.c +++ b/modules/other/gail/gailadjustment.c @@ -22,6 +22,7 @@ #include #include #include "gailadjustment.h" +#include "gailvalue.h" static void gail_adjustment_class_init (GailAdjustmentClass *klass); @@ -32,16 +33,25 @@ static void gail_adjustment_real_initialize (AtkObject *obj, static void atk_value_interface_init (AtkValueIface *iface); -static void gail_adjustment_get_current_value (AtkValue *obj, - GValue *value); -static void gail_adjustment_get_maximum_value (AtkValue *obj, - GValue *value); -static void gail_adjustment_get_minimum_value (AtkValue *obj, - GValue *value); -static void gail_adjustment_get_minimum_increment (AtkValue *obj, - GValue *value); -static gboolean gail_adjustment_set_current_value (AtkValue *obj, - const GValue *value); + +#ifdef HAVE_ATK_VALUE_2_12 +GAIL_DEFINE_ADJUSTMENT_VALUE_READERS ( + gail_adjustment, GAIL_ADJUSTMENT (obj)->adjustment, + adjustment->value, adjustment->upper) +GAIL_DEFINE_ADJUSTMENT_VALUE_INCREMENT ( + gail_adjustment, GAIL_ADJUSTMENT (obj)->adjustment, + GAIL_VALUE_MINIMUM_INCREMENT (adjustment)) + +static void +gail_adjustment_set_value (AtkValue *obj, gdouble value) +{ + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (obj)->adjustment; + if (adjustment != NULL) + gtk_adjustment_set_value (adjustment, value); +} +#endif static void gail_adjustment_destroyed (GtkAdjustment *adjustment, GailAdjustment *gail_adjustment); @@ -105,9 +115,15 @@ atk_value_interface_init (AtkValueIface *iface) iface->get_minimum_value = gail_adjustment_get_minimum_value; iface->get_minimum_increment = gail_adjustment_get_minimum_increment; iface->set_current_value = gail_adjustment_set_current_value; +#ifdef HAVE_ATK_VALUE_2_12 + iface->get_value_and_text = gail_adjustment_get_value_and_text; + iface->get_range = gail_adjustment_get_range; + iface->get_increment = gail_adjustment_get_increment; + iface->set_value = gail_adjustment_set_value; +#endif } -static void +void gail_adjustment_get_current_value (AtkValue *obj, GValue *value) { @@ -127,7 +143,7 @@ gail_adjustment_get_current_value (AtkValue *obj, g_value_set_double (value,current_value); } -static void +void gail_adjustment_get_maximum_value (AtkValue *obj, GValue *value) { @@ -147,7 +163,7 @@ gail_adjustment_get_maximum_value (AtkValue *obj, g_value_set_double (value, maximum_value); } -static void +void gail_adjustment_get_minimum_value (AtkValue *obj, GValue *value) { @@ -167,48 +183,20 @@ gail_adjustment_get_minimum_value (AtkValue *obj, g_value_set_double (value, minimum_value); } -static void -gail_adjustment_get_minimum_increment (AtkValue *obj, - GValue *value) +void +gail_adjustment_get_minimum_increment (AtkValue *obj, GValue *value) { - GtkAdjustment* adjustment; - gdouble minimum_increment; + GtkAdjustment *adjustment = GAIL_ADJUSTMENT (obj)->adjustment; - adjustment = GAIL_ADJUSTMENT (obj)->adjustment; if (adjustment == NULL) - { - /* State is defunct */ return; - } - if (adjustment->step_increment != 0 && - adjustment->page_increment != 0) - { - if (ABS (adjustment->step_increment) < ABS (adjustment->page_increment)) - minimum_increment = adjustment->step_increment; - else - minimum_increment = adjustment->page_increment; - } - else if (adjustment->step_increment == 0 && - adjustment->page_increment == 0) - { - minimum_increment = 0; - } - else if (adjustment->step_increment == 0) - { - minimum_increment = adjustment->page_increment; - } - else - { - minimum_increment = adjustment->step_increment; - } - - memset (value, 0, sizeof (GValue)); + memset (value, 0, sizeof (GValue)); g_value_init (value, G_TYPE_DOUBLE); - g_value_set_double (value, minimum_increment); + g_value_set_double (value, GAIL_VALUE_MINIMUM_INCREMENT (adjustment)); } -static gboolean +gboolean gail_adjustment_set_current_value (AtkValue *obj, const GValue *value) { diff --git a/modules/other/gail/gailadjustment.h b/modules/other/gail/gailadjustment.h index 790534dc9..0135b3d91 100644 --- a/modules/other/gail/gailadjustment.h +++ b/modules/other/gail/gailadjustment.h @@ -50,6 +50,22 @@ struct _GailAdjustmentClass AtkObject *gail_adjustment_new (GtkAdjustment *adjustment); +G_GNUC_INTERNAL void +gail_adjustment_get_current_value (AtkValue *obj, + GValue *value); +G_GNUC_INTERNAL void +gail_adjustment_get_maximum_value (AtkValue *obj, + GValue *value); +G_GNUC_INTERNAL void +gail_adjustment_get_minimum_value (AtkValue *obj, + GValue *value); +G_GNUC_INTERNAL void +gail_adjustment_get_minimum_increment (AtkValue *obj, + GValue *value); +G_GNUC_INTERNAL gboolean +gail_adjustment_set_current_value (AtkValue *obj, + const GValue *value); + G_END_DECLS #endif /* __GAIL_ADJUSTMENT_H__ */ diff --git a/modules/other/gail/gailpaned.c b/modules/other/gail/gailpaned.c index 5ac2d2340..42ac248da 100644 --- a/modules/other/gail/gailpaned.c +++ b/modules/other/gail/gailpaned.c @@ -22,6 +22,7 @@ #include #include #include "gailpaned.h" +#include "gailvalue.h" static void gail_paned_class_init (GailPanedClass *klass); @@ -44,6 +45,35 @@ static void gail_paned_get_minimum_value (AtkValue *obj, static gboolean gail_paned_set_current_value (AtkValue *obj, const GValue *value); +#ifdef HAVE_ATK_VALUE_2_12 +static void +gail_paned_get_value_and_text (AtkValue *obj, gdouble *value, gchar **text) +{ + GtkWidget *widget = GTK_ACCESSIBLE (obj)->widget; + + if (text != NULL) + *text = NULL; + if (value != NULL) + *value = widget != NULL + ? gtk_paned_get_position (GTK_PANED (widget)) : 0.0; +} + +static AtkRange * +gail_paned_get_range (AtkValue *obj) +{ + GtkWidget *widget = GTK_ACCESSIBLE (obj)->widget; + GtkPaned *paned; + + if (widget == NULL) + return NULL; + paned = GTK_PANED (widget); + return atk_range_new (paned->min_position, paned->max_position, NULL); +} + +GAIL_DEFINE_WIDGET_VALUE_SETTER ( + gail_paned, GTK_PANED, gtk_paned_set_position, (gint) value) +#endif + G_DEFINE_TYPE_WITH_CODE (GailPaned, gail_paned, GAIL_TYPE_CONTAINER, G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE, atk_value_interface_init)) @@ -112,6 +142,11 @@ atk_value_interface_init (AtkValueIface *iface) iface->get_maximum_value = gail_paned_get_maximum_value; iface->get_minimum_value = gail_paned_get_minimum_value; iface->set_current_value = gail_paned_set_current_value; +#ifdef HAVE_ATK_VALUE_2_12 + iface->get_value_and_text = gail_paned_get_value_and_text; + iface->get_range = gail_paned_get_range; + iface->set_value = gail_paned_set_value; +#endif } static void diff --git a/modules/other/gail/gailprogressbar.c b/modules/other/gail/gailprogressbar.c index d70c139be..6a0df280e 100644 --- a/modules/other/gail/gailprogressbar.c +++ b/modules/other/gail/gailprogressbar.c @@ -26,6 +26,7 @@ #include #include "gailprogressbar.h" +#include "gailvalue.h" #include "gailadjustment.h" static void gail_progress_bar_class_init (GailProgressBarClass *klass); @@ -50,6 +51,13 @@ static void gail_progress_bar_get_minimum_value (AtkValue *obj, static void gail_progress_bar_value_changed (GtkAdjustment *adjustment, gpointer data); +#ifdef HAVE_ATK_VALUE_2_12 +GAIL_DEFINE_ADJUSTMENT_VALUE_READERS ( + gail_progress_bar, + GAIL_VALUE_ADJUSTMENT (GAIL_PROGRESS_BAR (obj)->adjustment), + adjustment->value, adjustment->upper) +#endif + G_DEFINE_TYPE_WITH_CODE (GailProgressBar, gail_progress_bar, GAIL_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE, atk_value_interface_init)) @@ -108,6 +116,10 @@ atk_value_interface_init (AtkValueIface *iface) iface->get_current_value = gail_progress_bar_get_current_value; iface->get_maximum_value = gail_progress_bar_get_maximum_value; iface->get_minimum_value = gail_progress_bar_get_minimum_value; +#ifdef HAVE_ATK_VALUE_2_12 + iface->get_value_and_text = gail_progress_bar_get_value_and_text; + iface->get_range = gail_progress_bar_get_range; +#endif } static void @@ -125,7 +137,7 @@ gail_progress_bar_get_current_value (AtkValue *obj, */ return; - atk_value_get_current_value (ATK_VALUE (progress_bar->adjustment), value); + gail_adjustment_get_current_value (ATK_VALUE (progress_bar->adjustment), value); } static void @@ -143,7 +155,7 @@ gail_progress_bar_get_maximum_value (AtkValue *obj, */ return; - atk_value_get_maximum_value (ATK_VALUE (progress_bar->adjustment), value); + gail_adjustment_get_maximum_value (ATK_VALUE (progress_bar->adjustment), value); } static void @@ -161,7 +173,7 @@ gail_progress_bar_get_minimum_value (AtkValue *obj, */ return; - atk_value_get_minimum_value (ATK_VALUE (progress_bar->adjustment), value); + gail_adjustment_get_minimum_value (ATK_VALUE (progress_bar->adjustment), value); } static void diff --git a/modules/other/gail/gailrange.c b/modules/other/gail/gailrange.c index 9a91bb333..8170d86d0 100644 --- a/modules/other/gail/gailrange.c +++ b/modules/other/gail/gailrange.c @@ -23,6 +23,7 @@ #include #include #include "gailrange.h" +#include "gailvalue.h" #include "gailadjustment.h" #include "gail-private-macros.h" @@ -70,6 +71,28 @@ static gboolean gail_range_set_description (AtkAction *action, gint i, const gchar *desc); +#ifdef HAVE_ATK_VALUE_2_12 +#define GAIL_RANGE_ADJUSTMENT(obj) \ + (GTK_ACCESSIBLE (obj)->widget != NULL \ + ? GAIL_VALUE_ADJUSTMENT (GAIL_RANGE (obj)->adjustment) : NULL) +#define GAIL_RANGE_UPPER(obj, adjustment) \ + (gtk_range_get_restrict_to_fill_level ( \ + GTK_RANGE (GTK_ACCESSIBLE (obj)->widget)) \ + ? MIN ((adjustment)->upper - (adjustment)->page_size, \ + gtk_range_get_fill_level ( \ + GTK_RANGE (GTK_ACCESSIBLE (obj)->widget))) \ + : (adjustment)->upper - (adjustment)->page_size) + +GAIL_DEFINE_ADJUSTMENT_VALUE_READERS ( + gail_range, GAIL_RANGE_ADJUSTMENT (obj), adjustment->value, + GAIL_RANGE_UPPER (obj, adjustment)) +GAIL_DEFINE_ADJUSTMENT_VALUE_INCREMENT ( + gail_range, GAIL_RANGE_ADJUSTMENT (obj), + GAIL_VALUE_MINIMUM_INCREMENT (adjustment)) +GAIL_DEFINE_WIDGET_VALUE_SETTER ( + gail_range, GTK_RANGE, gtk_range_set_value, value) +#endif + G_DEFINE_TYPE_WITH_CODE (GailRange, gail_range, GAIL_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init) G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE, atk_value_interface_init)) @@ -163,6 +186,12 @@ atk_value_interface_init (AtkValueIface *iface) iface->get_minimum_value = gail_range_get_minimum_value; iface->get_minimum_increment = gail_range_get_minimum_increment; iface->set_current_value = gail_range_set_current_value; +#ifdef HAVE_ATK_VALUE_2_12 + iface->get_value_and_text = gail_range_get_value_and_text; + iface->get_range = gail_range_get_range; + iface->get_increment = gail_range_get_increment; + iface->set_value = gail_range_set_value; +#endif } static void @@ -180,7 +209,7 @@ gail_range_get_current_value (AtkValue *obj, */ return; - atk_value_get_current_value (ATK_VALUE (range->adjustment), value); + gail_adjustment_get_current_value (ATK_VALUE (range->adjustment), value); } static void @@ -201,7 +230,7 @@ gail_range_get_maximum_value (AtkValue *obj, */ return; - atk_value_get_maximum_value (ATK_VALUE (range->adjustment), value); + gail_adjustment_get_maximum_value (ATK_VALUE (range->adjustment), value); gtk_range = GTK_RANGE (gtk_accessible_get_widget (GTK_ACCESSIBLE (range))); g_return_if_fail (gtk_range); @@ -231,7 +260,7 @@ gail_range_get_minimum_value (AtkValue *obj, */ return; - atk_value_get_minimum_value (ATK_VALUE (range->adjustment), value); + gail_adjustment_get_minimum_value (ATK_VALUE (range->adjustment), value); } static void @@ -248,7 +277,7 @@ gail_range_get_minimum_increment (AtkValue *obj, GValue *value) */ return; - atk_value_get_minimum_increment (ATK_VALUE (range->adjustment), value); + gail_adjustment_get_minimum_increment (ATK_VALUE (range->adjustment), value); } static gboolean gail_range_set_current_value (AtkValue *obj, @@ -519,5 +548,3 @@ gail_range_set_description (AtkAction *action, else return FALSE; } - - diff --git a/modules/other/gail/gailscalebutton.c b/modules/other/gail/gailscalebutton.c index f04c69046..75915e43d 100644 --- a/modules/other/gail/gailscalebutton.c +++ b/modules/other/gail/gailscalebutton.c @@ -21,6 +21,7 @@ #include #include "gailscalebutton.h" +#include "gailvalue.h" #include "gailadjustment.h" #include "gail-private-macros.h" @@ -65,6 +66,24 @@ static void gail_scale_button_get_minimum_increment (AtkValue *obj, static gboolean gail_scale_button_set_current_value (AtkValue *obj, const GValue *value); +#ifdef HAVE_ATK_VALUE_2_12 +#define GAIL_SCALE_BUTTON_ADJUSTMENT(obj) \ + (GTK_ACCESSIBLE (obj)->widget != NULL \ + ? gtk_scale_button_get_adjustment ( \ + GTK_SCALE_BUTTON (GTK_ACCESSIBLE (obj)->widget)) : NULL) + +GAIL_DEFINE_ADJUSTMENT_VALUE_READERS ( + gail_scale_button, GAIL_SCALE_BUTTON_ADJUSTMENT (obj), + gtk_scale_button_get_value ( + GTK_SCALE_BUTTON (GTK_ACCESSIBLE (obj)->widget)), + adjustment->upper) +GAIL_DEFINE_ADJUSTMENT_VALUE_INCREMENT ( + gail_scale_button, GAIL_SCALE_BUTTON_ADJUSTMENT (obj), + adjustment->step_increment) +GAIL_DEFINE_WIDGET_VALUE_SETTER ( + gail_scale_button, GTK_SCALE_BUTTON, gtk_scale_button_set_value, value) +#endif + G_DEFINE_TYPE_WITH_CODE (GailScaleButton, gail_scale_button, GAIL_TYPE_BUTTON, G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init) G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE, atk_value_interface_init)); @@ -181,6 +200,12 @@ atk_value_interface_init (AtkValueIface *iface) iface->get_minimum_value = gail_scale_button_get_minimum_value; iface->get_minimum_increment = gail_scale_button_get_minimum_increment; iface->set_current_value = gail_scale_button_set_current_value; +#ifdef HAVE_ATK_VALUE_2_12 + iface->get_value_and_text = gail_scale_button_get_value_and_text; + iface->get_range = gail_scale_button_get_range; + iface->get_increment = gail_scale_button_get_increment; + iface->set_value = gail_scale_button_set_value; +#endif } static void @@ -295,5 +320,3 @@ gail_scale_button_notify_gtk (GObject *obj, GAIL_WIDGET_CLASS (gail_scale_button_parent_class)->notify_gtk (obj, pspec); } } - - diff --git a/modules/other/gail/gailspinbutton.c b/modules/other/gail/gailspinbutton.c index 709a413c0..36f2ffa32 100644 --- a/modules/other/gail/gailspinbutton.c +++ b/modules/other/gail/gailspinbutton.c @@ -22,6 +22,7 @@ #include #include #include "gailspinbutton.h" +#include "gailvalue.h" #include "gailadjustment.h" #include "gail-private-macros.h" @@ -49,6 +50,19 @@ static gboolean gail_spin_button_set_current_value (AtkValue *obj, static void gail_spin_button_value_changed (GtkAdjustment *adjustment, gpointer data); +#ifdef HAVE_ATK_VALUE_2_12 +GAIL_DEFINE_ADJUSTMENT_VALUE_READERS ( + gail_spin_button, + GAIL_VALUE_ADJUSTMENT (GAIL_SPIN_BUTTON (obj)->adjustment), + adjustment->value, adjustment->upper) +GAIL_DEFINE_ADJUSTMENT_VALUE_INCREMENT ( + gail_spin_button, + GAIL_VALUE_ADJUSTMENT (GAIL_SPIN_BUTTON (obj)->adjustment), + GAIL_VALUE_MINIMUM_INCREMENT (adjustment)) +GAIL_DEFINE_WIDGET_VALUE_SETTER ( + gail_spin_button, GTK_SPIN_BUTTON, gtk_spin_button_set_value, value) +#endif + G_DEFINE_TYPE_WITH_CODE (GailSpinButton, gail_spin_button, GAIL_TYPE_ENTRY, G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE, atk_value_interface_init)) @@ -110,6 +124,12 @@ atk_value_interface_init (AtkValueIface *iface) iface->get_minimum_value = gail_spin_button_get_minimum_value; iface->get_minimum_increment = gail_spin_button_get_minimum_increment; iface->set_current_value = gail_spin_button_set_current_value; +#ifdef HAVE_ATK_VALUE_2_12 + iface->get_value_and_text = gail_spin_button_get_value_and_text; + iface->get_range = gail_spin_button_get_range; + iface->get_increment = gail_spin_button_get_increment; + iface->set_value = gail_spin_button_set_value; +#endif } static void @@ -127,7 +147,7 @@ gail_spin_button_get_current_value (AtkValue *obj, */ return; - atk_value_get_current_value (ATK_VALUE (spin_button->adjustment), value); + gail_adjustment_get_current_value (ATK_VALUE (spin_button->adjustment), value); } static void @@ -145,7 +165,7 @@ gail_spin_button_get_maximum_value (AtkValue *obj, */ return; - atk_value_get_maximum_value (ATK_VALUE (spin_button->adjustment), value); + gail_adjustment_get_maximum_value (ATK_VALUE (spin_button->adjustment), value); } static void @@ -163,7 +183,7 @@ gail_spin_button_get_minimum_value (AtkValue *obj, */ return; - atk_value_get_minimum_value (ATK_VALUE (spin_button->adjustment), value); + gail_adjustment_get_minimum_value (ATK_VALUE (spin_button->adjustment), value); } static void @@ -180,7 +200,7 @@ gail_spin_button_get_minimum_increment (AtkValue *obj, GValue *value) */ return; - atk_value_get_minimum_increment (ATK_VALUE (spin_button->adjustment), value); + gail_adjustment_get_minimum_increment (ATK_VALUE (spin_button->adjustment), value); } static gboolean @@ -198,7 +218,7 @@ gail_spin_button_set_current_value (AtkValue *obj, */ return FALSE; - return atk_value_set_current_value (ATK_VALUE (spin_button->adjustment), value); + return gail_adjustment_set_current_value (ATK_VALUE (spin_button->adjustment), value); } static void @@ -264,4 +284,3 @@ gail_spin_button_value_changed (GtkAdjustment *adjustment, g_object_notify (G_OBJECT (spin_button), "accessible-value"); } - diff --git a/modules/other/gail/gailvalue.h b/modules/other/gail/gailvalue.h new file mode 100644 index 000000000..498453424 --- /dev/null +++ b/modules/other/gail/gailvalue.h @@ -0,0 +1,56 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef __GAIL_VALUE_H__ +#define __GAIL_VALUE_H__ + +#include + +#define GAIL_VALUE_ADJUSTMENT(accessible) \ + ((accessible) != NULL ? GAIL_ADJUSTMENT (accessible)->adjustment : NULL) +#define GAIL_VALUE_MINIMUM_INCREMENT(a) \ + ((a)->step_increment == 0.0 ? (a)->page_increment : \ + (a)->page_increment == 0.0 ? (a)->step_increment : \ + ABS ((a)->step_increment) < ABS ((a)->page_increment) \ + ? (a)->step_increment : (a)->page_increment) + +#ifdef HAVE_ATK_VALUE_2_12 +#define GAIL_DEFINE_ADJUSTMENT_VALUE_READERS(prefix, a_expr, value_expr, max_expr) \ +static void prefix##_get_value_and_text (AtkValue *obj, gdouble *value, gchar **text) \ +{ \ + GtkAdjustment *adjustment = (a_expr); \ + if (text != NULL) *text = NULL; \ + if (value != NULL) *value = adjustment != NULL ? (value_expr) : 0.0; \ +} \ +static AtkRange *prefix##_get_range (AtkValue *obj) \ +{ \ + GtkAdjustment *adjustment = (a_expr); \ + return adjustment != NULL \ + ? atk_range_new (adjustment->lower, (max_expr), NULL) : NULL; \ +} + +#define GAIL_DEFINE_ADJUSTMENT_VALUE_INCREMENT(prefix, a_expr, increment_expr) \ +static gdouble prefix##_get_increment (AtkValue *obj) \ +{ \ + GtkAdjustment *adjustment = (a_expr); \ + return adjustment != NULL ? (increment_expr) : 0.0; \ +} + +#define GAIL_DEFINE_WIDGET_VALUE_SETTER(prefix, cast, setter, value_expr) \ +static void prefix##_set_value (AtkValue *obj, gdouble value) \ +{ \ + GtkWidget *widget = GTK_ACCESSIBLE (obj)->widget; \ + if (widget != NULL) setter (cast (widget), (value_expr)); \ +} +#endif + +#endif /* __GAIL_VALUE_H__ */ From 782a2948b8e55a1905e4e006a01c2c28a3278e6c Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Thu, 6 Aug 2026 13:57:34 +0200 Subject: [PATCH 31/56] gail: update AtkValue test modules Use a shared private compatibility helper in the GAIL test modules. Exercise the modern AtkValue API when ATK 2.12 or newer is available while retaining the legacy test path for older ATK versions. Initialize temporary GValues explicitly and keep the helper symbols private to the test modules. --- modules/other/gail/tests/Makefile.am | 2 + modules/other/gail/tests/ferret.c | 10 +-- modules/other/gail/tests/testpaned.c | 11 +-- modules/other/gail/tests/testvalue.h | 98 +++++++++++++++++++++++++++ modules/other/gail/tests/testvalues.c | 18 ++--- 5 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 modules/other/gail/tests/testvalue.h diff --git a/modules/other/gail/tests/Makefile.am b/modules/other/gail/tests/Makefile.am index 993249558..0f0e02317 100644 --- a/modules/other/gail/tests/Makefile.am +++ b/modules/other/gail/tests/Makefile.am @@ -12,6 +12,8 @@ module_LTLIBRARIES = \ libferret.la endif +noinst_HEADERS = testvalue.h + noinst_LTLIBRARIES = \ libtestaction.la \ libtestbutton.la \ diff --git a/modules/other/gail/tests/ferret.c b/modules/other/gail/tests/ferret.c index 711852af0..39eaef441 100644 --- a/modules/other/gail/tests/ferret.c +++ b/modules/other/gail/tests/ferret.c @@ -16,6 +16,7 @@ #include #include "testlib.h" +#include "testvalue.h" typedef enum { @@ -1389,7 +1390,8 @@ _print_text_attributes (AtkText *aobject) static gint _print_value (AtkValue *aobject) { - GValue *value_back, val; + GValue val = { 0, }; + GValue *value_back; gint group_num; TabNumber tab_n = VALUE; @@ -1398,11 +1400,11 @@ _print_value (AtkValue *aobject) group_num = _print_groupname(tab_n, VALUE_INTERFACE, "Value Interface"); - atk_value_get_current_value(aobject, value_back); + gail_test_value_get_current_value(aobject, value_back); _print_value_type(group_num, "Value", value_back); - atk_value_get_minimum_value(aobject, value_back); + gail_test_value_get_minimum_value(aobject, value_back); _print_value_type(group_num, "Minimum Value", value_back); - atk_value_get_maximum_value(aobject, value_back); + gail_test_value_get_maximum_value(aobject, value_back); _print_value_type(group_num, "Maximum Value", value_back); return(group_num); } diff --git a/modules/other/gail/tests/testpaned.c b/modules/other/gail/tests/testpaned.c index 300031955..7e1f27f1c 100644 --- a/modules/other/gail/tests/testpaned.c +++ b/modules/other/gail/tests/testpaned.c @@ -2,6 +2,7 @@ #include #include #include +#include "testvalue.h" static gint _test_paned (gpointer data); static void _check_paned (AtkObject *obj); @@ -31,17 +32,17 @@ static void _property_change_handler (AtkObject *obj, value = &val; memset (value, 0, sizeof (GValue)); - atk_value_get_current_value (ATK_VALUE (obj), value); + gail_test_value_get_current_value (ATK_VALUE (obj), value); g_return_if_fail (G_VALUE_HOLDS_INT (value)); position = g_value_get_int (value); g_print ("Position is %d previous position was %d\n", position, last_position); last_position = position; - atk_value_get_minimum_value (ATK_VALUE (obj), value); + gail_test_value_get_minimum_value (ATK_VALUE (obj), value); g_return_if_fail (G_VALUE_HOLDS_INT (value)); position = g_value_get_int (value); g_print ("Minimum Value is %d\n", position); - atk_value_get_maximum_value (ATK_VALUE (obj), value); + gail_test_value_get_maximum_value (ATK_VALUE (obj), value); g_return_if_fail (G_VALUE_HOLDS_INT (value)); position = g_value_get_int (value); g_print ("Maximum Value is %d\n", position); @@ -61,14 +62,14 @@ static gint _test_paned (gpointer data) value = &val; memset (value, 0, sizeof (GValue)); - atk_value_get_current_value (ATK_VALUE (obj), value); + gail_test_value_get_current_value (ATK_VALUE (obj), value); g_return_val_if_fail (G_VALUE_HOLDS_INT (value), FALSE); position = g_value_get_int (value); g_print ("Position is : %d\n", position); last_position = position; position *= 2; g_value_set_int (value, position); - atk_value_set_current_value (ATK_VALUE (obj), value); + gail_test_value_set_current_value (ATK_VALUE (obj), value); times++; } if (times < 4) diff --git a/modules/other/gail/tests/testvalue.h b/modules/other/gail/tests/testvalue.h new file mode 100644 index 000000000..735278318 --- /dev/null +++ b/modules/other/gail/tests/testvalue.h @@ -0,0 +1,98 @@ +#ifndef __GAIL_TEST_VALUE_H__ +#define __GAIL_TEST_VALUE_H__ + +#include "config.h" + +#include + +#ifdef HAVE_ATK_VALUE_2_12 + +static void +gail_test_value_store_number (AtkValue *value, + GValue *result, + gdouble number) +{ + if (G_VALUE_TYPE (result) == 0) + g_value_init (result, + atk_object_get_role (ATK_OBJECT (value)) == + ATK_ROLE_SPLIT_PANE ? G_TYPE_INT : G_TYPE_DOUBLE); + + if (G_VALUE_HOLDS_INT (result)) + g_value_set_int (result, (gint) number); + else + { + g_return_if_fail (G_VALUE_HOLDS_DOUBLE (result)); + g_value_set_double (result, number); + } +} + +static void +gail_test_value_get_current_value (AtkValue *value, + GValue *result) +{ + gdouble number; + + atk_value_get_value_and_text (value, &number, NULL); + gail_test_value_store_number (value, result, number); +} + +static void +gail_test_value_get_limit (AtkValue *value, + GValue *result, + gboolean upper) +{ + AtkRange *range; + gdouble number; + + range = atk_value_get_range (value); + g_return_if_fail (range != NULL); + + if (upper) + number = atk_range_get_upper_limit (range); + else + number = atk_range_get_lower_limit (range); + + atk_range_free (range); + gail_test_value_store_number (value, result, number); +} + +#define gail_test_value_get_minimum_value(value, result) \ + gail_test_value_get_limit ((value), (result), FALSE) + +#define gail_test_value_get_maximum_value(value, result) \ + gail_test_value_get_limit ((value), (result), TRUE) + +#define GAIL_TEST_VALUE_NUMBER(value) \ + (G_VALUE_HOLDS_INT (value) \ + ? (gdouble) g_value_get_int (value) \ + : g_value_get_double (value)) + +#define gail_test_value_set_current_value(value, new_value) \ + (atk_value_set_value ((value), \ + GAIL_TEST_VALUE_NUMBER (new_value)), \ + TRUE) + +#else + +#define GAIL_TEST_VALUE_IFACE(value) \ + ATK_VALUE_GET_IFACE (value) + +#define gail_test_value_get_current_value(value, result) \ + (GAIL_TEST_VALUE_IFACE (value)->get_current_value ( \ + (value), (result))) + +#define gail_test_value_get_minimum_value(value, result) \ + (GAIL_TEST_VALUE_IFACE (value)->get_minimum_value ( \ + (value), (result))) + +#define gail_test_value_get_maximum_value(value, result) \ + (GAIL_TEST_VALUE_IFACE (value)->get_maximum_value ( \ + (value), (result))) + +#define gail_test_value_set_current_value(value, new_value) \ + (GAIL_TEST_VALUE_IFACE (value)->set_current_value ( \ + (value), (new_value))) + +#endif + +#endif /* __GAIL_TEST_VALUE_H__ */ diff --git a/modules/other/gail/tests/testvalues.c b/modules/other/gail/tests/testvalues.c index 34d91a25a..37155855c 100644 --- a/modules/other/gail/tests/testvalues.c +++ b/modules/other/gail/tests/testvalues.c @@ -1,6 +1,7 @@ #include #include #include +#include "testvalue.h" static void _traverse_children (AtkObject *obj); static void _add_handler (AtkObject *obj); @@ -14,8 +15,8 @@ static void _value_change_handler (AtkObject *obj, AtkPropertyValues *values) { const gchar *type_name = g_type_name (G_TYPE_FROM_INSTANCE (obj)); - GValue *value_back, val; - + GValue val = { 0, }; + GValue *value_back; value_back = &val; if (!ATK_IS_VALUE (obj)) { @@ -33,17 +34,17 @@ static void _value_change_handler (AtkObject *obj, g_print("Now calling the AtkValue interface functions\n"); - atk_value_get_current_value (ATK_VALUE(obj), value_back); + gail_test_value_get_current_value (ATK_VALUE(obj), value_back); g_return_if_fail (G_VALUE_HOLDS_DOUBLE (value_back)); g_print ("atk_value_get_current_value returns %f\n", g_value_get_double (value_back) ); - atk_value_get_maximum_value (ATK_VALUE (obj), value_back); + gail_test_value_get_maximum_value (ATK_VALUE (obj), value_back); g_return_if_fail (G_VALUE_HOLDS_DOUBLE (value_back)); g_print ("atk_value_get_maximum returns %f\n", g_value_get_double (value_back)); - atk_value_get_minimum_value (ATK_VALUE (obj), value_back); + gail_test_value_get_minimum_value (ATK_VALUE (obj), value_back); g_return_if_fail (G_VALUE_HOLDS_DOUBLE (value_back)); g_print ("atk_value_get_minimum returns %f\n", g_value_get_double (value_back)); @@ -103,7 +104,8 @@ static void _add_handler (AtkObject *obj) static void _set_values (AtkObject *obj) { - GValue *value_back, val; + GValue val = { 0, }; + GValue *value_back; static gint count = 0; gdouble double_value; @@ -125,7 +127,7 @@ static void _set_values (AtkObject *obj) { text = "5.7"; atk_editable_text_set_text_contents(ATK_EDITABLE_TEXT(obj),text); g_print("Set text to %s\n",text); - atk_value_get_current_value(ATK_VALUE(obj), value_back); + gail_test_value_get_current_value(ATK_VALUE(obj), value_back); g_return_if_fail (G_VALUE_HOLDS_DOUBLE (value_back)); g_print("atk_value_get_current_value returns %f\n", g_value_get_double( value_back)); @@ -134,7 +136,7 @@ static void _set_values (AtkObject *obj) { memset (value_back, 0, sizeof (GValue)); g_value_init (value_back, G_TYPE_DOUBLE); g_value_set_double (value_back, 10.0); - if (atk_value_set_current_value (ATK_VALUE (obj), value_back)) + if (gail_test_value_set_current_value (ATK_VALUE (obj), value_back)) { double_value = g_value_get_double (value_back); g_print("atk_value_set_current_value returns %f\n", From ea73f6812ce9859313d6677b4265f3b3d7ffa12a Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Fri, 7 Aug 2026 07:38:23 +0200 Subject: [PATCH 32/56] gtk: replace deprecated tooltip timestamp ABI field --- gtk/gtktooltips.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gtk/gtktooltips.h b/gtk/gtktooltips.h index eb1e0a0be..fbafd9939 100644 --- a/gtk/gtktooltips.h +++ b/gtk/gtktooltips.h @@ -74,7 +74,11 @@ struct _GtkTooltips guint have_grab : 1; guint use_sticky_delay : 1; gint timer_tag; - GTimeVal last_popdown; + struct + { + glong tv_sec; + glong tv_usec; + } last_popdown; }; struct _GtkTooltipsClass From f14303e16fcfa08c8651e02669864361d82281c2 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 13 May 2026 10:19:49 +0200 Subject: [PATCH 33/56] introspection: pass compiler and GTK DLL path to scanner --- gtk/Makefile.am | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gtk/Makefile.am b/gtk/Makefile.am index c3c9a1493..31cc16730 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -2,6 +2,9 @@ include $(top_srcdir)/Makefile.decl -include $(INTROSPECTION_MAKEFILE) INTROSPECTION_GIRS = +if OS_WIN32 +INTROSPECTION_SCANNER_ENV = PATH="$(builddir):$(builddir)/.libs:$${PATH}" +endif INTROSPECTION_SCANNER_ARGS = \ --warn-all \ --add-include-path=../gdk From 0f4142b31a294258c52c6973a8846323b969c040 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 10 Aug 2026 12:05:31 +0200 Subject: [PATCH 34/56] introspection: add GdkWin32 GIR generation --- gdk/Makefile.am | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/gdk/Makefile.am b/gdk/Makefile.am index d83d40db8..2b68f98a6 100644 --- a/gdk/Makefile.am +++ b/gdk/Makefile.am @@ -214,6 +214,11 @@ Gdk_2_0_gir_CFLAGS = \ -I$(top_builddir) \ -I$(top_builddir)/gdk \ $(INCLUDES) + +if OS_WIN32 +Gdk_2_0_gir_CFLAGS += -I$(srcdir)/win32 -I$(srcdir) +endif + INTROSPECTION_GIRS += Gdk-2.0.gir if USE_X11 @@ -269,6 +274,69 @@ introspection_files += $(filter-out x11/gdkx.h, $(x11_introspection_files)) endif # USE_X11 +if OS_WIN32 +win32_introspection_files = \ + win32/gdkapplaunchcontext-win32.c \ + win32/gdkcolor-win32.c \ + win32/gdkcursor-win32.c \ + win32/gdkdisplay-win32.c \ + win32/gdkdnd-win32.c \ + win32/gdkdrawable-win32.c \ + win32/gdkdrawable-win32.h \ + win32/gdkevents-win32.c \ + win32/gdkfont-win32.c \ + win32/gdkgc-win32.c \ + win32/gdkgeometry-win32.c \ + win32/gdkglobals-win32.c \ + win32/gdkimage-win32.c \ + win32/gdkim-win32.c \ + win32/gdkinput.c \ + win32/gdkinput-win32.c \ + win32/gdkinput-win32.h \ + win32/gdkkeys-win32.c \ + win32/gdkmain-win32.c \ + win32/gdkpixmap-win32.c \ + win32/gdkpixmap-win32.h \ + win32/gdkprivate-win32.h \ + win32/gdkproperty-win32.c \ + win32/gdkscreen-win32.c \ + win32/gdkselection-win32.c \ + win32/gdkspawn-win32.c \ + win32/gdktestutils-win32.c \ + win32/gdkvisual-win32.c \ + win32/gdkwin32.h \ + win32/gdkwin32id.c \ + win32/gdkwin32keys.h \ + win32/gdkwindow-win32.c \ + win32/gdkwindow-win32.h + +GdkWin32-2.0.gir: $(gdktargetlib) Gdk-2.0.gir Makefile +GdkWin32_2_0_gir_SCANNERFLAGS = --identifier-prefix=Gdk --symbol-prefix=gdk --include-uninstalled=Gdk-2.0.gir +GdkWin32_2_0_gir_INCLUDES = Gio-2.0 GdkPixbuf-2.0 Pango-1.0 +GdkWin32_2_0_gir_LIBS = $(gdktargetlib) +GdkWin32_2_0_gir_FILES = $(filter-out \ + win32/gdkdrawable-win32.h \ + win32/gdkinput-win32.h \ + win32/gdkpixmap-win32.h \ + win32/gdkprivate-win32.h \ + win32/gdkwindow-win32.h, \ + $(win32_introspection_files)) +GdkWin32_2_0_gir_EXPORT_PACKAGES = gdk-win32-2.0 +GdkWin32_2_0_gir_CFLAGS = \ + -DGDK_COMPILATION \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -I$(top_builddir)/gdk \ + -I$(srcdir)/win32 \ + -I$(srcdir) \ + $(INCLUDES) \ + -L$(top_builddir)/gdk +INTROSPECTION_GIRS += GdkWin32-2.0.gir + +introspection_files += $(filter-out win32/gdkwin32.h, $(win32_introspection_files)) + +endif # OS_WIN32 + girdir = $(datadir)/gir-1.0 gir_DATA = $(INTROSPECTION_GIRS) From 212df8a74e7457c80732d6bdd0795202256c4645 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 10 Aug 2026 14:08:34 +0200 Subject: [PATCH 35/56] fontsel: localize common font style names --- gtk/gtkfontbutton.c | 3 +- gtk/gtkfontsel.c | 87 ++++++++++++++++++++++++++++++++++++++++++++- gtk/gtkprivate.h | 7 ++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/gtk/gtkfontbutton.c b/gtk/gtkfontbutton.c index 08aea103e..68df02b8d 100644 --- a/gtk/gtkfontbutton.c +++ b/gtk/gtkfontbutton.c @@ -889,7 +889,8 @@ gtk_font_button_update_font_info (GtkFontButton *font_button) if (style == NULL || !g_ascii_strcasecmp (style, "Regular")) family_style = g_strdup (family); else - family_style = g_strdup_printf ("%s %s", family, style); + family_style = g_strdup_printf ("%s %s", family, + _gtk_font_style_name_translate (style)); gtk_label_set_text (GTK_LABEL (font_button->priv->font_label), family_style); diff --git a/gtk/gtkfontsel.c b/gtk/gtkfontsel.c index 701d22841..28ede4eb4 100644 --- a/gtk/gtkfontsel.c +++ b/gtk/gtkfontsel.c @@ -76,6 +76,91 @@ #define DEFAULT_FONT_NAME "Sans 10" +#ifdef ENABLE_NLS +/* Pango uses these standard English names for common font faces. + * Keep the msgids explicit for gettext, but only translate exact matches: + * font-specific face names must remain untouched. + */ +#define FONT_STYLE_ULTRA_LIGHT N_("Ultra-Light") +#define FONT_STYLE_LIGHT N_("Light") +#define FONT_STYLE_MEDIUM N_("Medium") +#define FONT_STYLE_NORMAL N_("Normal") +#define FONT_STYLE_REGULAR N_("Regular") +#define FONT_STYLE_ITALIC N_("Italic") +#define FONT_STYLE_OBLIQUE N_("Oblique") +#define FONT_STYLE_SEMI_BOLD N_("Semi-Bold") +#define FONT_STYLE_BOLD N_("Bold") +#define FONT_STYLE_ULTRA_BOLD N_("Ultra-Bold") +#define FONT_STYLE_HEAVY N_("Heavy") +#define FONT_STYLE_BOLD_ITALIC N_("Bold Italic") + +const gchar * +_gtk_font_style_name_translate (const gchar *name) +{ + switch (name[0]) + { + case 'B': + if (strcmp (name, FONT_STYLE_BOLD) == 0) + return _(FONT_STYLE_BOLD); + if (strcmp (name, FONT_STYLE_BOLD_ITALIC) == 0) + return _(FONT_STYLE_BOLD_ITALIC); + break; + + case 'H': + if (strcmp (name, FONT_STYLE_HEAVY) == 0) + return _(FONT_STYLE_HEAVY); + break; + + case 'I': + if (strcmp (name, FONT_STYLE_ITALIC) == 0) + return _(FONT_STYLE_ITALIC); + break; + + case 'L': + if (strcmp (name, FONT_STYLE_LIGHT) == 0) + return _(FONT_STYLE_LIGHT); + break; + + case 'M': + if (strcmp (name, FONT_STYLE_MEDIUM) == 0) + return _(FONT_STYLE_MEDIUM); + break; + + case 'N': + if (strcmp (name, FONT_STYLE_NORMAL) == 0) + return _(FONT_STYLE_NORMAL); + break; + + case 'O': + if (strcmp (name, FONT_STYLE_OBLIQUE) == 0) + return _(FONT_STYLE_OBLIQUE); + break; + + case 'R': + if (strcmp (name, FONT_STYLE_REGULAR) == 0) + return _(FONT_STYLE_REGULAR); + break; + + case 'S': + if (strcmp (name, FONT_STYLE_SEMI_BOLD) == 0) + return _(FONT_STYLE_SEMI_BOLD); + break; + + case 'U': + if (strcmp (name, FONT_STYLE_ULTRA_LIGHT) == 0) + return _(FONT_STYLE_ULTRA_LIGHT); + if (strcmp (name, FONT_STYLE_ULTRA_BOLD) == 0) + return _(FONT_STYLE_ULTRA_BOLD); + break; + + default: + break; + } + + return name; +} +#endif /* ENABLE_NLS */ + /* This is the initial and maximum height of the preview entry (it expands when large font sizes are selected). Initial height is also the minimum. */ #define INITIAL_PREVIEW_HEIGHT 44 @@ -869,7 +954,7 @@ gtk_font_selection_show_available_styles (GtkFontSelection *fontsel) gtk_list_store_append (model, &iter); gtk_list_store_set (model, &iter, FACE_COLUMN, faces[i], - FACE_NAME_COLUMN, str, + FACE_NAME_COLUMN, _gtk_font_style_name_translate (str), -1); if (i == 0) diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h index b2c2df999..cc1b50dfa 100644 --- a/gtk/gtkprivate.h +++ b/gtk/gtkprivate.h @@ -134,6 +134,13 @@ const gchar *_gtk_get_data_prefix (); #endif /* G_OS_WIN32 */ +#ifdef ENABLE_NLS +G_GNUC_INTERNAL +const gchar *_gtk_font_style_name_translate (const gchar *name); +#else +#define _gtk_font_style_name_translate(name) (name) +#endif + gboolean _gtk_fnmatch (const char *pattern, const char *string, gboolean no_leading_period); From efcc412ca2d0c59ffa3de96f3eb8bf833487c68e Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 11 Aug 2026 09:53:27 +0200 Subject: [PATCH 36/56] filechooser: reduce view mode startup overhead Replace the two-item GtkComboBoxText with a lightweight toggle and create icon-view zoom controls only when icon view is first used. Keep the existing icon-view zoom, cache invalidation and settings behavior while avoiding their startup cost for the default list view. Keep the zoom event boxes local instead of storing unused widget pointers. --- gtk/gtkfilechooserdefault.c | 116 ++++++++++++++++++++---------------- gtk/gtkfilechooserprivate.h | 4 +- 2 files changed, 64 insertions(+), 56 deletions(-) diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 2ce2d6c8f..fabf5913e 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -410,7 +410,8 @@ static void load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_s static void browse_files_center_selected_row (GtkFileChooserDefault *impl); static void view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode); -static void view_mode_combo_box_changed_cb (GtkComboBox *combo, +static void icon_view_scale_create (GtkFileChooserDefault *impl); +static void view_mode_toggle_button_toggled_cb (GtkToggleButton *toggle, GtkFileChooserDefault *impl); static void icon_view_scale_value_changed_cb (GtkRange *range, @@ -5208,12 +5209,22 @@ view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode) return; impl->view_mode = view_mode; - gtk_combo_box_set_active (GTK_COMBO_BOX (impl->view_mode_combo_box), - view_mode); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (impl->view_mode_toggle_button), + view_mode == VIEW_MODE_ICON); /* Creating the target view */ if (view_mode == VIEW_MODE_ICON) { + if (!impl->icon_view_scale_hbox) + { + icon_view_scale_create (impl); + gtk_box_pack_end ( + GTK_BOX (impl->browse_path_bar_hbox), + impl->icon_view_scale_hbox, + FALSE, FALSE, 0); + } + create_browse_files_icon_view (impl); impl->browse_files_current_view = impl->browse_files_icon_view; old_view = impl->browse_files_tree_view; @@ -5246,7 +5257,8 @@ view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode) impl->browse_files_icon_view = NULL; impl->list_icon_renderer = NULL; impl->list_name_renderer = NULL; - gtk_widget_hide (impl->icon_view_scale_hbox); + if (impl->icon_view_scale_hbox) + gtk_widget_hide (impl->icon_view_scale_hbox); } else g_assert_not_reached (); @@ -5264,12 +5276,14 @@ view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode) browse_files_center_selected_row (impl); } -/* Callback used when view mode combo box active item is changed */ +/* Callback used when the view mode toggle button is toggled. */ static void -view_mode_combo_box_changed_cb (GtkComboBox *combo, +view_mode_toggle_button_toggled_cb (GtkToggleButton *toggle, GtkFileChooserDefault *impl) { - ViewMode target = gtk_combo_box_get_active (combo); + ViewMode target = gtk_toggle_button_get_active (toggle) + ? VIEW_MODE_ICON + : VIEW_MODE_LIST; view_mode_set (impl, target); } @@ -5698,20 +5712,15 @@ location_button_toggled_cb (GtkToggleButton *toggle, location_mode_set (impl, new_mode, FALSE); } -/* Creates a combo box with two items: List View and Icon View. */ +/* Creates the view mode toggle. */ static void -view_mode_combo_box_create (GtkFileChooserDefault *impl) +view_mode_toggle_button_create (GtkFileChooserDefault *impl) { - impl->view_mode_combo_box = gtk_combo_box_text_new (); - gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT(impl->view_mode_combo_box), - _("List View")); /* VIEW_MODE_LIST */ - gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT(impl->view_mode_combo_box), - _("Icon View")); /* VIEW_MODE_ICON */ - gtk_combo_box_set_active (GTK_COMBO_BOX(impl->view_mode_combo_box), - VIEW_MODE_LIST); + impl->view_mode_toggle_button = + gtk_toggle_button_new_with_label (_("Icon View")); - g_signal_connect (impl->view_mode_combo_box, "changed", - G_CALLBACK (view_mode_combo_box_changed_cb), impl); + g_signal_connect (impl->view_mode_toggle_button, "toggled", + G_CALLBACK (view_mode_toggle_button_toggled_cb), impl); } /* Creates a hscale for the icon view. */ @@ -5719,46 +5728,48 @@ static void icon_view_scale_create (GtkFileChooserDefault *impl) { GtkObject *adj; + GtkWidget *zoom_icon; GtkWidget *image; impl->icon_view_scale_hbox = gtk_hbox_new (FALSE, 12); - impl->icon_view_scale_zoom_out_icon = gtk_event_box_new (); - gtk_event_box_set_visible_window (GTK_EVENT_BOX (impl->icon_view_scale_zoom_out_icon), FALSE); + zoom_icon = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (zoom_icon), FALSE); image = gtk_image_new_from_stock (GTK_STOCK_ZOOM_OUT, GTK_ICON_SIZE_BUTTON); - gtk_container_add (GTK_CONTAINER (impl->icon_view_scale_zoom_out_icon), image); + gtk_container_add (GTK_CONTAINER (zoom_icon), image); gtk_widget_show (image); - g_object_set_data (G_OBJECT (impl->icon_view_scale_zoom_out_icon), + g_object_set_data (G_OBJECT (zoom_icon), "gtk-file-chooser-zoom-direction", GINT_TO_POINTER (-1)); - gtk_widget_set_tooltip_text (impl->icon_view_scale_zoom_out_icon, _("Zoom out")); - g_signal_connect (impl->icon_view_scale_zoom_out_icon, "button-press-event", + gtk_widget_set_tooltip_text (zoom_icon, _("Zoom out")); + g_signal_connect (zoom_icon, "button-press-event", G_CALLBACK (icon_view_zoom_button_press_cb), impl); - gtk_size_group_add_widget (impl->browse_path_bar_size_group, impl->icon_view_scale_zoom_out_icon); - gtk_box_pack_start (GTK_BOX (impl->icon_view_scale_hbox), impl->icon_view_scale_zoom_out_icon, FALSE, FALSE, 0); - gtk_widget_show (impl->icon_view_scale_zoom_out_icon); + gtk_size_group_add_widget (impl->browse_path_bar_size_group, zoom_icon); + gtk_box_pack_start (GTK_BOX (impl->icon_view_scale_hbox), zoom_icon, FALSE, FALSE, 0); + gtk_widget_show (zoom_icon); - adj = gtk_adjustment_new (32, 32, 256, 16, 16, 0); + adj = gtk_adjustment_new (impl->icon_view_icon_size, + 32, 256, 16, 16, 0); impl->icon_view_scale = gtk_hscale_new (GTK_ADJUSTMENT (adj)); gtk_scale_set_draw_value (GTK_SCALE (impl->icon_view_scale), FALSE); gtk_widget_set_size_request (impl->icon_view_scale, 100, -1); gtk_box_pack_start (GTK_BOX (impl->icon_view_scale_hbox), impl->icon_view_scale, FALSE, FALSE, 0); gtk_widget_show (impl->icon_view_scale); - impl->icon_view_scale_zoom_in_icon = gtk_event_box_new (); - gtk_event_box_set_visible_window (GTK_EVENT_BOX (impl->icon_view_scale_zoom_in_icon), FALSE); + zoom_icon = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (zoom_icon), FALSE); image = gtk_image_new_from_stock (GTK_STOCK_ZOOM_IN, GTK_ICON_SIZE_BUTTON); - gtk_container_add (GTK_CONTAINER (impl->icon_view_scale_zoom_in_icon), image); + gtk_container_add (GTK_CONTAINER (zoom_icon), image); gtk_widget_show (image); - g_object_set_data (G_OBJECT (impl->icon_view_scale_zoom_in_icon), + g_object_set_data (G_OBJECT (zoom_icon), "gtk-file-chooser-zoom-direction", GINT_TO_POINTER (1)); - gtk_widget_set_tooltip_text (impl->icon_view_scale_zoom_in_icon, _("Zoom in")); - g_signal_connect (impl->icon_view_scale_zoom_in_icon, "button-press-event", + gtk_widget_set_tooltip_text (zoom_icon, _("Zoom in")); + g_signal_connect (zoom_icon, "button-press-event", G_CALLBACK (icon_view_zoom_button_press_cb), impl); - gtk_size_group_add_widget (impl->browse_path_bar_size_group, impl->icon_view_scale_zoom_in_icon); - gtk_box_pack_start (GTK_BOX (impl->icon_view_scale_hbox), impl->icon_view_scale_zoom_in_icon, FALSE, FALSE, 0); - gtk_widget_show (impl->icon_view_scale_zoom_in_icon); + gtk_size_group_add_widget (impl->browse_path_bar_size_group, zoom_icon); + gtk_box_pack_start (GTK_BOX (impl->icon_view_scale_hbox), zoom_icon, FALSE, FALSE, 0); + gtk_widget_show (zoom_icon); g_signal_connect (impl->icon_view_scale, "value-changed", G_CALLBACK (icon_view_scale_value_changed_cb), impl); @@ -5883,9 +5894,9 @@ path_bar_widgets_create (GtkFileChooserDefault *impl) impl->browse_path_bar_size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); gtk_size_group_set_ignore_hidden (impl->browse_path_bar_size_group, FALSE); - /* View mode combo box */ - view_mode_combo_box_create (impl); - gtk_box_pack_start (GTK_BOX (impl->browse_path_bar_hbox), impl->view_mode_combo_box, FALSE, FALSE, 0); + /* View mode selector */ + view_mode_toggle_button_create (impl); + gtk_box_pack_start (GTK_BOX (impl->browse_path_bar_hbox), impl->view_mode_toggle_button, FALSE, FALSE, 0); /* Location button */ location_button_create (impl); @@ -5908,10 +5919,6 @@ path_bar_widgets_create (GtkFileChooserDefault *impl) /* Widgets for special modes (recently-used in Open mode, Search mode) */ special_mode_widgets_create (impl); - /* Icon view scale */ - icon_view_scale_create (impl); - gtk_box_pack_end (GTK_BOX (impl->browse_path_bar_hbox), impl->icon_view_scale_hbox, FALSE, FALSE, 0); - /* Create Folder */ impl->browse_new_folder_button = gtk_button_new_with_mnemonic (_("Create Fo_lder")); g_signal_connect (impl->browse_new_folder_button, "clicked", @@ -6477,9 +6484,9 @@ update_appearance (GtkFileChooserDefault *impl) if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) - gtk_widget_show (impl->view_mode_combo_box); + gtk_widget_show (impl->view_mode_toggle_button); else - gtk_widget_hide (impl->view_mode_combo_box); + gtk_widget_hide (impl->view_mode_toggle_button); if (impl->location_entry) _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), impl->action); @@ -7043,13 +7050,16 @@ settings_load (GtkFileChooserDefault *impl) location_mode_set (impl, location_mode, TRUE); - g_signal_handlers_block_by_func (impl->icon_view_scale, - G_CALLBACK (icon_view_scale_value_changed_cb), - impl); - gtk_range_set_value (GTK_RANGE (impl->icon_view_scale), icon_view_scale); - g_signal_handlers_unblock_by_func (impl->icon_view_scale, - G_CALLBACK (icon_view_scale_value_changed_cb), - impl); + if (impl->icon_view_scale) + { + g_signal_handlers_block_by_func (impl->icon_view_scale, + G_CALLBACK (icon_view_scale_value_changed_cb), + impl); + gtk_range_set_value (GTK_RANGE (impl->icon_view_scale), icon_view_scale); + g_signal_handlers_unblock_by_func (impl->icon_view_scale, + G_CALLBACK (icon_view_scale_value_changed_cb), + impl); + } view_mode_set (impl, view_mode); diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index 193dea2e3..6e3456bc6 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -227,11 +227,9 @@ struct _GtkFileChooserDefault GtkWidget *extra_align; GtkWidget *extra_widget; - GtkWidget *view_mode_combo_box; + GtkWidget *view_mode_toggle_button; GtkWidget *icon_view_scale_hbox; GtkWidget *icon_view_scale; - GtkWidget *icon_view_scale_zoom_in_icon; - GtkWidget *icon_view_scale_zoom_out_icon; guint icon_view_scale_timeout_id; guint icon_view_scale_visible_timeout_id; guint icon_view_scale_after_layout_id; From 399e6ee64ecba9b47bd296c909e6105cad32e142 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 11 Aug 2026 10:47:36 +0200 Subject: [PATCH 37/56] cups: reduce authentication allocation overhead Avoid unnecessary copies and heap allocations in the CUPS authentication paths. Use borrowed strings where their lifetime is sufficient, use stack storage for the fixed password prompt fields, replace manual auth-info copying with g_strdupv(), and remove unused per-request hostname processing. Also free auth_info_visible in request_auth_info(). --- .../printbackends/cups/gtkprintbackendcups.c | 120 ++++++------------ 1 file changed, 40 insertions(+), 80 deletions(-) diff --git a/modules/printbackends/cups/gtkprintbackendcups.c b/modules/printbackends/cups/gtkprintbackendcups.c index d07d1bc7a..58f664884 100644 --- a/modules/printbackends/cups/gtkprintbackendcups.c +++ b/modules/printbackends/cups/gtkprintbackendcups.c @@ -61,7 +61,6 @@ #include "gtkcupsutils.h" - typedef struct _GtkPrintBackendCupsClass GtkPrintBackendCupsClass; #define GTK_PRINT_BACKEND_CUPS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_PRINT_BACKEND_CUPS, GtkPrintBackendCupsClass)) @@ -979,55 +978,43 @@ gtk_print_backend_cups_set_password (GtkPrintBackend *backend, { GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (backend); GList *l; - char dispatch_hostname[HTTP_MAX_URI]; - gchar *key; - gchar *username = NULL; - gchar *hostname = NULL; - gchar *password = NULL; - gint length; + gchar *key = NULL; + const gchar *username = NULL; + const gchar *hostname = NULL; + const gchar *password = NULL; gint i; - length = g_strv_length (auth_info_required); - if (auth_info != NULL) - for (i = 0; i < length; i++) + for (i = 0; auth_info_required[i] != NULL; i++) { if (g_strcmp0 (auth_info_required[i], "username") == 0) - username = g_strdup (auth_info[i]); + username = auth_info[i]; else if (g_strcmp0 (auth_info_required[i], "hostname") == 0) - hostname = g_strdup (auth_info[i]); + hostname = auth_info[i]; else if (g_strcmp0 (auth_info_required[i], "password") == 0) - password = g_strdup (auth_info[i]); + password = auth_info[i]; } if (hostname != NULL && username != NULL && password != NULL) { key = g_strconcat (username, "@", hostname, NULL); - g_hash_table_insert (cups_backend->auth, key, g_strdup (password)); + g_hash_table_replace (cups_backend->auth, key, g_strdup (password)); + + GTK_NOTE (PRINTING, + g_print ("CUPS backend: storing password for %s\n", key)); } g_free (cups_backend->username); cups_backend->username = g_strdup (username); - GTK_NOTE (PRINTING, - g_print ("CUPS backend: storing password for %s\n", key)); - for (l = cups_backend->requests; l; l = l->next) { GtkPrintCupsDispatchWatch *dispatch = l->data; - httpGetHostname (dispatch->request->http, dispatch_hostname, sizeof (dispatch_hostname)); - if (is_address_local (dispatch_hostname)) - strcpy (dispatch_hostname, "localhost"); - if (dispatch->request->need_auth_info) { if (auth_info != NULL) - { - dispatch->request->auth_info = g_new0 (gchar *, length + 1); - for (i = 0; i < length; i++) - dispatch->request->auth_info[i] = g_strdup (auth_info[i]); - } + dispatch->request->auth_info = g_strdupv (auth_info); dispatch->backend->authentication_lock = FALSE; dispatch->request->need_auth_info = FALSE; } @@ -1052,12 +1039,14 @@ request_password (gpointer data) gchar *prompt = NULL; gchar *key = NULL; char hostname[HTTP_MAX_URI]; - gchar **auth_info_required; - gchar **auth_info_default; - gchar **auth_info_display; - gboolean *auth_info_visible; - gint length = 3; - gint i; + const gchar *auth_info_required[4] = + { "hostname", "username", "password", NULL }; + const gchar *auth_info_default[4] = + { NULL, NULL, NULL, NULL }; + const gchar *auth_info_display[4] = + { NULL, NULL, NULL, NULL }; + gboolean auth_info_visible[4] = + { FALSE, TRUE, FALSE, FALSE }; if (dispatch->backend->authentication_lock) return FALSE; @@ -1071,21 +1060,11 @@ request_password (gpointer data) else username = cupsUser (); - auth_info_required = g_new0 (gchar*, length + 1); - auth_info_required[0] = g_strdup ("hostname"); - auth_info_required[1] = g_strdup ("username"); - auth_info_required[2] = g_strdup ("password"); + auth_info_default[0] = hostname; + auth_info_default[1] = username; - auth_info_default = g_new0 (gchar*, length + 1); - auth_info_default[0] = g_strdup (hostname); - auth_info_default[1] = g_strdup (username); - - auth_info_display = g_new0 (gchar*, length + 1); - auth_info_display[1] = g_strdup (_("Username:")); - auth_info_display[2] = g_strdup (_("Password:")); - - auth_info_visible = g_new0 (gboolean, length + 1); - auth_info_visible[1] = TRUE; + auth_info_display[1] = _("Username:"); + auth_info_display[2] = _("Password:"); key = g_strconcat (username, "@", hostname, NULL); password = g_hash_table_lookup (dispatch->backend->auth, key); @@ -1105,10 +1084,10 @@ request_password (gpointer data) { const char *job_title = gtk_cups_request_ipp_get_string (dispatch->request, IPP_TAG_NAME, "job-name"); const char *printer_uri = gtk_cups_request_ipp_get_string (dispatch->request, IPP_TAG_URI, "printer-uri"); - char *printer_name = NULL; + const char *printer_name = NULL; if (printer_uri != NULL && strrchr (printer_uri, '/') != NULL) - printer_name = g_strdup (strrchr (printer_uri, '/') + 1); + printer_name = strrchr (printer_uri, '/') + 1; if (dispatch->request->password_state == GTK_CUPS_PASSWORD_NOT_VALID) g_hash_table_remove (dispatch->backend->auth, key); @@ -1152,25 +1131,12 @@ request_password (gpointer data) break; } - g_free (printer_name); - g_signal_emit_by_name (dispatch->backend, "request-password", auth_info_required, auth_info_default, auth_info_display, auth_info_visible, prompt); g_free (prompt); } - for (i = 0; i < length; i++) - { - g_free (auth_info_required[i]); - g_free (auth_info_default[i]); - g_free (auth_info_display[i]); - } - - g_free (auth_info_required); - g_free (auth_info_default); - g_free (auth_info_display); - g_free (auth_info_visible); g_free (key); return FALSE; @@ -1270,12 +1236,12 @@ request_auth_info (gpointer user_data) const char *job_title; const char *printer_uri; gchar *prompt = NULL; - char *printer_name = NULL; + const char *printer_name = NULL; gint length; gint i; gboolean *auth_info_visible = NULL; - gchar **auth_info_default = NULL; - gchar **auth_info_display = NULL; + const gchar **auth_info_default = NULL; + const gchar **auth_info_display = NULL; dispatch = (GtkPrintCupsDispatchWatch *) user_data; @@ -1287,35 +1253,35 @@ request_auth_info (gpointer user_data) length = g_strv_length (dispatch->request->auth_info_required); auth_info_visible = g_new0 (gboolean, length); - auth_info_default = g_new0 (gchar *, length + 1); - auth_info_display = g_new0 (gchar *, length + 1); + auth_info_default = g_new0 (const gchar *, length + 1); + auth_info_display = g_new0 (const gchar *, length + 1); for (i = 0; i < length; i++) { if (g_strcmp0 (dispatch->request->auth_info_required[i], "domain") == 0) { - auth_info_display[i] = g_strdup (_("Domain:")); - auth_info_default[i] = g_strdup ("WORKGROUP"); + auth_info_display[i] = _("Domain:"); + auth_info_default[i] = "WORKGROUP"; auth_info_visible[i] = TRUE; } else if (g_strcmp0 (dispatch->request->auth_info_required[i], "username") == 0) { - auth_info_display[i] = g_strdup (_("Username:")); + auth_info_display[i] = _("Username:"); if (dispatch->backend->username != NULL) - auth_info_default[i] = g_strdup (dispatch->backend->username); + auth_info_default[i] = dispatch->backend->username; else - auth_info_default[i] = g_strdup (cupsUser ()); + auth_info_default[i] = cupsUser (); auth_info_visible[i] = TRUE; } else if (g_strcmp0 (dispatch->request->auth_info_required[i], "password") == 0) { - auth_info_display[i] = g_strdup (_("Password:")); + auth_info_display[i] = _("Password:"); auth_info_visible[i] = FALSE; } } if (printer_uri != NULL && strrchr (printer_uri, '/') != NULL) - printer_name = g_strdup (strrchr (printer_uri, '/') + 1); + printer_name = strrchr (printer_uri, '/') + 1; dispatch->backend->authentication_lock = TRUE; @@ -1341,15 +1307,9 @@ request_auth_info (gpointer user_data) auth_info_visible, prompt); - for (i = 0; i < length; i++) - { - g_free (auth_info_default[i]); - g_free (auth_info_display[i]); - } - g_free (auth_info_default); g_free (auth_info_display); - g_free (printer_name); + g_free (auth_info_visible); g_free (prompt); g_idle_add (check_auth_info, user_data); From fa2e5d3293470e1dc896a51316927fa3ab5fcfae Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 12 Aug 2026 19:38:55 +0200 Subject: [PATCH 38/56] cups: remember authentication with Secret Service Look up and store CUPS authentication information through the Secret Service D-Bus API. Enable the feature by default when CUPS support is built, requiring the libsecret development files unless explicitly disabled. Keep the print backend free of a runtime libsecret dependency so authentication continues to work when Secret Service is unavailable. --- configure.ac | 16 + gtk/gtkprintbackend.c | 90 ++ .../printbackends/cups/gtkprintbackendcups.c | 941 ++++++++++++++++++ 3 files changed, 1047 insertions(+) diff --git a/configure.ac b/configure.ac index 04536e910..553ea001c 100644 --- a/configure.ac +++ b/configure.ac @@ -1538,6 +1538,11 @@ LIBS="$old_LIBS" # Printing system checks ################################################################ +AC_ARG_ENABLE(cups-secret-service, + [AS_HELP_STRING([--disable-cups-secret-service] + [disable Secret Service support in the cups print backend])],, + [enable_cups_secret_service=yes]) + AC_ARG_ENABLE(cups, [AS_HELP_STRING([--disable-cups] [disable cups print backend])],, @@ -1584,6 +1589,17 @@ else AM_CONDITIONAL(HAVE_CUPS, true) + if test "x$enable_cups_secret_service" != "xno"; then + PKG_CHECK_EXISTS([libsecret-1], [], + [AC_MSG_ERROR([ +*** libsecret-1 is required when CUPS Secret Service support is enabled. +*** Install the libsecret development files or configure +*** with --disable-cups-secret-service. +])]) + AC_DEFINE([HAVE_CUPS_SECRET_SERVICE], [1], + [Define to 1 if CUPS Secret Service support is enabled]) + fi + gtk_save_cflags="$CFLAGS" CFLAGS="$CUPS_CFLAGS" AC_COMPILE_IFELSE( diff --git a/gtk/gtkprintbackend.c b/gtk/gtkprintbackend.c index 66f5b41a9..5efd124e5 100644 --- a/gtk/gtkprintbackend.c +++ b/gtk/gtkprintbackend.c @@ -48,8 +48,22 @@ struct _GtkPrintBackendPrivate GtkPrintBackendStatus status; char **auth_info_required; char **auth_info; +#ifdef HAVE_CUPS_SECRET_SERVICE + guint store_auth_info : 1; +#endif }; +#ifdef HAVE_CUPS_SECRET_SERVICE +typedef gboolean (*GtkPrintBackendCanStoreAuthInfoFunc) + (GtkPrintBackend *backend); + +typedef void (*GtkPrintBackendSetPasswordFullFunc) + (GtkPrintBackend *backend, + gchar **auth_info_required, + gchar **auth_info, + gboolean store_auth_info); +#endif + enum { PRINTER_LIST_CHANGED, PRINTER_LIST_DONE, @@ -459,6 +473,9 @@ gtk_print_backend_init (GtkPrintBackend *backend) (GDestroyNotify) g_object_unref); priv->auth_info_required = NULL; priv->auth_info = NULL; +#ifdef HAVE_CUPS_SECRET_SERVICE + priv->store_auth_info = FALSE; +#endif } static void @@ -675,6 +692,41 @@ gtk_print_backend_set_password (GtkPrintBackend *backend, GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend, auth_info_required, auth_info); } + +#ifdef HAVE_CUPS_SECRET_SERVICE +static void +gtk_print_backend_set_password_full (GtkPrintBackend *backend, + gchar **auth_info_required, + gchar **auth_info, + gboolean store_auth_info) +{ + GtkPrintBackendClass *class; + + class = GTK_PRINT_BACKEND_GET_CLASS (backend); + + if (class->_gtk_reserved2 != NULL) + ((GtkPrintBackendSetPasswordFullFunc) class->_gtk_reserved2) + (backend, + auth_info_required, + auth_info, + store_auth_info); + else + gtk_print_backend_set_password (backend, + auth_info_required, + auth_info); +} + + +static void +store_auth_info_toggled (GtkToggleButton *button, + GtkPrintBackend *backend) +{ + backend->priv->store_auth_info = + gtk_toggle_button_get_active (button); +} +#endif + + static void store_entry (GtkEntry *entry, gpointer user_data) @@ -698,10 +750,23 @@ password_dialog_response (GtkWidget *dialog, GtkPrintBackendPrivate *priv = backend->priv; gint i; +#ifdef HAVE_CUPS_SECRET_SERVICE + if (response_id == GTK_RESPONSE_OK) + gtk_print_backend_set_password_full (backend, + priv->auth_info_required, + priv->auth_info, + priv->store_auth_info); + else + gtk_print_backend_set_password_full (backend, + priv->auth_info_required, + NULL, + FALSE); +#else if (response_id == GTK_RESPONSE_OK) gtk_print_backend_set_password (backend, priv->auth_info_required, priv->auth_info); else gtk_print_backend_set_password (backend, priv->auth_info_required, NULL); +#endif for (i = 0; i < g_strv_length (priv->auth_info_required); i++) if (priv->auth_info[i] != NULL) @@ -738,10 +803,23 @@ request_password (GtkPrintBackend *backend, gchar **ai_default = (gchar **) auth_info_default; gchar **ai_display = (gchar **) auth_info_display; gboolean *ai_visible = (gboolean *) auth_info_visible; +#ifdef HAVE_CUPS_SECRET_SERVICE + GtkPrintBackendClass *class; + gboolean can_store_auth_info = FALSE; +#endif priv->auth_info_required = g_strdupv (ai_required); length = g_strv_length (ai_required); priv->auth_info = g_new0 (gchar *, length + 1); +#ifdef HAVE_CUPS_SECRET_SERVICE + priv->store_auth_info = FALSE; + + class = GTK_PRINT_BACKEND_GET_CLASS (backend); + if (class->_gtk_reserved1 != NULL) + can_store_auth_info = + ((GtkPrintBackendCanStoreAuthInfoFunc) class->_gtk_reserved1) + (backend); +#endif dialog = gtk_dialog_new_with_buttons ( _("Authentication"), NULL, GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, @@ -810,6 +888,18 @@ request_password (GtkPrintBackend *backend, } } +#ifdef HAVE_CUPS_SECRET_SERVICE + if (can_store_auth_info) + { + GtkWidget *remember; + + remember = gtk_check_button_new_with_mnemonic (_("_Remember password")); + gtk_box_pack_start (GTK_BOX (vbox), remember, FALSE, FALSE, 6); + g_signal_connect (remember, "toggled", + G_CALLBACK (store_auth_info_toggled), backend); + } +#endif + if (focus != NULL) { gtk_widget_grab_focus (focus); diff --git a/modules/printbackends/cups/gtkprintbackendcups.c b/modules/printbackends/cups/gtkprintbackendcups.c index 58f664884..c417ba88e 100644 --- a/modules/printbackends/cups/gtkprintbackendcups.c +++ b/modules/printbackends/cups/gtkprintbackendcups.c @@ -143,6 +143,11 @@ struct _GtkPrintBackendCups GHashTable *auth; gchar *username; gboolean authentication_lock; +#ifdef HAVE_CUPS_SECRET_SERVICE + gchar *store_auth_info_uri; + guint secrets_service_available : 1; + guint store_auth_info_allowed : 1; +#endif #ifdef HAVE_CUPS_API_1_6 GDBusConnection *dbus_connection; gchar *avahi_default_printer; @@ -212,10 +217,21 @@ static cairo_surface_t * cups_printer_create_cairo_surface (GtkPrinter static void gtk_print_backend_cups_set_password (GtkPrintBackend *backend, gchar **auth_info_required, gchar **auth_info); +#ifdef HAVE_CUPS_SECRET_SERVICE +static void gtk_print_backend_cups_set_password_full (GtkPrintBackend *backend, + gchar **auth_info_required, + gchar **auth_info, + gboolean store_auth_info); +static gboolean gtk_print_backend_cups_can_store_auth_info + (GtkPrintBackend *backend); +#endif void overwrite_and_free (gpointer data); static gboolean is_address_local (const gchar *address); static gboolean request_auth_info (gpointer data); +#ifdef HAVE_CUPS_SECRET_SERVICE +static void lookup_auth_info (gpointer data); +#endif #ifdef HAVE_CUPS_API_1_6 static void avahi_request_printer_list (GtkPrintBackendCups *cups_backend); @@ -340,6 +356,12 @@ gtk_print_backend_cups_class_init (GtkPrintBackendCupsClass *class) backend_class->printer_get_hard_margins = cups_printer_get_hard_margins; backend_class->printer_get_capabilities = cups_printer_get_capabilities; backend_class->set_password = gtk_print_backend_cups_set_password; +#ifdef HAVE_CUPS_SECRET_SERVICE + backend_class->_gtk_reserved1 = + (void (*) (void)) gtk_print_backend_cups_can_store_auth_info; + backend_class->_gtk_reserved2 = + (void (*) (void)) gtk_print_backend_cups_set_password_full; +#endif } static cairo_status_t @@ -806,6 +828,11 @@ gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups) backend_cups->requests = NULL; backend_cups->auth = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, overwrite_and_free); backend_cups->authentication_lock = FALSE; +#ifdef HAVE_CUPS_SECRET_SERVICE + backend_cups->store_auth_info_uri = NULL; + backend_cups->secrets_service_available = FALSE; + backend_cups->store_auth_info_allowed = FALSE; +#endif backend_cups->covers = NULL; backend_cups->number_of_covers = 0; @@ -852,6 +879,10 @@ gtk_print_backend_cups_finalize (GObject *object) g_free (backend_cups->username); +#ifdef HAVE_CUPS_SECRET_SERVICE + g_free (backend_cups->store_auth_info_uri); +#endif + #ifdef HAVE_CUPS_API_1_6 g_clear_object (&backend_cups->avahi_cancellable); g_free (backend_cups->avahi_default_printer); @@ -971,10 +1002,22 @@ httpGetHostname(http_t *http, } #endif +#ifdef HAVE_CUPS_SECRET_SERVICE +static void cups_secret_store (gchar **auth_info, + gchar **auth_info_labels, + const gchar *printer_uri); + +static void +gtk_print_backend_cups_set_password_full (GtkPrintBackend *backend, + gchar **auth_info_required, + gchar **auth_info, + gboolean store_auth_info) +#else static void gtk_print_backend_cups_set_password (GtkPrintBackend *backend, gchar **auth_info_required, gchar **auth_info) +#endif { GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (backend); GList *l; @@ -1007,6 +1050,18 @@ gtk_print_backend_cups_set_password (GtkPrintBackend *backend, g_free (cups_backend->username); cups_backend->username = g_strdup (username); +#ifdef HAVE_CUPS_SECRET_SERVICE + if (store_auth_info && + password != NULL && + cups_backend->store_auth_info_uri != NULL) + cups_secret_store (auth_info, + auth_info_required, + cups_backend->store_auth_info_uri); + + g_free (cups_backend->store_auth_info_uri); + cups_backend->store_auth_info_uri = NULL; +#endif + for (l = cups_backend->requests; l; l = l->next) { GtkPrintCupsDispatchWatch *dispatch = l->data; @@ -1030,6 +1085,33 @@ gtk_print_backend_cups_set_password (GtkPrintBackend *backend, } } + +#ifdef HAVE_CUPS_SECRET_SERVICE +static void +gtk_print_backend_cups_set_password (GtkPrintBackend *backend, + gchar **auth_info_required, + gchar **auth_info) +{ + gtk_print_backend_cups_set_password_full (backend, + auth_info_required, + auth_info, + FALSE); +} + + +static gboolean +gtk_print_backend_cups_can_store_auth_info (GtkPrintBackend *backend) +{ + GtkPrintBackendCups *cups_backend; + + cups_backend = GTK_PRINT_BACKEND_CUPS (backend); + + return cups_backend->secrets_service_available && + cups_backend->store_auth_info_allowed; +} +#endif + + static gboolean request_password (gpointer data) { @@ -1229,6 +1311,845 @@ check_auth_info (gpointer user_data) return TRUE; } + +#ifdef HAVE_CUPS_SECRET_SERVICE + +#define CUPS_SECRET_BUS "org.freedesktop.secrets" +#define CUPS_SECRET_PATH "/org/freedesktop/secrets" +#define CUPS_SECRET_DEFAULT_COLLECTION \ + "/org/freedesktop/secrets/aliases/default" +#define CUPS_SECRET_IFACE(x) "org.freedesktop.Secret." x +#define CUPS_SECRET_TIMEOUT 5000 + +typedef enum +{ + CUPS_SECRET_SEARCH, + CUPS_SECRET_UNLOCK, + CUPS_SECRET_PROMPT, + CUPS_SECRET_PROMPT_WAIT, + CUPS_SECRET_ATTRIBUTES, + CUPS_SECRET_SESSION, + CUPS_SECRET_VALUE, + CUPS_SECRET_CREATE +} CupsSecretStep; + +typedef struct +{ + GDBusConnection *connection; + GtkPrintCupsDispatchWatch *dispatch; + gchar *item_path; + gchar *session_path; + gchar *prompt_path; + gchar **auth_info; + GVariant *store_properties; + gchar *store_password; + guint prompt_subscription; + guint length; + guint prompt_completed : 1; + guint prompt_dismissed : 1; + guint store : 1; + guint create_prompt : 1; + gint password_field; + CupsSecretStep step; +} CupsSecretLookup; + + +static void cups_secret_reply (GObject *, GAsyncResult *, gpointer); + + +static void +cups_secret_call (CupsSecretLookup *lookup, + const gchar *path, + const gchar *interface, + const gchar *method, + GVariant *parameters, + const GVariantType *reply_type, + CupsSecretStep step) +{ + lookup->step = step; + + g_dbus_connection_call (lookup->connection, + CUPS_SECRET_BUS, + path, + interface, + method, + parameters, + reply_type, + G_DBUS_CALL_FLAGS_NONE, + CUPS_SECRET_TIMEOUT, + NULL, + cups_secret_reply, + lookup); +} + + +static void +cups_secret_free (CupsSecretLookup *lookup) +{ + guint i; + + if (lookup->prompt_subscription != 0) + g_dbus_connection_signal_unsubscribe (lookup->connection, + lookup->prompt_subscription); + + if (lookup->session_path != NULL) + g_dbus_connection_call (lookup->connection, + CUPS_SECRET_BUS, + lookup->session_path, + CUPS_SECRET_IFACE ("Session"), + "Close", + NULL, + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + CUPS_SECRET_TIMEOUT, + NULL, + NULL, + NULL); + + if (lookup->auth_info != NULL) + { + for (i = 0; i < lookup->length; i++) + overwrite_and_free (lookup->auth_info[i]); + + g_free (lookup->auth_info); + } + + if (lookup->store_properties != NULL) + g_variant_unref (lookup->store_properties); + + if (lookup->store_password != NULL) + overwrite_and_free (lookup->store_password); + + g_free (lookup->item_path); + g_free (lookup->session_path); + g_free (lookup->prompt_path); + + if (lookup->connection != NULL) + g_object_unref (lookup->connection); + + g_free (lookup); +} + + +static void +cups_secret_done (CupsSecretLookup *lookup, + gboolean found, + GError *error) +{ + GtkPrintCupsDispatchWatch *dispatch; + + if (lookup->store) + { + if (error != NULL) + { + GTK_NOTE (PRINTING, + g_print ("CUPS backend: failed to remember password: %s\n", + error->message)); + g_error_free (error); + } + + cups_secret_free (lookup); + return; + } + + dispatch = lookup->dispatch; + + dispatch->backend->secrets_service_available = + error == NULL; + + if (error != NULL) + { + GTK_NOTE (PRINTING, + g_print ("CUPS backend: Secret Service lookup failed: %s\n", + error->message)); + g_error_free (error); + } + + if (found) + gtk_print_backend_cups_set_password ( + GTK_PRINT_BACKEND (dispatch->backend), + dispatch->request->auth_info_required, + lookup->auth_info); + else + dispatch->backend->authentication_lock = FALSE; + + cups_secret_free (lookup); + + if (found) + check_auth_info (dispatch); + else + request_auth_info (dispatch); +} + + + +static void +cups_secret_get_attributes (CupsSecretLookup *lookup) +{ + cups_secret_call (lookup, + lookup->item_path, + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + CUPS_SECRET_IFACE ("Item"), + "Attributes"), + G_VARIANT_TYPE ("(v)"), + CUPS_SECRET_ATTRIBUTES); +} + + +static void +cups_secret_open_session (CupsSecretLookup *lookup) +{ + cups_secret_call ( + lookup, + CUPS_SECRET_PATH, + CUPS_SECRET_IFACE ("Service"), + "OpenSession", + g_variant_new ("(sv)", + "plain", + g_variant_new_string ("")), + G_VARIANT_TYPE ("(vo)"), + CUPS_SECRET_SESSION); +} + + +static void +cups_secret_create_item (CupsSecretLookup *lookup) +{ + GVariant *properties; + GVariant *secret; + + if (lookup->store_properties == NULL || + lookup->store_password == NULL || + lookup->session_path == NULL) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + properties = lookup->store_properties; + lookup->store_properties = NULL; + + secret = g_variant_new ( + "(oay@ays)", + lookup->session_path, + NULL, + g_variant_new_bytestring (lookup->store_password), + "text/plain"); + + cups_secret_call ( + lookup, + CUPS_SECRET_DEFAULT_COLLECTION, + CUPS_SECRET_IFACE ("Collection"), + "CreateItem", + g_variant_new ("(@a{sv}@(oayays)b)", + properties, + secret, + TRUE), + G_VARIANT_TYPE ("(oo)"), + CUPS_SECRET_CREATE); +} + + +static void +cups_secret_after_prompt (CupsSecretLookup *lookup, + gboolean dismissed) +{ + if (dismissed) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + if (!lookup->store) + { + cups_secret_get_attributes (lookup); + return; + } + + if (lookup->create_prompt) + { + cups_secret_done (lookup, TRUE, NULL); + return; + } + + cups_secret_open_session (lookup); +} + + + +static void +cups_secret_prompt_completed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + CupsSecretLookup *lookup = user_data; + gboolean dismissed; + + (void) connection; + (void) sender_name; + (void) object_path; + (void) interface_name; + (void) signal_name; + + g_dbus_connection_signal_unsubscribe ( + lookup->connection, + lookup->prompt_subscription); + + lookup->prompt_subscription = 0; + + g_free (lookup->prompt_path); + lookup->prompt_path = NULL; + + if (!g_variant_is_of_type (parameters, + G_VARIANT_TYPE ("(bv)"))) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + g_variant_get (parameters, "(bv)", + &dismissed, NULL); + + if (lookup->step == CUPS_SECRET_PROMPT) + { + lookup->prompt_completed = TRUE; + lookup->prompt_dismissed = dismissed; + return; + } + + cups_secret_after_prompt (lookup, dismissed); +} + + +static void +cups_secret_prompt (CupsSecretLookup *lookup) +{ + lookup->prompt_completed = FALSE; + lookup->prompt_dismissed = FALSE; + + lookup->prompt_subscription = + g_dbus_connection_signal_subscribe ( + lookup->connection, + NULL, + CUPS_SECRET_IFACE ("Prompt"), + "Completed", + lookup->prompt_path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + cups_secret_prompt_completed, + lookup, + NULL); + + cups_secret_call ( + lookup, + lookup->prompt_path, + CUPS_SECRET_IFACE ("Prompt"), + "Prompt", + g_variant_new ("(s)", "0"), + G_VARIANT_TYPE ("()"), + CUPS_SECRET_PROMPT); +} + + + +static void +cups_secret_reply (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CupsSecretLookup *lookup = user_data; + GError *error = NULL; + GVariant *output; + + output = g_dbus_connection_call_finish ( + G_DBUS_CONNECTION (source_object), res, &error); + + if (output == NULL) + { + cups_secret_done (lookup, FALSE, error); + return; + } + + switch (lookup->step) + { + case CUPS_SECRET_SEARCH: + { + const gchar **unlocked; + const gchar **locked; + const gchar *path; + gboolean is_locked; + + g_variant_get (output, "(^a&o^a&o)", + &unlocked, &locked); + g_variant_unref (output); + + if (unlocked[0] != NULL) + { + path = unlocked[0]; + is_locked = FALSE; + } + else + { + path = locked[0]; + is_locked = TRUE; + } + + if (path != NULL) + lookup->item_path = g_strdup (path); + + g_free (unlocked); + g_free (locked); + + if (lookup->item_path == NULL) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + if (!is_locked) + { + cups_secret_get_attributes (lookup); + return; + } + + { + const gchar *paths[2] = + { lookup->item_path, NULL }; + + cups_secret_call (lookup, + CUPS_SECRET_PATH, + CUPS_SECRET_IFACE ("Service"), + "Unlock", + g_variant_new ("(^ao)", paths), + G_VARIANT_TYPE ("(aoo)"), + CUPS_SECRET_UNLOCK); + } + + return; + } + + case CUPS_SECRET_UNLOCK: + { + g_variant_get (output, "(@aoo)", + NULL, &lookup->prompt_path); + g_variant_unref (output); + + if (strcmp (lookup->prompt_path, "/") == 0) + { + g_free (lookup->prompt_path); + lookup->prompt_path = NULL; + cups_secret_after_prompt (lookup, FALSE); + return; + } + + cups_secret_prompt (lookup); + return; + } + + case CUPS_SECRET_PROMPT: + g_variant_unref (output); + + if (lookup->prompt_completed) + cups_secret_after_prompt (lookup, + lookup->prompt_dismissed); + else + lookup->step = CUPS_SECRET_PROMPT_WAIT; + + return; + + case CUPS_SECRET_PROMPT_WAIT: + break; + + case CUPS_SECRET_ATTRIBUTES: + { + GVariant *attributes; + gchar **required; + const gchar *value; + guint i; + + g_variant_get (output, "(v)", &attributes); + g_variant_unref (output); + + if (!g_variant_is_of_type (attributes, + G_VARIANT_TYPE ("a{ss}"))) + { + g_variant_unref (attributes); + cups_secret_done (lookup, FALSE, NULL); + return; + } + + required = lookup->dispatch->request->auth_info_required; + lookup->auth_info = + g_new0 (gchar *, lookup->length + 1); + lookup->password_field = -1; + + for (i = 0; i < lookup->length; i++) + { + value = NULL; + + if (strcmp (required[i], "password") == 0) + { + lookup->password_field = i; + continue; + } + + if (strcmp (required[i], "username") == 0) + { + if (!g_variant_lookup (attributes, + "user", + "&s", + &value)) + g_variant_lookup (attributes, + "username", + "&s", + &value); + } + else if (strcmp (required[i], "hostname") == 0) + { + if (!g_variant_lookup (attributes, + "server", + "&s", + &value)) + g_variant_lookup (attributes, + "hostname", + "&s", + &value); + } + else if (strcmp (required[i], "domain") == 0) + { + g_variant_lookup (attributes, + "domain", + "&s", + &value); + } + + if (value == NULL) + { + g_variant_unref (attributes); + cups_secret_done (lookup, FALSE, NULL); + return; + } + + lookup->auth_info[i] = g_strdup (value); + } + + g_variant_unref (attributes); + + if (lookup->password_field < 0) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + cups_secret_open_session (lookup); + return; + } + + case CUPS_SECRET_SESSION: + { + g_variant_get (output, "(vo)", + NULL, &lookup->session_path); + g_variant_unref (output); + + if (strcmp (lookup->session_path, "/") == 0) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + if (lookup->store) + { + cups_secret_create_item (lookup); + return; + } + + cups_secret_call ( + lookup, + lookup->item_path, + CUPS_SECRET_IFACE ("Item"), + "GetSecret", + g_variant_new ("(o)", + lookup->session_path), + G_VARIANT_TYPE ("((oayays))"), + CUPS_SECRET_VALUE); + + return; + } + + case CUPS_SECRET_VALUE: + { + GVariant *value; + gconstpointer password; + gsize length; + guint i; + + g_variant_get (output, + "((o@ay@ays))", + NULL, NULL, &value, NULL); + g_variant_unref (output); + + password = g_variant_get_fixed_array ( + value, &length, sizeof (guchar)); + + if (password != NULL) + lookup->auth_info[lookup->password_field] = + g_strndup (password, length); + + g_variant_unref (value); + + for (i = 0; i < lookup->length; i++) + if (lookup->auth_info[i] == NULL) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + cups_secret_done (lookup, TRUE, NULL); + return; + } + + case CUPS_SECRET_CREATE: + { + const gchar *item; + const gchar *prompt; + + g_variant_get (output, "(&o&o)", + &item, &prompt); + + if (strcmp (item, "/") != 0) + { + g_variant_unref (output); + cups_secret_done (lookup, TRUE, NULL); + return; + } + + if (strcmp (prompt, "/") == 0) + { + g_variant_unref (output); + cups_secret_done (lookup, FALSE, NULL); + return; + } + + g_free (lookup->prompt_path); + lookup->prompt_path = g_strdup (prompt); + lookup->create_prompt = TRUE; + + g_variant_unref (output); + + cups_secret_prompt (lookup); + return; + } + } + + g_variant_unref (output); + cups_secret_done (lookup, FALSE, NULL); +} + + +static void +cups_secret_connection (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CupsSecretLookup *lookup = user_data; + GVariantBuilder builder; + GError *error = NULL; + const gchar *uri; + + (void) source_object; + + lookup->connection = g_bus_get_finish (res, &error); + + if (lookup->connection == NULL) + { + cups_secret_done (lookup, FALSE, error); + return; + } + + if (lookup->store) + { + const gchar *paths[2] = + { CUPS_SECRET_DEFAULT_COLLECTION, NULL }; + + cups_secret_call ( + lookup, + CUPS_SECRET_PATH, + CUPS_SECRET_IFACE ("Service"), + "Unlock", + g_variant_new ("(^ao)", paths), + G_VARIANT_TYPE ("(aoo)"), + CUPS_SECRET_UNLOCK); + + return; + } + + uri = gtk_cups_request_ipp_get_string ( + lookup->dispatch->request, + IPP_TAG_URI, + "printer-uri"); + + if (uri == NULL) + { + cups_secret_done (lookup, FALSE, NULL); + return; + } + + g_variant_builder_init (&builder, + G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&builder, + "{ss}", + "uri", + uri); + + cups_secret_call ( + lookup, + CUPS_SECRET_PATH, + CUPS_SECRET_IFACE ("Service"), + "SearchItems", + g_variant_new ("(@a{ss})", + g_variant_builder_end (&builder)), + G_VARIANT_TYPE ("(aoao)"), + CUPS_SECRET_SEARCH); +} + + +static void +lookup_auth_info (gpointer data) +{ + GtkPrintCupsDispatchWatch *dispatch = data; + CupsSecretLookup *lookup; + gchar **required; + guint i; + + if (dispatch->backend->authentication_lock) + return; + + required = dispatch->request->auth_info_required; + + for (i = 0; required[i] != NULL; i++) + if (strcmp (required[i], "password") == 0) + break; + + if (required[i] == NULL || + gtk_cups_request_ipp_get_string ( + dispatch->request, + IPP_TAG_URI, + "printer-uri") == NULL) + { + request_auth_info (dispatch); + return; + } + + lookup = g_new0 (CupsSecretLookup, 1); + lookup->dispatch = dispatch; + lookup->length = g_strv_length (required); + + dispatch->backend->authentication_lock = TRUE; + + g_bus_get (G_BUS_TYPE_SESSION, + NULL, + cups_secret_connection, + lookup); +} + + +static void +cups_secret_store (gchar **auth_info, + gchar **auth_info_labels, + const gchar *printer_uri) +{ + CupsSecretLookup *lookup; + GVariantBuilder attributes; + GVariantBuilder properties; + const gchar *password = NULL; + const gchar *key; + guint i; + + if (auth_info == NULL || + auth_info_labels == NULL || + printer_uri == NULL) + return; + + g_variant_builder_init (&attributes, + G_VARIANT_TYPE ("a{ss}")); + + g_variant_builder_add (&attributes, + "{ss}", + "uri", + printer_uri); + + for (i = 0; auth_info_labels[i] != NULL; i++) + { + if (auth_info[i] == NULL) + continue; + + key = NULL; + + if (strcmp (auth_info_labels[i], "password") == 0) + { + password = auth_info[i]; + continue; + } + + if (strcmp (auth_info_labels[i], "username") == 0) + key = "user"; + else if (strcmp (auth_info_labels[i], "hostname") == 0) + key = "server"; + else if (strcmp (auth_info_labels[i], "domain") == 0) + key = "domain"; + + if (key != NULL) + g_variant_builder_add (&attributes, + "{ss}", + key, + auth_info[i]); + } + + if (password == NULL) + { + g_variant_builder_clear (&attributes); + return; + } + + g_variant_builder_init (&properties, + G_VARIANT_TYPE ("a{sv}")); + + g_variant_builder_add ( + &properties, + "{sv}", + CUPS_SECRET_IFACE ("Item.Label"), + g_variant_new_string (printer_uri)); + + g_variant_builder_add ( + &properties, + "{sv}", + CUPS_SECRET_IFACE ("Item.Attributes"), + g_variant_builder_end (&attributes)); + + lookup = g_new0 (CupsSecretLookup, 1); + lookup->store = TRUE; + + lookup->store_properties = + g_variant_ref_sink ( + g_variant_builder_end (&properties)); + + lookup->store_password = + g_strdup (password); + + g_bus_get (G_BUS_TYPE_SESSION, + NULL, + cups_secret_connection, + lookup); +} +#endif + + static gboolean request_auth_info (gpointer user_data) { @@ -1300,6 +2221,18 @@ request_auth_info (gpointer user_data) prompt = g_strdup ( _("Authentication is required to print this document")); } +#ifdef HAVE_CUPS_SECRET_SERVICE + g_free (dispatch->backend->store_auth_info_uri); + dispatch->backend->store_auth_info_uri = NULL; + + dispatch->backend->store_auth_info_allowed = + dispatch->backend->secrets_service_available && + printer_uri != NULL; + + if (dispatch->backend->store_auth_info_allowed) + dispatch->backend->store_auth_info_uri = g_strdup (printer_uri); +#endif + g_signal_emit_by_name (dispatch->backend, "request-password", dispatch->request->auth_info_required, auth_info_default, @@ -1307,6 +2240,10 @@ request_auth_info (gpointer user_data) auth_info_visible, prompt); +#ifdef HAVE_CUPS_SECRET_SERVICE + dispatch->backend->store_auth_info_allowed = FALSE; +#endif + g_free (auth_info_default); g_free (auth_info_display); g_free (auth_info_visible); @@ -1514,7 +2451,11 @@ cups_request_execute (GtkPrintBackendCups *print_backend, { dispatch->callback = callback; dispatch->callback_data = user_data; +#ifdef HAVE_CUPS_SECRET_SERVICE + lookup_auth_info (dispatch); +#else request_auth_info (dispatch); +#endif } else { From f8ff879dd288ec8442bc0a2544c3d762d57d13a8 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 12 Aug 2026 20:59:11 +0200 Subject: [PATCH 39/56] x11: size available depth array correctly Visual initialization considers eight possible depths, but GdkScreenX11 only reserved space for seven. Reserve all eight entries so a server exposing every considered depth cannot write past the array. --- gdk/x11/gdkscreen-x11.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/x11/gdkscreen-x11.h b/gdk/x11/gdkscreen-x11.h index 7df8e0e7f..35d5d6c13 100644 --- a/gdk/x11/gdkscreen-x11.h +++ b/gdk/x11/gdkscreen-x11.h @@ -73,7 +73,7 @@ struct _GdkScreenX11 GdkVisualPrivate *system_visual; GdkVisualPrivate **visuals; gint nvisuals; - gint available_depths[7]; + gint available_depths[8]; gint navailable_depths; GdkVisualType available_types[6]; gint navailable_types; From be71ea43fca6a9dcbf4e509aa722c2901690ad68 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 12 Aug 2026 21:54:38 +0200 Subject: [PATCH 40/56] updateiconcache: make directory scans deterministic and leaner Sort directory entries before scanning so cache layout does not depend on filesystem enumeration order. Classify entries with one stat operation, reuse the sorted name buffers for suffix and basename handling, and release transient scan and cache-building resources once writing is complete. This preserves the sorted cache output while reducing filesystem metadata calls and eliminating observed leaks. Provide compile-time S_ISDIR and S_ISREG fallbacks for MSVC. Based on the openSUSE gtk2-updateiconcache_sort.patch. --- gtk/updateiconcache.c | 204 +++++++++++++++++++++++++++++------------- 1 file changed, 140 insertions(+), 64 deletions(-) diff --git a/gtk/updateiconcache.c b/gtk/updateiconcache.c index 4a3158e12..e167d2f8f 100644 --- a/gtk/updateiconcache.c +++ b/gtk/updateiconcache.c @@ -25,6 +25,15 @@ #include #include #include + +#ifdef _MSC_VER +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR) +#endif +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & _S_IFMT) == _S_IFREG) +#endif +#endif #include #ifdef HAVE_UNISTD_H #include @@ -608,6 +617,16 @@ replace_backslashes_with_slashes (gchar *path) path[i] = '/'; } +static gint +compare_string_pointers (gconstpointer a, + gconstpointer b) +{ + const gchar * const *str_a = a; + const gchar * const *str_b = b; + + return strcmp (*str_a, *str_b); +} + static GList * scan_directory (const gchar *base_path, const gchar *subdir, @@ -617,10 +636,13 @@ scan_directory (const gchar *base_path, { GHashTable *dir_hash; GDir *dir; - const gchar *name; + GPtrArray *names; + const gchar *entry_name; + gchar *name; gchar *dir_path; gboolean dir_added = FALSE; guint dir_index = 0xffff; + guint i; dir_path = g_build_path ("/", base_path, subdir, NULL); @@ -628,91 +650,103 @@ scan_directory (const gchar *base_path, dir = g_dir_open (dir_path, 0, NULL); if (!dir) - return directories; + { + g_free (dir_path); + return directories; + } + + names = g_ptr_array_new_with_free_func (g_free); + + while ((entry_name = g_dir_read_name (dir))) + g_ptr_array_add (names, g_strdup (entry_name)); + + g_dir_close (dir); + + g_ptr_array_sort (names, compare_string_pointers); dir_hash = g_hash_table_new (g_str_hash, g_str_equal); - while ((name = g_dir_read_name (dir))) + for (i = 0; i < names->len; i++) { + GStatBuf stat_buf; gchar *path; - gboolean retval; - int flags = 0; - Image *image; - gchar *basename, *dot; + name = g_ptr_array_index (names, i); path = g_build_filename (dir_path, name, NULL); - retval = g_file_test (path, G_FILE_TEST_IS_DIR); - if (retval) - { - gchar *subsubdir; + if (g_stat (path, &stat_buf) == 0) + { + if (S_ISDIR (stat_buf.st_mode)) + { + gchar *subsubdir; - if (subdir) - subsubdir = g_build_path ("/", subdir, name, NULL); - else - subsubdir = g_strdup (name); - directories = scan_directory (base_path, subsubdir, files, - directories, depth + 1); - g_free (subsubdir); + if (subdir) + subsubdir = g_build_path ("/", subdir, name, NULL); + else + subsubdir = g_strdup (name); - continue; - } + directories = scan_directory (base_path, subsubdir, files, + directories, depth + 1); + g_free (subsubdir); + } + else if (subdir != NULL && S_ISREG (stat_buf.st_mode)) + { + gint flags = 0; + Image *image; + gchar *dot; - /* ignore images in the toplevel directory */ - if (subdir == NULL) - continue; + dot = strrchr (name, '.'); - retval = g_file_test (path, G_FILE_TEST_IS_REGULAR); - if (retval) - { - if (g_str_has_suffix (name, ".png")) - flags |= HAS_SUFFIX_PNG; - else if (g_str_has_suffix (name, ".svg")) - flags |= HAS_SUFFIX_SVG; - else if (g_str_has_suffix (name, ".xpm")) - flags |= HAS_SUFFIX_XPM; - else if (g_str_has_suffix (name, ".icon")) - flags |= HAS_ICON_FILE; + if (dot != NULL) + { + if (strcmp (dot, ".png") == 0) + flags = HAS_SUFFIX_PNG; + else if (strcmp (dot, ".svg") == 0) + flags = HAS_SUFFIX_SVG; + else if (strcmp (dot, ".xpm") == 0) + flags = HAS_SUFFIX_XPM; + else if (strcmp (dot, ".icon") == 0) + flags = HAS_ICON_FILE; + } - if (flags == 0) - continue; + if (flags != 0) + { + *dot = '\0'; - basename = g_strdup (name); - dot = strrchr (basename, '.'); - *dot = '\0'; + image = g_hash_table_lookup (dir_hash, name); + if (!image) + { + if (!dir_added) + { + dir_added = TRUE; + dir_index = g_list_length (directories); + directories = + g_list_append (directories, + g_strdup (subdir)); + } - image = g_hash_table_lookup (dir_hash, basename); - if (!image) - { - if (!dir_added) - { - dir_added = TRUE; - if (subdir) - { - dir_index = g_list_length (directories); - directories = g_list_append (directories, g_strdup (subdir)); - } - else - continue; - } + image = g_new0 (Image, 1); + image->dir_index = dir_index; + g_hash_table_insert (dir_hash, + g_strdup (name), + image); + } - image = g_new0 (Image, 1); - image->dir_index = dir_index; - g_hash_table_insert (dir_hash, g_strdup (basename), image); - } + image->flags |= flags; - image->flags |= flags; + maybe_cache_image_data (image, path); + maybe_cache_icon_data (image, path); - maybe_cache_image_data (image, path); - maybe_cache_icon_data (image, path); - - g_free (basename); - } + *dot = '.'; + } + } + } g_free (path); } - g_dir_close (dir); + g_ptr_array_free (names, TRUE); + g_free (dir_path); /* Move dir into the big file hash */ g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files); @@ -1375,6 +1409,34 @@ write_dir_index (FILE *cache, int offset, GList *directories) return TRUE; } +static void +free_hash_context (HashContext *context) +{ + gint i; + + for (i = 0; i < context->size; i++) + { + HashNode *node = context->nodes[i]; + + while (node) + { + HashNode *next = node->next; + GList *l; + + for (l = node->image_list; l; l = l->next) + g_free (l->data); + + g_list_free (node->image_list); + g_free (node->name); + g_free (node); + + node = next; + } + } + + g_free (context->nodes); +} + static gboolean write_file (FILE *cache, GHashTable *files, GList *directories) { @@ -1418,6 +1480,11 @@ write_file (FILE *cache, GHashTable *files, GList *directories) return FALSE; } + g_hash_table_destroy (string_pool); + string_pool = NULL; + + free_hash_context (&context); + return TRUE; } @@ -1544,6 +1611,8 @@ opentmp: exit (1); } + g_hash_table_destroy (files); + if (!safe_fclose (cache)) { g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno)); @@ -1623,6 +1692,9 @@ opentmp: if (!quiet) g_printerr (_("Cache file created successfully.\n")); + + g_free (tmp_cache_path); + g_free (cache_path); } static void @@ -1658,6 +1730,9 @@ write_csource (const gchar *path) } g_printf ("0x%02x\n};\n", (guint8)data[i]); + + g_free (data); + g_free (cache_path); } static GOptionEntry args[] = { @@ -1721,6 +1796,7 @@ main (int argc, char **argv) g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE); g_option_context_parse (context, &argc, &argv, NULL); + g_option_context_free (context); path = argv[1]; #ifdef G_OS_WIN32 From 09131ada3a5fd6bd165497de75f34718890f74d1 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Wed, 12 Aug 2026 23:17:34 +0200 Subject: [PATCH 41/56] fontsel: reduce font list sorting overhead Precompute locale collation keys for font families instead of repeatedly collating names from the qsort comparator. Describe each font face once before sorting rather than allocating and freeing descriptions for every comparison, and populate the list stores with insert_with_values. Keep selection callbacks immediate and preserve GTK2-NG's restricted translation of standard font style names. This reduces FontSelection creation work substantially while keeping the resulting family, face and size models unchanged. --- gtk/gtkfontsel.c | 144 +++++++++++++++++++++++++++++------------------ 1 file changed, 90 insertions(+), 54 deletions(-) diff --git a/gtk/gtkfontsel.c b/gtk/gtkfontsel.c index 28ede4eb4..6bc2ce7ae 100644 --- a/gtk/gtkfontsel.c +++ b/gtk/gtkfontsel.c @@ -819,13 +819,22 @@ gtk_font_selection_select_font (GtkTreeSelection *selection, } } -static int -cmp_families (const void *a, const void *b) +typedef struct { - const char *a_name = pango_font_family_get_name (*(PangoFontFamily **)a); - const char *b_name = pango_font_family_get_name (*(PangoFontFamily **)b); + PangoFontFamily *family; + const gchar *name; + gchar *collate_key; +} FontFamilySortInfo; - return g_utf8_collate (a_name, b_name); +static int +cmp_families (const void *a, + const void *b) +{ + const FontFamilySortInfo *family_a = a; + const FontFamilySortInfo *family_b = b; + + return strcmp (family_a->collate_key, + family_b->collate_key); } static void @@ -833,6 +842,7 @@ gtk_font_selection_show_available_fonts (GtkFontSelection *fontsel) { GtkListStore *model; PangoFontFamily **families; + FontFamilySortInfo *sorted_families; PangoFontFamily *match_family = NULL; gint n_families, i; GtkTreeIter match_row; @@ -840,27 +850,37 @@ gtk_font_selection_show_available_fonts (GtkFontSelection *fontsel) model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (fontsel->family_list))); pango_context_list_families (gtk_widget_get_pango_context (GTK_WIDGET (fontsel)), - &families, &n_families); - qsort (families, n_families, sizeof (PangoFontFamily *), cmp_families); + &families, &n_families); + + sorted_families = g_new (FontFamilySortInfo, n_families); + for (i = 0; i < n_families; i++) + { + const gchar *name = pango_font_family_get_name (families[i]); + + sorted_families[i].family = families[i]; + sorted_families[i].name = name; + sorted_families[i].collate_key = g_utf8_collate_key (name ? name : "", -1); + } + + qsort (sorted_families, n_families, sizeof (FontFamilySortInfo), cmp_families); gtk_list_store_clear (model); - for (i=0; ifamily_list), &match_row); #ifdef INCLUDE_FONT_ENTRIES gtk_entry_set_text (GTK_ENTRY (fontsel->font_entry), - pango_font_family_get_name (match_family)); + pango_font_family_get_name (match_family)); #endif /* INCLUDE_FONT_ENTRIES */ } + for (i = 0; i < n_families; i++) + g_free (sorted_families[i].collate_key); + + g_free (sorted_families); g_free (families); } @@ -898,18 +922,22 @@ compare_font_descriptions (const PangoFontDescription *a, const PangoFontDescrip return 0; } -static int -faces_sort_func (const void *a, const void *b) +typedef struct { - PangoFontDescription *desc_a = pango_font_face_describe (*(PangoFontFace **)a); - PangoFontDescription *desc_b = pango_font_face_describe (*(PangoFontFace **)b); + PangoFontFace *face; + PangoFontDescription *desc; + const gchar *name; +} FontFaceSortInfo; - int ord = compare_font_descriptions (desc_a, desc_b); +static int +faces_sort_func (const void *a, + const void *b) +{ + const FontFaceSortInfo *face_a = a; + const FontFaceSortInfo *face_b = b; - pango_font_description_free (desc_a); - pango_font_description_free (desc_b); - - return ord; + return compare_font_descriptions (face_a->desc, + face_b->desc); } static gboolean @@ -929,6 +957,7 @@ gtk_font_selection_show_available_styles (GtkFontSelection *fontsel) { gint n_faces, i; PangoFontFace **faces; + FontFaceSortInfo *sorted_faces; PangoFontDescription *old_desc; GtkListStore *model; GtkTreeIter match_row; @@ -942,38 +971,40 @@ gtk_font_selection_show_available_styles (GtkFontSelection *fontsel) old_desc= NULL; pango_font_family_list_faces (fontsel->family, &faces, &n_faces); - qsort (faces, n_faces, sizeof (PangoFontFace *), faces_sort_func); + + sorted_faces = g_new (FontFaceSortInfo, n_faces); + for (i = 0; i < n_faces; i++) + { + sorted_faces[i].face = faces[i]; + sorted_faces[i].desc = pango_font_face_describe (faces[i]); + sorted_faces[i].name = pango_font_face_get_face_name (faces[i]); + } + + qsort (sorted_faces, n_faces, sizeof (FontFaceSortInfo), faces_sort_func); gtk_list_store_clear (model); - for (i=0; i < n_faces; i++) + for (i = 0; i < n_faces; i++) { GtkTreeIter iter; - const gchar *str = pango_font_face_get_face_name (faces[i]); + const gchar *str = sorted_faces[i].name; - gtk_list_store_append (model, &iter); - gtk_list_store_set (model, &iter, - FACE_COLUMN, faces[i], - FACE_NAME_COLUMN, _gtk_font_style_name_translate (str), - -1); + gtk_list_store_insert_with_values (model, &iter, -1, + FACE_COLUMN, sorted_faces[i].face, + FACE_NAME_COLUMN, _gtk_font_style_name_translate (str), + -1); if (i == 0) - { - match_row = iter; - match_face = faces[i]; - } - else if (old_desc) - { - PangoFontDescription *tmp_desc = pango_font_face_describe (faces[i]); - - if (font_description_style_equal (tmp_desc, old_desc)) - { - match_row = iter; - match_face = faces[i]; - } - - pango_font_description_free (tmp_desc); - } + { + match_row = iter; + match_face = sorted_faces[i].face; + } + else if (old_desc && + font_description_style_equal (sorted_faces[i].desc, old_desc)) + { + match_row = iter; + match_face = sorted_faces[i].face; + } } if (old_desc) @@ -990,6 +1021,10 @@ gtk_font_selection_show_available_styles (GtkFontSelection *fontsel) set_cursor_to_iter (GTK_TREE_VIEW (fontsel->face_list), &match_row); } + for (i = 0; i < n_faces; i++) + pango_font_description_free (sorted_faces[i].desc); + + g_free (sorted_faces); g_free (faces); } @@ -1060,8 +1095,9 @@ gtk_font_selection_show_available_sizes (GtkFontSelection *fontsel, { GtkTreeIter iter; - gtk_list_store_append (model, &iter); - gtk_list_store_set (model, &iter, SIZE_COLUMN, font_sizes[i], -1); + gtk_list_store_insert_with_values (model, &iter, -1, + SIZE_COLUMN, font_sizes[i], + -1); if (font_sizes[i] * PANGO_SCALE == fontsel->size) set_cursor_to_iter (GTK_TREE_VIEW (fontsel->size_list), &iter); From 6ad2ee66d9992896e997e05d29306db19110e5ee Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Thu, 13 Aug 2026 00:06:32 +0200 Subject: [PATCH 42/56] gtkfilesystemmodel: avoid sorting newly queried files twice Remember the number of model nodes before updating asynchronously queried file information. If update_file added a previously unknown file, add_file already sorted the model, so skip the redundant second full sort. Existing files still trigger the required resort after their metadata changes. Using the node count also avoids the additional node lookup required by the original warning-cleanups implementation. --- gtk/gtkfilesystemmodel.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gtk/gtkfilesystemmodel.c b/gtk/gtkfilesystemmodel.c index 52fd17a08..4c619c2a3 100644 --- a/gtk/gtkfilesystemmodel.c +++ b/gtk/gtkfilesystemmodel.c @@ -1230,7 +1230,7 @@ gtk_file_system_model_query_done (GObject * object, GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */ GFile *file = G_FILE (object); GFileInfo *info; - guint id; + guint n_files; info = g_file_query_info_finish (file, res, NULL); if (info == NULL) @@ -1238,10 +1238,12 @@ gtk_file_system_model_query_done (GObject * object, gdk_threads_enter (); + n_files = model->files->len; + _gtk_file_system_model_update_file (model, file, info); - id = node_get_for_file (model, file); - gtk_file_system_model_sort_node (model, id); + if (model->files->len == n_files) + gtk_file_system_model_sort (model); g_object_unref (info); From 240b7d0a71d887dff2fb03515255cc272fcfa2e4 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Thu, 13 Aug 2026 00:11:57 +0200 Subject: [PATCH 43/56] gail: avoid formatting string attribute values Duplicate Pango string attribute values directly instead of routing an unchanged string through g_strdup_printf. This preserves the allocated result while avoiding unnecessary format parsing and varargs handling. --- modules/other/gail/libgail-util/gailmisc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/other/gail/libgail-util/gailmisc.c b/modules/other/gail/libgail-util/gailmisc.c index 9dc143d07..8213c716a 100644 --- a/modules/other/gail/libgail-util/gailmisc.c +++ b/modules/other/gail/libgail-util/gailmisc.c @@ -251,7 +251,7 @@ gail_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set, if ((pango_string = (PangoAttrString*) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY)) != NULL) { - value = g_strdup_printf("%s", pango_string->value); + value = g_strdup (pango_string->value); attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_FAMILY_NAME, value); From 6d7bb4c86afb6f2363da3604a2d1544fc77092e7 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Thu, 13 Aug 2026 00:55:35 +0200 Subject: [PATCH 44/56] gtkcalendar: preserve strftime calendar names Format weekday and month names with strftime() and convert the locale-encoded results to UTF-8 instead of creating temporary GDateTime objects during calendar initialization. Keep the corrected Sunday-to-Saturday weekday ordering. If %OB is unsupported, immediately reformat January with %B so the fallback also preserves the first month. --- gtk/gtkcalendar.c | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/gtk/gtkcalendar.c b/gtk/gtkcalendar.c index 2e453db04..e6f6be63f 100644 --- a/gtk/gtkcalendar.c +++ b/gtk/gtkcalendar.c @@ -688,8 +688,7 @@ gtk_calendar_init (GtkCalendar *calendar) #else static const char *month_format = NULL; char buffer[255]; - GDateTime *tmp_dt; - gchar *tmp_str; + time_t tmp_time; #endif GtkCalendarPrivate *priv; gchar *year_before; @@ -710,15 +709,9 @@ gtk_calendar_init (GtkCalendar *calendar) for (i=0; i<7; i++) { #ifndef G_OS_WIN32 - tmp_dt = g_date_time_new_utc (2001, 1, i + 7, 12, 0, 0.0); - if (tmp_dt) - { - tmp_str = g_date_time_format (tmp_dt, "%a"); - default_abbreviated_dayname[i] = tmp_str ? tmp_str : g_strdup (""); - g_date_time_unref (tmp_dt); - } - else - default_abbreviated_dayname[i] = g_strdup (""); + tmp_time = (i + 3) * 86400; + strftime (buffer, sizeof (buffer), "%a", gmtime (&tmp_time)); + default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL); #else if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SABBREVDAYNAME1 + (i+6)%7, wbuffer, G_N_ELEMENTS (wbuffer))) @@ -732,30 +725,24 @@ gtk_calendar_init (GtkCalendar *calendar) for (i=0; i<12; i++) { #ifndef G_OS_WIN32 - tmp_dt = g_date_time_new_utc (2001, i + 1, 1, 12, 0, 0.0); + tmp_time = i * 2764800; if (G_UNLIKELY (month_format == NULL)) { buffer[0] = '\0'; month_format = "%OB"; - tmp_str = tmp_dt ? g_date_time_format (tmp_dt, month_format) : NULL; - if (tmp_str) - { - g_strlcpy (buffer, tmp_str, sizeof (buffer)); - g_free (tmp_str); - } + strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time)); + /* "%OB" is not supported in Linux with glibc < 2.27 */ if (!strcmp (buffer, "%OB") || !strcmp (buffer, "OB") || !strcmp (buffer, "")) - month_format = "%B"; - } - - if (tmp_dt) - { - tmp_str = g_date_time_format (tmp_dt, month_format); - default_monthname[i] = tmp_str ? tmp_str : g_strdup (""); - g_date_time_unref (tmp_dt); + { + month_format = "%B"; + strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time)); + } } else - default_monthname[i] = g_strdup (""); + strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time)); + + default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL); #else if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SMONTHNAME1 + i, wbuffer, G_N_ELEMENTS (wbuffer))) From d5b799435ae436a3b9e972748f6e113bd97a85af Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Thu, 13 Aug 2026 11:15:26 +0200 Subject: [PATCH 45/56] gtkfilechooser: select clicked file before popup menu --- gtk/gtkfilechooserdefault.c | 58 +++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index fabf5913e..d1313202f 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -4523,10 +4523,11 @@ list_popup_menu_cb (GtkWidget *widget, */ static gboolean list_button_press_event_cb (GtkWidget *widget, - GdkEventButton *event, - GtkFileChooserDefault *impl) + GdkEventButton *event, + GtkFileChooserDefault *impl) { static gboolean in_press = FALSE; + GtkTreePath *path = NULL; if (in_press) return FALSE; @@ -4534,9 +4535,56 @@ list_button_press_event_cb (GtkWidget *widget, if (!_gtk_button_event_triggers_context_menu (event)) return FALSE; - in_press = TRUE; - gtk_widget_event (widget, (GdkEvent *) event); - in_press = FALSE; + if (widget == impl->browse_files_tree_view) + { + GtkTreeView *tree_view; + GtkTreeSelection *selection; + + tree_view = GTK_TREE_VIEW (widget); + + if (gtk_tree_view_get_path_at_pos (tree_view, + (gint) event->x, + (gint) event->y, + &path, NULL, NULL, NULL)) + { + selection = gtk_tree_view_get_selection (tree_view); + + if (!gtk_tree_selection_path_is_selected (selection, path)) + { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + } + } + } + else + { + GtkIconView *icon_view; + + icon_view = GTK_ICON_VIEW (widget); + path = gtk_icon_view_get_path_at_pos (icon_view, + (gint) event->x, + (gint) event->y); + + if (path != NULL) + { + if (!gtk_icon_view_path_is_selected (icon_view, path)) + { + gtk_icon_view_unselect_all (icon_view); + gtk_icon_view_select_path (icon_view, path); + } + + gtk_icon_view_set_cursor (icon_view, path, NULL, FALSE); + } + } + + if (path != NULL) + gtk_tree_path_free (path); + else + { + in_press = TRUE; + gtk_widget_event (widget, (GdkEvent *) event); + in_press = FALSE; + } file_list_popup_menu (impl, event); return TRUE; From 54adbe66fbd9e0577140bc05830b6277e6dbcaa0 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Thu, 13 Aug 2026 11:52:53 +0200 Subject: [PATCH 46/56] gtkfilechooser: add copy location menu item --- gtk/gtkfilechooserdefault.c | 71 +++++++++++++++++++++++++++++++++++++ gtk/gtkfilechooserprivate.h | 1 + 2 files changed, 72 insertions(+) diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index d1313202f..41602f824 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -3938,6 +3938,7 @@ popup_menu_detach_cb (GtkWidget *attach_widget, impl->browse_files_popup_menu = NULL; impl->browse_files_popup_menu_add_shortcut_item = NULL; + impl->browse_files_popup_menu_copy_location_item = NULL; impl->browse_files_popup_menu_hidden_files_item = NULL; } @@ -3949,6 +3950,63 @@ add_to_shortcuts_cb (GtkMenuItem *item, bookmarks_add_selected_folder (impl); } +static void +copy_location_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GString *locations = data; + GFile *file; + gchar *location; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + -1); + + if (file == NULL) + return; + + location = g_file_get_path (file); + + if (location == NULL) + location = g_file_get_uri (file); + + if (location != NULL) + { + if (locations->len != 0) + g_string_append_c (locations, '\n'); + + g_string_append (locations, location); + g_free (location); + } + + g_object_unref (file); +} + +static void +copy_location_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + GString *locations; + + locations = g_string_new (NULL); + + current_selection_selected_foreach (impl, + copy_location_foreach_cb, + locations); + + if (locations->len != 0) + gtk_clipboard_set_text ( + gtk_widget_get_clipboard (impl->browse_files_current_view, + GDK_SELECTION_CLIPBOARD), + locations->str, + -1); + + g_string_free (locations, TRUE); +} + + /* Callback used when the "Show Hidden Files" menu item is toggled */ static void show_hidden_toggled_cb (GtkCheckMenuItem *item, @@ -4298,6 +4356,16 @@ file_list_build_popup_menu (GtkFileChooserDefault *impl) gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + item = gtk_image_menu_item_new_with_mnemonic (_("_Copy Location")); + impl->browse_files_popup_menu_copy_location_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, + GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (copy_location_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + item = gtk_separator_menu_item_new (); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); @@ -4391,6 +4459,9 @@ file_list_update_popup_menu (GtkFileChooserDefault *impl) * bookmarks_check_add_sensitivity() */ + gtk_widget_set_sensitive (impl->browse_files_popup_menu_copy_location_item, + current_selection_count_selected_rows (impl) > 0); + /* 'Show Hidden Files' */ g_signal_handlers_block_by_func (impl->browse_files_popup_menu_hidden_files_item, G_CALLBACK (show_hidden_toggled_cb), impl); diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index 6e3456bc6..56cc5db62 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -182,6 +182,7 @@ struct _GtkFileChooserDefault GtkWidget *browse_files_icon_view; GtkWidget *browse_files_popup_menu; GtkWidget *browse_files_popup_menu_add_shortcut_item; + GtkWidget *browse_files_popup_menu_copy_location_item; GtkWidget *browse_files_popup_menu_hidden_files_item; GtkWidget *browse_files_popup_menu_size_column_item; GtkWidget *browse_files_popup_menu_sort_by_name_item; From d6bc937777132bfe0afa246782d92f68d2e4e338 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Sun, 16 Aug 2026 21:06:44 +0200 Subject: [PATCH 47/56] gtk: return text btree string buffer directly --- gtk/gtktextbtree.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gtk/gtktextbtree.c b/gtk/gtktextbtree.c index 2cd097800..ce2bd1973 100644 --- a/gtk/gtktextbtree.c +++ b/gtk/gtktextbtree.c @@ -2407,7 +2407,6 @@ _gtk_text_btree_get_text (const GtkTextIter *start_orig, GtkTextLineSegment *seg; GtkTextLineSegment *end_seg; GString *retval; - gchar *str; GtkTextIter iter; GtkTextIter start; GtkTextIter end; @@ -2439,9 +2438,7 @@ _gtk_text_btree_get_text (const GtkTextIter *start_orig, copy_segment (retval, include_hidden, include_nonchars, &iter, &end); - str = retval->str; - g_string_free (retval, FALSE); - return str; + return g_string_free (retval, FALSE); } gint From 9e500eecd6b4a55affba3466ebbf040e95caf352 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Sun, 16 Aug 2026 21:33:48 +0200 Subject: [PATCH 48/56] gtk,gail: use component extents for accessible geometry --- gtk/gtkiconview.c | 4 ++-- modules/other/gail/gailbutton.c | 2 +- modules/other/gail/gailimage.c | 2 +- modules/other/gail/gailimagecell.c | 2 +- modules/other/gail/gailnotebookpage.c | 2 +- modules/other/gail/gailpixmap.c | 2 +- modules/other/gail/tests/ferret.c | 7 ++++--- modules/other/gail/tests/testcomponent.c | 22 ++++------------------ 8 files changed, 15 insertions(+), 28 deletions(-) diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c index 618f7132d..5b58cc9c8 100644 --- a/gtk/gtkiconview.c +++ b/gtk/gtkiconview.c @@ -7917,7 +7917,7 @@ gtk_icon_view_item_accessible_image_get_image_position (AtkImage *image, if (atk_state_set_contains_state (item->state_set, ATK_STATE_DEFUNCT)) return; - atk_component_get_position (ATK_COMPONENT (image), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type); if (get_pixbuf_box (GTK_ICON_VIEW (item->widget), item->item, &box)) { @@ -8601,7 +8601,7 @@ gtk_icon_view_item_accessible_get_extents (AtkComponent *component, if (gtk_icon_view_item_accessible_is_showing (item)) { parent_obj = gtk_widget_get_accessible (item->widget); - atk_component_get_position (ATK_COMPONENT (parent_obj), &l_x, &l_y, coord_type); + atk_component_get_extents (ATK_COMPONENT (parent_obj), &l_x, &l_y, NULL, NULL, coord_type); *x = l_x + item->item->x; *y = l_y + item->item->y; } diff --git a/modules/other/gail/gailbutton.c b/modules/other/gail/gailbutton.c index 6ecf9cd74..495efce1b 100644 --- a/modules/other/gail/gailbutton.c +++ b/modules/other/gail/gailbutton.c @@ -1021,7 +1021,7 @@ gail_button_get_image_position (AtkImage *image, if (button_image != NULL) { obj = gtk_widget_get_accessible (GTK_WIDGET (button_image)); - atk_component_get_position (ATK_COMPONENT (obj), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (obj), x, y, NULL, NULL, coord_type); } else { diff --git a/modules/other/gail/gailimage.c b/modules/other/gail/gailimage.c index 0a33cd1ec..968b1d464 100644 --- a/modules/other/gail/gailimage.c +++ b/modules/other/gail/gailimage.c @@ -179,7 +179,7 @@ gail_image_get_image_position (AtkImage *image, gint *y, AtkCoordType coord_type) { - atk_component_get_position (ATK_COMPONENT (image), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type); } static void diff --git a/modules/other/gail/gailimagecell.c b/modules/other/gail/gailimagecell.c index efcded036..5860b723f 100644 --- a/modules/other/gail/gailimagecell.c +++ b/modules/other/gail/gailimagecell.c @@ -150,7 +150,7 @@ gail_image_cell_get_image_position (AtkImage *image, gint *y, AtkCoordType coord_type) { - atk_component_get_position (ATK_COMPONENT (image), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type); } static void diff --git a/modules/other/gail/gailnotebookpage.c b/modules/other/gail/gailnotebookpage.c index 444ddac0e..cd4e18d7d 100644 --- a/modules/other/gail/gailnotebookpage.c +++ b/modules/other/gail/gailnotebookpage.c @@ -467,7 +467,7 @@ gail_notebook_page_get_extents (AtkComponent *component, child = atk_object_ref_accessible_child (ATK_OBJECT (component), 0); gail_return_if_fail (child); - atk_component_get_position (ATK_COMPONENT (child), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (child), x, y, NULL, NULL, coord_type); g_object_unref (child); } else diff --git a/modules/other/gail/gailpixmap.c b/modules/other/gail/gailpixmap.c index b8ab4b3da..4f44f93be 100644 --- a/modules/other/gail/gailpixmap.c +++ b/modules/other/gail/gailpixmap.c @@ -103,7 +103,7 @@ gail_pixmap_get_image_position (AtkImage *obj, gint *y, AtkCoordType coord_type) { - atk_component_get_position (ATK_COMPONENT (obj), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (obj), x, y, NULL, NULL, coord_type); } static void diff --git a/modules/other/gail/tests/ferret.c b/modules/other/gail/tests/ferret.c index 39eaef441..e75c33d13 100644 --- a/modules/other/gail/tests/ferret.c +++ b/modules/other/gail/tests/ferret.c @@ -1514,9 +1514,10 @@ _button_watcher (GSignalInvocationHint *ihint, event = (GdkEventButton *) gp; aobject = gtk_widget_get_accessible (widget); aobject_x = aobject_y = 0; - atk_component_get_position (ATK_COMPONENT (aobject), - &aobject_x, &aobject_y, - ATK_XY_WINDOW); + atk_component_get_extents (ATK_COMPONENT (aobject), + &aobject_x, &aobject_y, + NULL, NULL, + ATK_XY_WINDOW); x = aobject_x + (gint) event->x; y = aobject_y + (gint) event->y; child = atk_component_ref_accessible_at_point (ATK_COMPONENT (aobject), diff --git a/modules/other/gail/tests/testcomponent.c b/modules/other/gail/tests/testcomponent.c index a28eccebd..45880a207 100644 --- a/modules/other/gail/tests/testcomponent.c +++ b/modules/other/gail/tests/testcomponent.c @@ -7,31 +7,17 @@ static void _check_position (AtkObject *obj) AtkObject *parent, *ret_object; gint x, y, width, height; - gint x1, y1, width1, height1; x = y = width = height = 0; - x1 = y1 = width1 = height1 = 0; if (!ATK_IS_COMPONENT (obj)) return; - atk_component_get_extents (ATK_COMPONENT(obj), &x, &y, &width, &height, ATK_XY_SCREEN); - atk_component_get_position (ATK_COMPONENT(obj), &x1, &y1, ATK_XY_SCREEN ); - atk_component_get_size (ATK_COMPONENT(obj), &width1, &height1); - if ((x1 != x) || (y1 != y)) - { - g_print ("atk_component_get_extents and atk_get_position give different" - " values: %d,%d %d,%d\n", x, y, x1, y1); - } - if ((width1 != width) || (height1 != height)) - { - g_print ("atk_component_get_extents and atk_get_size give different" - " values: %d,%d %d,%d\n", width, height, width1, height1); - } - - atk_component_get_position (ATK_COMPONENT(obj), &x1, &y1, ATK_XY_SCREEN); + atk_component_get_extents (ATK_COMPONENT(obj), + &x, &y, &width, &height, + ATK_XY_SCREEN); g_print ("Object Type: %s\n", g_type_name (G_OBJECT_TYPE (obj))); - g_print ("Object at %d, %d on screen\n", x1, y1); + g_print ("Object at %d, %d on screen\n", x, y); g_print ("Object at %d, %d, size: %d, %d\n", x, y, width, height); parent = atk_object_get_parent (obj); From 1a2c01d459d26c1297dc9427e547408ddc9dd1bf Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 17 Aug 2026 09:08:36 +0200 Subject: [PATCH 49/56] gail: bypass deprecated focus handler wrapper --- modules/other/gail/gailwidget.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/other/gail/gailwidget.c b/modules/other/gail/gailwidget.c index f9e82649c..703377c49 100644 --- a/modules/other/gail/gailwidget.c +++ b/modules/other/gail/gailwidget.c @@ -169,8 +169,8 @@ gail_widget_real_initialize (AtkObject *obj, "size_allocate", G_CALLBACK (gail_widget_size_allocate_gtk), NULL); - atk_component_add_focus_handler (ATK_COMPONENT (accessible), - gail_widget_focus_event); + gail_widget_add_focus_handler (ATK_COMPONENT (accessible), + gail_widget_focus_event); /* * Add signal handlers for GTK signals required to support property changes */ From 55ce116115a59136c38fe2497ea17e60dbd18cdc Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 17 Aug 2026 09:28:03 +0200 Subject: [PATCH 50/56] gtk,gail: preserve legacy ATK focus tracking --- gtk/gtkiconview.c | 2 ++ modules/other/gail/gail.c | 10 ++++++++-- modules/other/gail/gailnotebook.c | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c index 5b58cc9c8..acd2e6f49 100644 --- a/gtk/gtkiconview.c +++ b/gtk/gtkiconview.c @@ -3450,7 +3450,9 @@ gtk_icon_view_set_cursor_item (GtkIconView *icon_view, if (item_obj != NULL) { + G_GNUC_BEGIN_IGNORE_DEPRECATIONS atk_focus_tracker_notify (item_obj); + G_GNUC_END_IGNORE_DEPRECATIONS atk_object_notify_state_change (item_obj, ATK_STATE_FOCUSED, TRUE); g_object_unref (item_obj); } diff --git a/modules/other/gail/gail.c b/modules/other/gail/gail.c index 3669a99ce..619fbf12f 100644 --- a/modules/other/gail/gail.c +++ b/modules/other/gail/gail.c @@ -587,9 +587,11 @@ gail_focus_notify (GtkWidget *widget) /* * Do not report focus on redundant object */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS if (atk_obj && - (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT)) - atk_focus_tracker_notify (atk_obj); + (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT)) + atk_focus_tracker_notify (atk_obj); + G_GNUC_END_IGNORE_DEPRECATIONS if (atk_obj && transient) g_object_unref (atk_obj); if (subsequent_focus_widget) @@ -938,8 +940,10 @@ gail_accessibility_module_init (void) /* LIBGNOMECANVAS SUPPORT */ GAIL_WIDGET_SET_FACTORY (GTK_TYPE_OBJECT, gail_object); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS atk_focus_tracker_init (gail_focus_tracker_init); focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker); + G_GNUC_END_IGNORE_DEPRECATIONS /* Initialize the GailUtility class */ g_type_class_unref (g_type_class_ref (GAIL_TYPE_UTIL)); @@ -974,7 +978,9 @@ gnome_accessibility_module_shutdown (void) return; } gail_initialized = FALSE; + G_GNUC_BEGIN_IGNORE_DEPRECATIONS atk_remove_focus_tracker (focus_tracker_id); + G_GNUC_END_IGNORE_DEPRECATIONS fprintf (stderr, "GTK Accessibility Module shutdown\n"); diff --git a/modules/other/gail/gailnotebook.c b/modules/other/gail/gailnotebook.c index 795af606b..f937abd0e 100644 --- a/modules/other/gail/gailnotebook.c +++ b/modules/other/gail/gailnotebook.c @@ -583,7 +583,9 @@ gail_notebook_check_focus_tab (gpointer data) AtkObject *obj; obj = atk_object_ref_accessible_child (atk_obj, focus_page_num); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS atk_focus_tracker_notify (obj); + G_GNUC_END_IGNORE_DEPRECATIONS g_object_unref (obj); } From 99e3e78c897f3491a1c7afaa9cc853c6f9fba781 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 17 Aug 2026 09:54:30 +0200 Subject: [PATCH 51/56] gail: preserve legacy ATK focus tracking in tests --- modules/other/gail/tests/ferret.c | 6 +++--- modules/other/gail/tests/testaction.c | 2 +- modules/other/gail/tests/testbutton.c | 2 +- modules/other/gail/tests/testcombo.c | 2 +- modules/other/gail/tests/testcomponent.c | 3 ++- modules/other/gail/tests/testimage.c | 2 +- modules/other/gail/tests/testlib.h | 7 +++++++ modules/other/gail/tests/testmenuitem.c | 2 +- modules/other/gail/tests/testnotebook.c | 2 +- modules/other/gail/tests/testobject.c | 6 +++--- modules/other/gail/tests/testoptionmenu.c | 2 +- modules/other/gail/tests/testpaned.c | 2 +- modules/other/gail/tests/testprops.c | 2 +- modules/other/gail/tests/testselection.c | 3 ++- modules/other/gail/tests/teststatusbar.c | 3 ++- modules/other/gail/tests/testtable.c | 2 +- modules/other/gail/tests/testtext.c | 4 ++-- modules/other/gail/tests/testtoplevel.c | 2 +- modules/other/gail/tests/testtreetable.c | 2 +- modules/other/gail/tests/testvalues.c | 3 ++- 20 files changed, 35 insertions(+), 24 deletions(-) diff --git a/modules/other/gail/tests/ferret.c b/modules/other/gail/tests/ferret.c index e75c33d13..43f9d4bf1 100644 --- a/modules/other/gail/tests/ferret.c +++ b/modules/other/gail/tests/ferret.c @@ -1447,7 +1447,7 @@ _print_value_type(gint group_num, gchar *type, GValue *value) static void _create_event_watcher (void) { - focus_tracker_id = atk_add_focus_tracker (_print_accessible); + GAIL_TEST_LEGACY_FOCUS_CALL (focus_tracker_id = atk_add_focus_tracker (_print_accessible)); if (track_mouse) { @@ -2689,12 +2689,12 @@ void _toggle_trackfocus(GtkCheckMenuItem *checkmenuitem, if (checkmenuitem->active) { track_focus = TRUE; - focus_tracker_id = atk_add_focus_tracker (_print_accessible); + GAIL_TEST_LEGACY_FOCUS_CALL (focus_tracker_id = atk_add_focus_tracker (_print_accessible)); } else { g_print ("No longer tracking focus.\n"); track_focus = FALSE; - atk_remove_focus_tracker (focus_tracker_id); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_remove_focus_tracker (focus_tracker_id)); } } diff --git a/modules/other/gail/tests/testaction.c b/modules/other/gail/tests/testaction.c index 9d83d1c9e..f1ccb701a 100644 --- a/modules/other/gail/tests/testaction.c +++ b/modules/other/gail/tests/testaction.c @@ -73,7 +73,7 @@ _check_object (AtkObject *obj) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_object); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_object)); } int diff --git a/modules/other/gail/tests/testbutton.c b/modules/other/gail/tests/testbutton.c index b039e89fa..24a05fa21 100644 --- a/modules/other/gail/tests/testbutton.c +++ b/modules/other/gail/tests/testbutton.c @@ -192,7 +192,7 @@ _print_button_image_info(AtkObject *obj) { static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_object); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_object)); } int diff --git a/modules/other/gail/tests/testcombo.c b/modules/other/gail/tests/testcombo.c index 1602bf9ce..f39c3555f 100644 --- a/modules/other/gail/tests/testcombo.c +++ b/modules/other/gail/tests/testcombo.c @@ -174,7 +174,7 @@ static gint _close_combo_list (gpointer data) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_combo_box); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_combo_box)); } int diff --git a/modules/other/gail/tests/testcomponent.c b/modules/other/gail/tests/testcomponent.c index 45880a207..086c4e4db 100644 --- a/modules/other/gail/tests/testcomponent.c +++ b/modules/other/gail/tests/testcomponent.c @@ -1,4 +1,5 @@ #include +#include "testlib.h" static void _check_position (AtkObject *obj); @@ -93,7 +94,7 @@ static void _check_position (AtkObject *obj) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_position); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_position)); } int diff --git a/modules/other/gail/tests/testimage.c b/modules/other/gail/tests/testimage.c index b27e86e5b..e358cd69d 100644 --- a/modules/other/gail/tests/testimage.c +++ b/modules/other/gail/tests/testimage.c @@ -142,7 +142,7 @@ static void _check_objects (AtkObject *obj) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_objects); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_objects)); } int diff --git a/modules/other/gail/tests/testlib.h b/modules/other/gail/tests/testlib.h index e12fdefd5..10c429f28 100644 --- a/modules/other/gail/tests/testlib.h +++ b/modules/other/gail/tests/testlib.h @@ -68,3 +68,10 @@ gchar *get_arg_of_func (gint window, int string_to_int (const char *the_string); gboolean isVisibleDialog (void); + +/* Legacy ATK focus tracking is distinct from state-change::focused. */ +#define GAIL_TEST_LEGACY_FOCUS_CALL(call) G_STMT_START { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + call; \ + G_GNUC_END_IGNORE_DEPRECATIONS \ +} G_STMT_END diff --git a/modules/other/gail/tests/testmenuitem.c b/modules/other/gail/tests/testmenuitem.c index c27505b39..2c9aade8b 100644 --- a/modules/other/gail/tests/testmenuitem.c +++ b/modules/other/gail/tests/testmenuitem.c @@ -124,7 +124,7 @@ static gint _do_menu_item_action (gpointer data) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_object); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_object)); } int diff --git a/modules/other/gail/tests/testnotebook.c b/modules/other/gail/tests/testnotebook.c index fdc663ad8..a8e1d8d9f 100644 --- a/modules/other/gail/tests/testnotebook.c +++ b/modules/other/gail/tests/testnotebook.c @@ -211,7 +211,7 @@ static gint _finish_selection (gpointer data) static void _create_event_watcher (void) { - atk_add_focus_tracker (_do_selection); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_do_selection)); } int diff --git a/modules/other/gail/tests/testobject.c b/modules/other/gail/tests/testobject.c index c11343a84..25f435aa5 100644 --- a/modules/other/gail/tests/testobject.c +++ b/modules/other/gail/tests/testobject.c @@ -163,7 +163,7 @@ static void _print_accessible (AtkObject *obj) * Add a focus handler so we see focus out events as well */ if (ATK_IS_COMPONENT (obj)) - atk_component_add_focus_handler (ATK_COMPONENT (obj), _focus_handler); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_component_add_focus_handler (ATK_COMPONENT (obj), _focus_handler)); g_print ("Object:\n"); _print_type (obj); _print_states (obj); @@ -229,7 +229,7 @@ static void _check_children (AtkObject *obj) if (ATK_IS_COMPONENT (obj)) { - atk_component_add_focus_handler (ATK_COMPONENT (obj), _focus_handler); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_component_add_focus_handler (ATK_COMPONENT (obj), _focus_handler)); layer = atk_component_get_layer (ATK_COMPONENT (obj)); if (role == ATK_ROLE_MENU) g_assert (layer == ATK_LAYER_POPUP); @@ -318,7 +318,7 @@ _create_event_watcher (void) * _print_accessible() will be called for an accessible object when its * widget receives focus. */ - id = atk_add_focus_tracker (_print_accessible); + GAIL_TEST_LEGACY_FOCUS_CALL (id = atk_add_focus_tracker (_print_accessible)); } static void diff --git a/modules/other/gail/tests/testoptionmenu.c b/modules/other/gail/tests/testoptionmenu.c index 01dc64410..7bc13d339 100644 --- a/modules/other/gail/tests/testoptionmenu.c +++ b/modules/other/gail/tests/testoptionmenu.c @@ -143,7 +143,7 @@ static gint _do_menu_item_action (gpointer data) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_object); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_object)); } int diff --git a/modules/other/gail/tests/testpaned.c b/modules/other/gail/tests/testpaned.c index 7e1f27f1c..8ae9e2daa 100644 --- a/modules/other/gail/tests/testpaned.c +++ b/modules/other/gail/tests/testpaned.c @@ -117,7 +117,7 @@ static void _check_paned (AtkObject *obj) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_paned); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_paned)); } int diff --git a/modules/other/gail/tests/testprops.c b/modules/other/gail/tests/testprops.c index 4c8e1745e..7477b4763 100644 --- a/modules/other/gail/tests/testprops.c +++ b/modules/other/gail/tests/testprops.c @@ -206,7 +206,7 @@ _check_properties (AtkObject *obj) static void _create_event_watcher (void) { - id = atk_add_focus_tracker (_check_properties); + GAIL_TEST_LEGACY_FOCUS_CALL (id = atk_add_focus_tracker (_check_properties)); } int diff --git a/modules/other/gail/tests/testselection.c b/modules/other/gail/tests/testselection.c index bf0274b82..89cb6b0e6 100644 --- a/modules/other/gail/tests/testselection.c +++ b/modules/other/gail/tests/testselection.c @@ -1,6 +1,7 @@ #include #include #include +#include "testlib.h" /* * This module tests the selection interface on menu items. @@ -184,7 +185,7 @@ static gint _finish_selection (gpointer data) static void _create_event_watcher (void) { - atk_add_focus_tracker (_do_selection); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_do_selection)); } int diff --git a/modules/other/gail/tests/teststatusbar.c b/modules/other/gail/tests/teststatusbar.c index 5e38279c7..0223af593 100644 --- a/modules/other/gail/tests/teststatusbar.c +++ b/modules/other/gail/tests/teststatusbar.c @@ -1,6 +1,7 @@ #include #include #include +#include "testlib.h" /* * To use this test module, run the test program testgtk and click on @@ -113,7 +114,7 @@ _notify_handler (GObject *obj, GParamSpec *pspec) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_statusbar); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_statusbar)); } int diff --git a/modules/other/gail/tests/testtable.c b/modules/other/gail/tests/testtable.c index 29f1d64e2..3fd1b3dc7 100644 --- a/modules/other/gail/tests/testtable.c +++ b/modules/other/gail/tests/testtable.c @@ -533,7 +533,7 @@ _selection_tests(AtkObject *obj) static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_table); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_table)); } int diff --git a/modules/other/gail/tests/testtext.c b/modules/other/gail/tests/testtext.c index 7a0daf0c8..bc5c56707 100644 --- a/modules/other/gail/tests/testtext.c +++ b/modules/other/gail/tests/testtext.c @@ -113,13 +113,13 @@ static void _check_text (AtkObject *in_obj) if (!(isVisibleDialog())) setup_gui(obj, runtest); - atk_remove_focus_tracker (id1); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_remove_focus_tracker (id1)); } static void _create_event_watcher (void) { - id1 = atk_add_focus_tracker (_check_text); + GAIL_TEST_LEGACY_FOCUS_CALL (id1 = atk_add_focus_tracker (_check_text)); } int diff --git a/modules/other/gail/tests/testtoplevel.c b/modules/other/gail/tests/testtoplevel.c index 63addd065..024e35583 100644 --- a/modules/other/gail/tests/testtoplevel.c +++ b/modules/other/gail/tests/testtoplevel.c @@ -62,7 +62,7 @@ _check_toplevel (AtkObject *obj) static void _create_event_watcher (void) { - id = atk_add_focus_tracker (_check_toplevel); + GAIL_TEST_LEGACY_FOCUS_CALL (id = atk_add_focus_tracker (_check_toplevel)); } int diff --git a/modules/other/gail/tests/testtreetable.c b/modules/other/gail/tests/testtreetable.c index a5d606e41..0fa2d1046 100644 --- a/modules/other/gail/tests/testtreetable.c +++ b/modules/other/gail/tests/testtreetable.c @@ -342,7 +342,7 @@ _check_expanders (AtkTable *table, static void _create_event_watcher (void) { - atk_add_focus_tracker (_check_table); + GAIL_TEST_LEGACY_FOCUS_CALL (atk_add_focus_tracker (_check_table)); } int diff --git a/modules/other/gail/tests/testvalues.c b/modules/other/gail/tests/testvalues.c index 37155855c..d2331d73c 100644 --- a/modules/other/gail/tests/testvalues.c +++ b/modules/other/gail/tests/testvalues.c @@ -2,6 +2,7 @@ #include #include #include "testvalue.h" +#include "testlib.h" static void _traverse_children (AtkObject *obj); static void _add_handler (AtkObject *obj); @@ -181,7 +182,7 @@ static void _check_values (AtkObject *obj) static void _create_event_watcher (void) { - id = atk_add_focus_tracker (_check_values); + GAIL_TEST_LEGACY_FOCUS_CALL (id = atk_add_focus_tracker (_check_values)); } int From 011405e808deb6d71912bf1778ef72f0f0acc9bb Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 17 Aug 2026 19:59:37 +0200 Subject: [PATCH 52/56] gtk: limit direct module paths to GTK_PATH --- gtk/gtkmodules.c | 67 ++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/gtk/gtkmodules.c b/gtk/gtkmodules.c index a325b3e56..efde1eeb4 100644 --- a/gtk/gtkmodules.c +++ b/gtk/gtkmodules.c @@ -123,49 +123,44 @@ split_file_list (const gchar *str) } static gchar ** -get_module_path (void) +get_module_path (gint *env_dirs) { const gchar *module_path_env; const gchar *exe_prefix; const gchar *home_dir; - gchar *home_gtk_dir = NULL; - gchar *module_path; - gchar *default_dir; + gchar **path; + gint count; static gchar **result = NULL; + static gint n_env_dirs = 0; if (result) - return result; - - home_dir = g_get_home_dir(); - if (home_dir) - home_gtk_dir = g_build_filename (home_dir, ".gtk-2.0", NULL); + { + *env_dirs = n_env_dirs; + return result; + } + home_dir = g_get_home_dir (); module_path_env = g_getenv ("GTK_PATH"); exe_prefix = g_getenv ("GTK_EXE_PREFIX"); + result = split_file_list (module_path_env); + + for (path = result; *path; path++) + n_env_dirs++; + + count = n_env_dirs; + result = g_renew (gchar *, result, count + (home_dir ? 3 : 2)); + + if (home_dir) + result[count++] = g_build_filename (home_dir, ".gtk-2.0", NULL); + if (exe_prefix) - default_dir = g_build_filename (exe_prefix, "lib", "gtk-2.0", NULL); + result[count++] = g_build_filename (exe_prefix, "lib", "gtk-2.0", NULL); else - default_dir = g_build_filename (GTK_LIBDIR, "gtk-2.0", NULL); + result[count++] = g_build_filename (GTK_LIBDIR, "gtk-2.0", NULL); - if (module_path_env && home_gtk_dir) - module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - module_path_env, home_gtk_dir, default_dir, NULL); - else if (module_path_env) - module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - module_path_env, default_dir, NULL); - else if (home_gtk_dir) - module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - home_gtk_dir, default_dir, NULL); - else - module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - default_dir, NULL); - - g_free (home_gtk_dir); - g_free (default_dir); - - result = split_file_list (module_path); - g_free (module_path); + result[count] = NULL; + *env_dirs = n_env_dirs; return result; } @@ -181,15 +176,18 @@ get_module_path (void) gchar ** _gtk_get_module_path (const gchar *type) { - gchar **paths = get_module_path(); gchar **path; + gchar **paths; gchar **result; gint count = 0; + gint env_dirs; + + paths = get_module_path (&env_dirs); for (path = paths; *path; path++) count++; - result = g_new (gchar *, count * 5 + 1); + result = g_new (gchar *, count * 4 + env_dirs + 1); count = 0; for (path = paths; *path; path++) @@ -219,10 +217,11 @@ _gtk_get_module_path (const gchar *type) * live directly in .../.libs directories, for example print backends, * theme engines or input modules. * - * Keep this limited to absolute paths so the removed fallback that could - * load modules from the current working directory is not reintroduced. + * Keep this limited to absolute GTK_PATH entries so the removed fallback + * that could load modules from the current working directory is not + * reintroduced for built-in default paths. */ - if (g_path_is_absolute (*path)) + if (path < paths + env_dirs && g_path_is_absolute (*path)) result[count++] = g_strdup (*path); } From f4c37aed9369ccaeb681bdf8b37eafea2b0a53ff Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 17 Aug 2026 20:40:32 +0200 Subject: [PATCH 53/56] configure: resolve indirect Pango dependencies on Linux Retry a failed Pango sanity link with -rpath-link for absolute library directories already present in LDFLAGS and PANGO_LIBS. This fixes custom-prefix transitive shared-library resolution without changing the normal configure path or runtime RPATH. --- configure.ac | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 553ea001c..f4aa7b84c 100644 --- a/configure.ac +++ b/configure.ac @@ -1397,14 +1397,39 @@ CFLAGS="$CFLAGS $PANGO_CFLAGS" if $PKG_CONFIG --uninstalled $PANGO_PACKAGES; then : else - gtk_save_LIBS="$LIBS" + gtk_save_LIBS="$LIBS" LIBS="$PANGO_LIBS $LIBS" + AC_LINK_IFELSE( [AC_LANG_CALL([], [pango_context_new])], [:], - [AC_MSG_ERROR([ + [gtk_pango_rpath_link= + + if test "x$os_linux" = xyes; then + for gtk_pango_flag in $LDFLAGS $PANGO_LIBS; do + case "$gtk_pango_flag" in + -L/*) + gtk_pango_rpath_link="$gtk_pango_rpath_link -Wl,-rpath-link,${gtk_pango_flag#-L}" + ;; + esac + done + fi + + if test -n "$gtk_pango_rpath_link"; then + LDFLAGS="$LDFLAGS $gtk_pango_rpath_link" + + AC_LINK_IFELSE( + [AC_LANG_CALL([], [pango_context_new])], + [:], + [gtk_pango_rpath_link=]) + fi + + if test -z "$gtk_pango_rpath_link"; then + AC_MSG_ERROR([ *** Can't link to Pango. Pango is required to build -*** GTK+. For more information see http://www.pango.org])]) +*** GTK+. For more information see http://www.pango.org]) + fi]) + LIBS="$gtk_save_LIBS" fi From 0ead09d9afb80d0e1c25189d29089892f05f18f4 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 17 Aug 2026 22:06:13 +0200 Subject: [PATCH 54/56] gail: connect property change tests directly to signal Replace the deprecated ATK property-change connection wrapper in the GAIL tests with direct AtkObject::property-change signal connections and use the complete GObject callback signature. --- modules/other/gail/tests/testpaned.c | 10 ++++++---- modules/other/gail/tests/testprops.c | 10 ++++++---- modules/other/gail/tests/teststatusbar.c | 10 ++++++---- modules/other/gail/tests/testtable.c | 10 ++++++---- modules/other/gail/tests/testvalues.c | 10 ++++++---- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/modules/other/gail/tests/testpaned.c b/modules/other/gail/tests/testpaned.c index 8ae9e2daa..d96179da2 100644 --- a/modules/other/gail/tests/testpaned.c +++ b/modules/other/gail/tests/testpaned.c @@ -8,13 +8,15 @@ static gint _test_paned (gpointer data); static void _check_paned (AtkObject *obj); static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values); + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED); #define NUM_VALID_ROLES 1 static gint last_position; static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values) + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED) { const gchar *type_name = g_type_name (G_TYPE_FROM_INSTANCE (obj)); const gchar *name = atk_object_get_name (obj); @@ -103,8 +105,8 @@ static void _check_paned (AtkObject *obj) { done_paned = TRUE; } - atk_object_connect_property_change_handler (paned_obj, - (AtkPropertyChangeHandler*) _property_change_handler); + g_signal_connect (paned_obj, "property-change", + G_CALLBACK (_property_change_handler), NULL); g_timeout_add (2000, _test_paned, paned_obj); } diff --git a/modules/other/gail/tests/testprops.c b/modules/other/gail/tests/testprops.c index 7477b4763..3a9cf52c8 100644 --- a/modules/other/gail/tests/testprops.c +++ b/modules/other/gail/tests/testprops.c @@ -8,7 +8,8 @@ static void _traverse_children (AtkObject *obj); static void _add_handler (AtkObject *obj); static void _check_properties (AtkObject *obj); static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values); + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED); static void _state_changed (AtkObject *obj, const gchar *name, gboolean set); @@ -64,7 +65,8 @@ _model_changed (AtkObject *obj) static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values) + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED) { const gchar *type_name = g_type_name (G_TYPE_FROM_INSTANCE (obj)); const gchar *name = atk_object_get_name (obj); @@ -165,8 +167,8 @@ _add_handler (AtkObject *obj) } if (!found) { - atk_object_connect_property_change_handler (obj, - (AtkPropertyChangeHandler*) _property_change_handler); + g_signal_connect (obj, "property-change", + G_CALLBACK (_property_change_handler), NULL); g_signal_connect (obj, "state-change", (GCallback) _state_changed, NULL); if (ATK_IS_SELECTION (obj)) diff --git a/modules/other/gail/tests/teststatusbar.c b/modules/other/gail/tests/teststatusbar.c index 0223af593..4cd23096d 100644 --- a/modules/other/gail/tests/teststatusbar.c +++ b/modules/other/gail/tests/teststatusbar.c @@ -12,7 +12,8 @@ static void _check_statusbar (AtkObject *obj); static AtkObject* _find_object (AtkObject* obj, AtkRole role); static void _notify_handler (GObject *obj, GParamSpec *pspec); static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values); + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED); static AtkObject* _find_object (AtkObject *obj, @@ -50,7 +51,8 @@ _find_object (AtkObject *obj, } static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values) + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED) { const gchar *type_name = g_type_name (G_TYPE_FROM_INSTANCE (obj)); const gchar *name = atk_object_get_name (obj); @@ -92,8 +94,8 @@ static void _check_statusbar (AtkObject *obj) g_cclosure_new (G_CALLBACK (_notify_handler), NULL, NULL), FALSE); - atk_object_connect_property_change_handler (statusbar, - (AtkPropertyChangeHandler*) _property_change_handler); + g_signal_connect (statusbar, "property-change", + G_CALLBACK (_property_change_handler), NULL); } diff --git a/modules/other/gail/tests/testtable.c b/modules/other/gail/tests/testtable.c index 3fd1b3dc7..bbf30bcc6 100644 --- a/modules/other/gail/tests/testtable.c +++ b/modules/other/gail/tests/testtable.c @@ -42,7 +42,8 @@ static void _notify_table_child_removed (GObject *obj, gint index, AtkObject *child); static void _property_signal_connect (AtkObject *obj); static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values); + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED); static gboolean tested_set_headers = FALSE; static void test_choice_gui (AtkObject **obj); @@ -195,8 +196,8 @@ static void _check_table (AtkObject *in_obj) } g_table = ATK_TABLE(obj); - atk_object_connect_property_change_handler (obj, - (AtkPropertyChangeHandler*) _property_change_handler); + g_signal_connect (obj, "property-change", + G_CALLBACK (_property_change_handler), NULL); current_obj = obj; /* @@ -657,7 +658,8 @@ static void _property_signal_connect (AtkObject *obj) static void _property_change_handler (AtkObject *obj, - AtkPropertyValues *values) + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED) { gchar *obj_text; const gchar *name; diff --git a/modules/other/gail/tests/testvalues.c b/modules/other/gail/tests/testvalues.c index d2331d73c..e7462cb66 100644 --- a/modules/other/gail/tests/testvalues.c +++ b/modules/other/gail/tests/testvalues.c @@ -8,12 +8,14 @@ static void _traverse_children (AtkObject *obj); static void _add_handler (AtkObject *obj); static void _check_values (AtkObject *obj); static void _value_change_handler (AtkObject *obj, - AtkPropertyValues *values); + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED); static guint id; static void _value_change_handler (AtkObject *obj, - AtkPropertyValues *values) + AtkPropertyValues *values, + gpointer user_data G_GNUC_UNUSED) { const gchar *type_name = g_type_name (G_TYPE_FROM_INSTANCE (obj)); GValue val = { 0, }; @@ -97,8 +99,8 @@ static void _add_handler (AtkObject *obj) } if (!found) { - atk_object_connect_property_change_handler (obj, - (AtkPropertyChangeHandler*) _value_change_handler); + g_signal_connect (obj, "property-change", + G_CALLBACK (_value_change_handler), NULL); g_ptr_array_add (obj_array, obj); } } From c70ae22fe2e6879b6a1390aef079a66ae3a33fc2 Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Mon, 17 Aug 2026 22:21:19 +0200 Subject: [PATCH 55/56] gail: explicitly discard test value setter result Explicitly discard the boolean result where the paned accessibility test intentionally ignores it, avoiding the comma-expression unused-value warning without changing generated code. --- modules/other/gail/tests/testpaned.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/other/gail/tests/testpaned.c b/modules/other/gail/tests/testpaned.c index d96179da2..c429979a0 100644 --- a/modules/other/gail/tests/testpaned.c +++ b/modules/other/gail/tests/testpaned.c @@ -71,7 +71,7 @@ static gint _test_paned (gpointer data) last_position = position; position *= 2; g_value_set_int (value, position); - gail_test_value_set_current_value (ATK_VALUE (obj), value); + (void) gail_test_value_set_current_value (ATK_VALUE (obj), value); times++; } if (times < 4) From 6cdb4e5eeb21e6216d9b85f30c60fb43e25f840f Mon Sep 17 00:00:00 2001 From: Daemonratte Date: Tue, 15 Sep 2026 19:02:10 +0200 Subject: [PATCH 56/56] gtkfilesystemmodel: make directory loading incremental --- gtk/gtkfilechooserdefault.c | 81 +- gtk/gtkfilechooserprivate.h | 4 +- gtk/gtkfilesystemmodel.c | 1848 ++++++++++++++++++++++------------- 3 files changed, 1199 insertions(+), 734 deletions(-) diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 41602f824..9f9aac514 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -140,14 +140,12 @@ _gtk_file_chooser_profile_log (const char *func, int indent, const char *msg1, c #endif - typedef struct _GtkFileChooserDefaultClass GtkFileChooserDefaultClass; #define GTK_FILE_CHOOSER_DEFAULT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_DEFAULT, GtkFileChooserDefaultClass)) #define GTK_IS_FILE_CHOOSER_DEFAULT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_DEFAULT)) #define GTK_FILE_CHOOSER_DEFAULT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_DEFAULT, GtkFileChooserDefaultClass)) -#define MAX_LOADING_TIME 500 #define DEFAULT_NEW_FOLDER_NAME _("Type name of new folder") @@ -406,7 +404,6 @@ static void remove_bookmark_button_clicked_cb (GtkButton *button, static void update_cell_renderer_attributes (GtkFileChooserDefault *impl); -static void load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_state); static void browse_files_center_selected_row (GtkFileChooserDefault *impl); static void view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode); @@ -918,9 +915,6 @@ gtk_file_chooser_default_finalize (GObject *object) search_clear_model (impl, FALSE); recent_clear_model (impl, FALSE); - /* stopping the load above should have cleared this */ - g_assert (impl->load_timeout_id == 0); - g_free (impl->preview_display_name); g_free (impl->edited_new_text); @@ -7481,61 +7475,8 @@ load_set_model (GtkFileChooserDefault *impl) profile_end ("end", NULL); } -/* Timeout callback used when the loading timer expires */ -static gboolean -load_timeout_cb (gpointer data) -{ - GtkFileChooserDefault *impl; - profile_start ("start", NULL); - impl = GTK_FILE_CHOOSER_DEFAULT (data); - g_assert (impl->load_state == LOAD_PRELOAD); - g_assert (impl->load_timeout_id != 0); - g_assert (impl->browse_files_model != NULL); - - impl->load_timeout_id = 0; - impl->load_state = LOAD_LOADING; - - load_set_model (impl); - - profile_end ("end", NULL); - - return FALSE; -} - -/* Sets up a new load timer for the model and switches to the LOAD_PRELOAD state */ -static void -load_setup_timer (GtkFileChooserDefault *impl) -{ - g_assert (impl->load_timeout_id == 0); - g_assert (impl->load_state != LOAD_PRELOAD); - - impl->load_timeout_id = gdk_threads_add_timeout (MAX_LOADING_TIME, load_timeout_cb, impl); - impl->load_state = LOAD_PRELOAD; -} - -/* Removes the load timeout; changes the impl->load_state to the specified value. */ -static void -load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_state) -{ - if (impl->load_timeout_id != 0) - { - g_assert (impl->load_state == LOAD_PRELOAD); - - g_source_remove (impl->load_timeout_id); - impl->load_timeout_id = 0; - } - else - g_assert (impl->load_state == LOAD_EMPTY || - impl->load_state == LOAD_LOADING || - impl->load_state == LOAD_FINISHED); - - g_assert (new_load_state == LOAD_EMPTY || - new_load_state == LOAD_LOADING || - new_load_state == LOAD_FINISHED); - impl->load_state = new_load_state; -} /* Selects the first row in the file list */ static void @@ -7754,15 +7695,8 @@ browse_files_model_finished_loading_cb (GtkFileSystemModel *model, if (error) show_error_on_reading_current_folder (impl, error); - if (impl->load_state == LOAD_PRELOAD) - { - load_remove_timer (impl, LOAD_FINISHED); - load_set_model (impl); - } - else if (impl->load_state == LOAD_LOADING) - { - /* Nothing */ - } + if (impl->load_state == LOAD_LOADING) + load_set_model (impl); else { /* We can't g_assert_not_reached(), as something other than us may have @@ -7772,8 +7706,6 @@ browse_files_model_finished_loading_cb (GtkFileSystemModel *model, return; } - g_assert (impl->load_timeout_id == 0); - impl->load_state = LOAD_FINISHED; pending_select_files_process (impl); @@ -7789,7 +7721,7 @@ static void stop_loading_and_clear_list_model (GtkFileChooserDefault *impl, gboolean remove_from_treeview) { - load_remove_timer (impl, LOAD_EMPTY); + impl->load_state = LOAD_EMPTY; if (impl->browse_files_model) { @@ -8216,12 +8148,15 @@ set_list_model (GtkFileChooserDefault *impl, gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->browse_files_model), MODEL_COL_SIZE, size_sort_func, impl, NULL); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->browse_files_model), MODEL_COL_MTIME, mtime_sort_func, impl, NULL); gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (impl->browse_files_model), NULL, NULL, NULL); - set_sort_column (impl); + gtk_tree_sortable_set_sort_column_id + (GTK_TREE_SORTABLE (impl->browse_files_model), + impl->sort_column, + impl->sort_order); impl->list_sort_ascending = TRUE; g_signal_connect (impl->browse_files_model, "sort-column-changed", G_CALLBACK (list_sort_column_changed_cb), impl); - load_setup_timer (impl); /* This changes the state to LOAD_PRELOAD */ + impl->load_state = LOAD_LOADING; g_signal_connect (impl->browse_files_model, "finished-loading", G_CALLBACK (browse_files_model_finished_loading_cb), impl); diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index 56cc5db62..40466c68a 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -122,8 +122,7 @@ struct _GtkFileChooserWidgetPrivate typedef enum { LOAD_EMPTY, /* There is no model */ - LOAD_PRELOAD, /* Model is loading and a timer is running; model isn't inserted into the tree yet */ - LOAD_LOADING, /* Timeout expired, model is inserted into the tree, but not fully loaded yet */ + LOAD_LOADING, /* Model is loading; inserted into the view when loading finishes */ LOAD_FINISHED /* Model is fully loaded and inserted into the tree */ } LoadState; @@ -263,7 +262,6 @@ struct _GtkFileChooserDefault LoadState load_state; ReloadState reload_state; - guint load_timeout_id; OperationMode operation_mode; diff --git a/gtk/gtkfilesystemmodel.c b/gtk/gtkfilesystemmodel.c index 4c619c2a3..6adc1c37a 100644 --- a/gtk/gtkfilesystemmodel.c +++ b/gtk/gtkfilesystemmodel.c @@ -36,89 +36,17 @@ /*** Structure: how GtkFileSystemModel works * - * This is a custom GtkTreeModel used to hold a collection of files for GtkFileChooser. There are two use cases: + * File nodes have stable IDs in fixed-size slot storage. model->order holds + * logical sort order and model->visible_order is the GtkTreeModel projection. + * file_lookup permanently maps GFile to stable node ID. * - * 1. The model populates itself from a folder, using the GIO file enumerator API. This happens if you use - * _gtk_file_system_model_new_for_directory(). This is the normal usage for showing the contents of a folder. + * Cached column values live in sparse 64-ID pages allocated per used column, + * so unused columns have no per-node GValue storage. * - * 2. The caller populates the model by hand, with files not necessarily in the same folder. This happens - * if you use _gtk_file_system_model_new() and then _gtk_file_system_model_add_and_query_file(). This is - * the special kind of usage for "search" and "recent-files", where the file chooser gives the model the - * files to be displayed. - * - * Internal data structure - * ----------------------- - * - * Each file is kept in a FileModelNode structure. Each FileModelNode holds a GFile* and other data. All the - * node structures have the same size, determined at runtime, depending on the number of columns that were passed - * to _gtk_file_system_model_new() or _gtk_file_system_model_new_for_directory() (that is, the size of a node is - * not sizeof (FileModelNode), but rather model->node_size). The last field in the FileModelNode structure, - * node->values[], is an array of GValue, used to hold the data for those columns. - * - * The model stores an array of FileModelNode structures in model->files. This is a GArray where each element is - * model->node_size bytes in size (the model computes that node size when initializing itself). There are - * convenience macros, get_node() and node_index(), to access that array based on an array index or a pointer to - * a node inside the array. - * - * The model accesses files through two of its fields: - * - * model->files - GArray of FileModelNode structures. - * - * model->file_lookup - hash table that maps a GFile* to an index inside the model->files array. - * - * The model->file_lookup hash table is populated lazily. It is both accessed and populated with the - * node_get_for_file() function. The invariant is that the files in model->files[n] for n < g_hash_table_size - * (model->file_lookup) are already added to the hash table. The hash table will get cleared when we re-sort the - * files, as the array will be in a different order and the indexes need to be rebuilt. - * - * Each FileModelNode has a node->visible field, which indicates whether the node is visible in the GtkTreeView. - * A node may be invisible if, for example, it corresponds to a hidden file and the file chooser is not showing - * hidden files. Also, a file filter may be explicitly set onto the model, for example, to only show files that - * match "*.jpg". In this case, node->filtered_out says whether the node failed the filter. The ultimate - * decision on whether a node is visible or not in the treeview is distilled into the node->visible field. - * The reason for having a separate node->filtered_out field is so that the file chooser can query whether - * a (filtered-out) folder should be made sensitive in the GUI. - * - * Visible rows vs. possibly-invisible nodes - * ----------------------------------------- - * - * Since not all nodes in the model->files array may be visible, we need a way to map visible row indexes from - * the treeview to array indexes in our array of files. And thus we introduce a bit of terminology: - * - * index - An index in the model->files array. All variables/fields that represent indexes are either called - * "index" or "i_*", or simply "i" for things like loop counters. - * - * row - An index in the GtkTreeView, i.e. the index of a row within the outward-facing API of the - * GtkFileSystemModel. However, note that our rows are 1-based, not 0-based, for the reason explained in the - * following paragraph. Variables/fields that represent visible rows are called "row", or "r_*", or simply - * "r". - * - * Each FileModelNode has a node->row field which is the number of visible rows in the treeview, *before and - * including* that node. This means that node->row is 1-based, instead of 0-based --- this makes some code - * simpler, believe it or not :) This also means that when the calling GtkTreeView gives us a GtkTreePath, we - * turn the 0-based treepath into a 1-based row for our purposes. If a node is not visible, it will have the - * same row number as its closest preceding visible node. - * - * We try to compute the node->row fields lazily. A node is said to be "valid" if its node->row is accurate. - * For this, the model keeps a model->n_nodes_valid field which is the count of valid nodes starting from the - * beginning of the model->files array. When a node changes its information, or when a node gets deleted, that - * node and the following ones get invalidated by simply setting model->n_nodes_valid to the array index of the - * node. If the model happens to need a node's row number and that node is in the model->files array after - * model->n_nodes_valid, then the nodes get re-validated up to the sought node. See node_validate_rows() for - * this logic. - * - * You never access a node->row directly. Instead, call node_get_tree_row(). That function will validate the nodes - * up to the sought one if the node is not valid yet, and it will return a proper 0-based row. - * - * Sorting - * ------- - * - * The model implements the GtkTreeSortable interface. To avoid re-sorting - * every time a node gets added (which would lead to O(n^2) performance during - * the initial population of the model), the model can freeze itself (with - * freeze_updates()) during the intial population process. When the model is - * frozen, sorting will not happen. The model will sort itself when the freeze - * count goes back to zero, via corresponding calls to thaw_updates(). + * Directory enumeration uses one bounded GIO batch as its pending queue. + * Integration is cooperatively drained in short main-loop slices; the next + * GIO batch is requested only after the current one has been consumed. + * Editing has its own hold state and no longer reuses directory batching. */ /*** DEFINES ***/ @@ -130,69 +58,107 @@ /* random number that everyone else seems to use, too */ #define FILES_PER_QUERY 100 +#define MODEL_DRAIN_SLICE_US 2000 +#define MODEL_DRAIN_CLOCK_GRANULARITY 4 +#define MODEL_FINALIZE_CLOCK_GRANULARITY 8 + +enum +{ + MODEL_FINALIZE_NONE, + MODEL_FINALIZE_MERGE, + MODEL_FINALIZE_VISIBLE, + MODEL_FINALIZE_REPLAY +}; + +#define VALUE_PAGE_SHIFT 6 +#define VALUE_PAGE_SIZE (1u << VALUE_PAGE_SHIFT) +#define VALUE_PAGE_MASK (VALUE_PAGE_SIZE - 1u) typedef struct _FileModelNode FileModelNode; typedef struct _GtkFileSystemModelClass GtkFileSystemModelClass; struct _FileModelNode { - GFile * file; /* file represented by this node or NULL for editable */ - GFileInfo * info; /* info for this file or NULL if unknown */ + GFile * file; + GFileInfo * info; - guint row; /* if valid (see model->n_valid_indexes), visible nodes before and including - * this one - see the "Structure" comment above. - */ + guint order_pos; + guint visible_pos; + guint generation; - guint visible :1; /* if the file is currently visible */ - guint filtered_out :1;/* if the file is currently filtered out (i.e. it didn't pass the filters) */ - guint frozen_add :1; /* true if the model was frozen and the entry has not been added yet */ - - GValue values[1]; /* actually n_columns values */ + guint visible :1; + guint filtered_out :1; + guint held_add :1; }; +typedef struct +{ + GValue values[VALUE_PAGE_SIZE]; +} ValuePage; + +typedef struct +{ + GFile *file; + GFileMonitorEvent type; +} PendingMonitorChange; + +typedef GWeakRef PendingMonitorQuery; + struct _GtkFileSystemModel { GObject parent_instance; + GFile * dir; + char * attributes; + GFileMonitor * dir_monitor; + GFileEnumerator * dir_enumerator; + GCancellable * cancellable; + GList * pending_files; + guint drain_source; - GFile * dir; /* directory that's displayed */ - guint dir_thaw_source;/* GSource id for unfreezing the model */ - char * attributes; /* attributes the file info must contain, or NULL for all attributes */ - GFileMonitor * dir_monitor; /* directory that is monitored, or NULL if monitoring was not supported */ + GArray * sort_scratch; + guint sort_width; + guint sort_left; + guint sort_i; + guint sort_j; + guint sort_k; + guint sort_mid; + guint sort_right; + guint finalize_phase; + GError * load_error; - GCancellable * cancellable; /* cancellable in use for all operations - cancelled on dispose */ - GArray * files; /* array of FileModelNode containing all our files */ - gsize node_size; /* Size of a FileModelNode structure once its ->values field has n_columns */ - guint n_nodes_valid; /* count of valid nodes (i.e. those whose node->row is accurate) */ - GHashTable * file_lookup; /* mapping of GFile => array index in model->files - * This hash table doesn't always have the same number of entries as the files array; - * it can get cleared completely when we resort. - * The hash table gets re-populated in node_get_for_file() if this mismatch is - * detected. - */ + GQueue * pending_monitor_changes; + GHashTable * pending_monitor_queries; - guint n_columns; /* number of columns */ - GType * column_types; /* types of each column */ - GtkFileSystemModelGetValue get_func; /* function to call to fill in values in columns */ - gpointer get_data; /* data to pass to get_func */ + GArray * files; + GArray * order; + GArray * visible_order; + guint free_id_head; + GHashTable * file_lookup; - GtkFileFilter * filter; /* filter to use for deciding which nodes are visible */ + guint n_columns; + GType * column_types; + GPtrArray ** value_pages; + GtkFileSystemModelGetValue get_func; + gpointer get_data; - int sort_column_id; /* current sorting column */ - GtkSortType sort_order; /* current sorting order */ - GList * sort_list; /* list of sorting functions */ - GtkTreeIterCompareFunc default_sort_func; /* default sort function */ - gpointer default_sort_data; /* data to pass to default sort func */ - GDestroyNotify default_sort_destroy; /* function to call to destroy default_sort_data */ + GtkFileFilter * filter; - guint frozen; /* number of times we're frozen */ + int sort_column_id; + GtkSortType sort_order; + GList * sort_list; + GtkTreeIterCompareFunc default_sort_func; + gpointer default_sort_data; + GDestroyNotify default_sort_destroy; - gboolean filter_on_thaw :1;/* set when filtering needs to happen upon thawing */ - gboolean sort_on_thaw :1;/* set when sorting needs to happen upon thawing */ + guint editing_hold :1; + guint refilter_pending :1; + guint resort_pending :1; + guint directory_loading :1; - guint show_hidden :1; /* whether to show hidden files */ - guint show_folders :1;/* whether to show folders */ - guint show_files :1; /* whether to show files */ - guint filter_folders :1;/* whether filter applies to folders */ + guint show_hidden :1; + guint show_folders :1; + guint show_files :1; + guint filter_folders :1; }; #define GTK_FILE_SYSTEM_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass)) @@ -208,8 +174,17 @@ struct _GtkFileSystemModelClass void (*finished_loading) (GtkFileSystemModel *model, GError *error); }; -static void freeze_updates (GtkFileSystemModel *model); -static void thaw_updates (GtkFileSystemModel *model); +static void gtk_file_system_model_got_files (GObject *object, + GAsyncResult *res, + gpointer data); +static gboolean gtk_file_system_model_drain (gpointer data); +static void gtk_file_system_model_schedule_work (GtkFileSystemModel *model); +static gboolean gtk_file_system_model_finalize_load (gpointer data); +static void gtk_file_system_model_begin_finalize_load (GtkFileSystemModel *model, + GError *error); +static void gtk_file_system_model_apply_monitor_change (GtkFileSystemModel *model, + GFile *file, + GFileMonitorEvent type); static guint node_get_for_file (GtkFileSystemModel *model, GFile *file); @@ -221,89 +196,118 @@ static void remove_file (GtkFileSystemModel *model, GFile *file); /* iter setup: - * @user_data: the model - * @user_data2: GUINT_TO_POINTER of array index of current entry - * - * All other fields are unused. Note that the array index does not corrspond - * 1:1 with the path index as entries might not be visible. + * @user_data: the model + * @user_data2: stable node ID + * @user_data3: node generation */ -#define ITER_INDEX(iter) GPOINTER_TO_UINT((iter)->user_data2) -#define ITER_IS_VALID(model, iter) ((model) == (iter)->user_data) -#define ITER_INIT_FROM_INDEX(model, _iter, _index) G_STMT_START {\ - g_assert (_index < (model)->files->len); \ +#define ITER_INDEX(iter) GPOINTER_TO_UINT ((iter)->user_data2) + +#define get_node(_model, _index) \ + (&g_array_index ((_model)->files, FileModelNode, (_index))) + +static gboolean +iter_is_valid (GtkFileSystemModel *model, + GtkTreeIter *iter) +{ + guint id; + FileModelNode *node; + + if (iter == NULL || iter->user_data != model) + return FALSE; + + id = ITER_INDEX (iter); + if (id >= model->files->len) + return FALSE; + + node = get_node (model, id); + return (id == 0 || node->file != NULL) && + node->generation == GPOINTER_TO_UINT (iter->user_data3); +} + +#define ITER_IS_VALID(model, iter) iter_is_valid ((model), (iter)) + +#define ITER_INIT_FROM_INDEX(model, _iter, _index) G_STMT_START { \ + FileModelNode *_node; \ + g_assert ((_index) < (model)->files->len); \ + _node = get_node ((model), (_index)); \ + g_assert ((_index) == 0 || _node->file != NULL); \ (_iter)->user_data = (model); \ (_iter)->user_data2 = GUINT_TO_POINTER (_index); \ -}G_STMT_END + (_iter)->user_data3 = GUINT_TO_POINTER (_node->generation); \ +} G_STMT_END -/*** FileModelNode ***/ - -/* Get a FileModelNode structure given an index in the model->files array of nodes */ -#define get_node(_model, _index) ((FileModelNode *) ((_model)->files->data + (_index) * (_model)->node_size)) - -/* Get an index within the model->files array of nodes, given a FileModelNode* */ -#define node_index(_model, _node) (((gchar *) (_node) - (_model)->files->data) / (_model)->node_size) - -/* @up_to_index: smallest model->files array index that will be valid after this call - * @up_to_row: smallest node->row that will be valid after this call - * - * If you want to validate up to an index or up to a row, specify the index or - * the row you want and specify G_MAXUINT for the other argument. Pass - * G_MAXUINT for both arguments for "validate everything". - */ static void -node_validate_rows (GtkFileSystemModel *model, guint up_to_index, guint up_to_row) +order_update_positions_from (GtkFileSystemModel *model, + guint first) { - guint i, row; + guint i; - if (model->files->len == 0) - return; - - up_to_index = MIN (up_to_index, model->files->len - 1); - - i = model->n_nodes_valid; - if (i != 0) - row = get_node (model, i - 1)->row; - else - row = 0; - - while (i <= up_to_index && row <= up_to_row) + for (i = first; i < model->order->len; i++) { - FileModelNode *node = get_node (model, i); - if (node->visible) - row++; - node->row = row; - i++; + guint id = g_array_index (model->order, guint, i); + get_node (model, id)->order_pos = i; + } +} + +static void +visible_update_positions_from (GtkFileSystemModel *model, + guint first) +{ + guint i; + + for (i = first; i < model->visible_order->len; i++) + { + guint id = g_array_index (model->visible_order, guint, i); + get_node (model, id)->visible_pos = i; } - model->n_nodes_valid = i; } static guint -node_get_tree_row (GtkFileSystemModel *model, guint index) +visible_find_order_pos (GtkFileSystemModel *model, + guint order_pos, + gboolean after) { - if (model->n_nodes_valid <= index) - node_validate_rows (model, index, G_MAXUINT); + guint lo = get_node (model, 0)->visible ? 1 : 0; + guint hi = model->visible_order->len; - return get_node (model, index)->row - 1; -} + while (lo < hi) + { + guint mid = lo + (hi - lo) / 2; + guint id = g_array_index (model->visible_order, guint, mid); + guint pos = get_node (model, id)->order_pos; -static void -node_invalidate_index (GtkFileSystemModel *model, guint id) -{ - model->n_nodes_valid = MIN (model->n_nodes_valid, id); + if (pos < order_pos || (after && pos == order_pos)) + lo = mid + 1; + else + hi = mid; + } + + return lo; } static GtkTreePath * -tree_path_new_from_node (GtkFileSystemModel *model, guint id) +tree_path_new_from_node (GtkFileSystemModel *model, + guint id) { - guint r = node_get_tree_row (model, id); + FileModelNode *node = get_node (model, id); + guint row = G_MAXUINT; - g_assert (r < model->files->len); + if (node->visible) + row = node->visible_pos; + else if (id != 0) + { + guint pos = visible_find_order_pos (model, node->order_pos, TRUE); + if (pos > 0) + row = pos - 1; + } - return gtk_tree_path_new_from_indices (r, -1); + g_assert (row != G_MAXUINT); + return gtk_tree_path_new_from_indices (row, -1); } static void -emit_row_inserted_for_node (GtkFileSystemModel *model, guint id) +emit_row_inserted_for_node (GtkFileSystemModel *model, + guint id) { GtkTreePath *path; GtkTreeIter iter; @@ -315,7 +319,8 @@ emit_row_inserted_for_node (GtkFileSystemModel *model, guint id) } static void -emit_row_changed_for_node (GtkFileSystemModel *model, guint id) +emit_row_changed_for_node (GtkFileSystemModel *model, + guint id) { GtkTreePath *path; GtkTreeIter iter; @@ -327,7 +332,8 @@ emit_row_changed_for_node (GtkFileSystemModel *model, guint id) } static void -emit_row_deleted_for_row (GtkFileSystemModel *model, guint row) +emit_row_deleted_for_row (GtkFileSystemModel *model, + guint row) { GtkTreePath *path; @@ -337,11 +343,167 @@ emit_row_deleted_for_row (GtkFileSystemModel *model, guint row) } static void -node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean visible, gboolean filtered_out) +visible_remove_node (GtkFileSystemModel *model, + guint id) { FileModelNode *node = get_node (model, id); + guint pos; - /* Filteredness */ + g_assert (node->visible); + pos = node->visible_pos; + + g_array_remove_index (model->visible_order, pos); + node->visible = FALSE; + visible_update_positions_from (model, pos); +} + +static void +value_page_free (gpointer data) +{ + ValuePage *page = data; + guint slot; + + if (page) + for (slot = 0; slot < VALUE_PAGE_SIZE; slot++) + if (G_VALUE_TYPE (&page->values[slot]) != G_TYPE_INVALID) + g_value_unset (&page->values[slot]); + + g_free (page); +} + +static void +file_model_node_clear (gpointer data) +{ + FileModelNode *node = data; + + g_clear_object (&node->file); + g_clear_object (&node->info); +} + +static GValue * +value_cache_slot (GtkFileSystemModel *model, + guint id, + guint column, + gboolean create) +{ + GPtrArray *pages; + ValuePage *page; + guint page_index; + + g_assert (column < model->n_columns); + + pages = model->value_pages[column]; + if (pages == NULL) + { + if (!create) + return NULL; + + pages = g_ptr_array_new_with_free_func (value_page_free); + model->value_pages[column] = pages; + } + + page_index = id >> VALUE_PAGE_SHIFT; + if (page_index >= pages->len) + { + if (!create) + return NULL; + + g_ptr_array_set_size (pages, page_index + 1); + } + + page = pages->pdata[page_index]; + if (page == NULL && create) + { + page = g_new0 (ValuePage, 1); + pages->pdata[page_index] = page; + } + + if (page == NULL) + return NULL; + + return &page->values[id & VALUE_PAGE_MASK]; +} + +static gboolean +value_cache_clear_slot (GtkFileSystemModel *model, + guint id, + guint column) +{ + GValue *value = value_cache_slot (model, id, column, FALSE); + + if (value == NULL || G_VALUE_TYPE (value) == G_TYPE_INVALID) + return FALSE; + + g_value_unset (value); + return TRUE; +} + +static void +value_cache_clear_node (GtkFileSystemModel *model, + guint id) +{ + guint column; + + for (column = 0; column < model->n_columns; column++) + value_cache_clear_slot (model, id, column); +} + + + +static guint +node_allocate_slot (GtkFileSystemModel *model) +{ + FileModelNode *node; + guint id; + + if (model->free_id_head != 0) + { + id = model->free_id_head; + node = get_node (model, id); + model->free_id_head = node->order_pos; + } + else + { + id = model->files->len; + g_array_set_size (model->files, id + 1); + node = get_node (model, id); + node->generation = 1; + } + + return id; +} + +static void +node_retire_slot (GtkFileSystemModel *model, + guint id) +{ + FileModelNode *node; + guint generation; + + g_assert (id != 0); + + node = get_node (model, id); + file_model_node_clear (node); + value_cache_clear_node (model, id); + + generation = node->generation + 1; + if (generation == 0) + generation = 1; + + memset (node, 0, sizeof (*node)); + node->generation = generation; + node->order_pos = model->free_id_head; + + model->free_id_head = id; +} + +static void +node_set_visible_and_filtered_out (GtkFileSystemModel *model, + guint id, + gboolean visible, + gboolean filtered_out) +{ + FileModelNode *node = get_node (model, id); if (node->filtered_out != filtered_out) { @@ -350,27 +512,25 @@ node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean emit_row_changed_for_node (model, id); } - /* Visibility */ - - if (node->visible == visible || - node->frozen_add) + if (node->visible == visible || node->held_add) return; if (visible) { + guint pos; + + g_assert (!node->visible); + pos = id == 0 ? 0 : + visible_find_order_pos (model, node->order_pos, FALSE); + g_array_insert_val (model->visible_order, pos, id); node->visible = TRUE; - node_invalidate_index (model, id); + visible_update_positions_from (model, pos); emit_row_inserted_for_node (model, id); } else { - guint row; - - row = node_get_tree_row (model, id); - g_assert (row < model->files->len); - - node->visible = FALSE; - node_invalidate_index (model, id); + guint row = node->visible_pos; + visible_remove_node (model, id); emit_row_deleted_for_row (model, row); } } @@ -524,73 +684,29 @@ gtk_file_system_model_get_column_type (GtkTreeModel *tree_model, return model->column_types[i]; } -static int -compare_indices (gconstpointer key, gconstpointer _node) -{ - const FileModelNode *node = _node; - - return GPOINTER_TO_UINT (key) - node->row; -} - static gboolean gtk_file_system_model_iter_nth_child (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreeIter *parent, - gint n) + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); - char *node; guint id; - guint row_to_find; g_return_val_if_fail (n >= 0, FALSE); - if (parent != NULL) + if (parent != NULL || (guint) n >= model->visible_order->len) return FALSE; - row_to_find = n + 1; /* plus one as our node->row numbers are 1-based; see the "Structure" comment at the beginning */ - - if (model->n_nodes_valid > 0 && - get_node (model, model->n_nodes_valid - 1)->row >= row_to_find) - { - /* Fast path - the nodes are valid up to the sought one. - * - * First, find a node with the sought row number...*/ - - node = bsearch (GUINT_TO_POINTER (row_to_find), - model->files->data, - model->n_nodes_valid, - model->node_size, - compare_indices); - if (node == NULL) - return FALSE; - - /* ... Second, back up until we find the first visible node with that row number */ - - id = node_index (model, node); - while (!get_node (model, id)->visible) - id--; - - g_assert (get_node (model, id)->row == row_to_find); - } - else - { - /* Slow path - the nodes need to be validated up to the sought one */ - - node_validate_rows (model, G_MAXUINT, n); /* note that this is really "n", not row_to_find - see node_validate_rows() */ - id = model->n_nodes_valid - 1; - if (model->n_nodes_valid == 0 || get_node (model, id)->row != row_to_find) - return FALSE; - } - + id = g_array_index (model->visible_order, guint, n); ITER_INIT_FROM_INDEX (model, iter, id); return TRUE; } static gboolean gtk_file_system_model_get_iter (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreePath *path) + GtkTreeIter *iter, + GtkTreePath *path) { g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE); @@ -605,12 +721,11 @@ gtk_file_system_model_get_iter (GtkTreeModel *tree_model, static GtkTreePath * gtk_file_system_model_get_path (GtkTreeModel *tree_model, - GtkTreeIter *iter) + GtkTreeIter *iter) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); - return tree_path_new_from_node (model, ITER_INDEX (iter)); } @@ -638,25 +753,29 @@ gtk_file_system_model_get_value (GtkTreeModel *tree_model, static gboolean gtk_file_system_model_iter_next (GtkTreeModel *tree_model, - GtkTreeIter *iter) + GtkTreeIter *iter) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); - guint i; + FileModelNode *node; + guint pos; g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE); - for (i = ITER_INDEX (iter) + 1; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); + node = get_node (model, ITER_INDEX (iter)); - if (node->visible) - { - ITER_INIT_FROM_INDEX (model, iter, i); - return TRUE; - } - } + if (node->visible) + pos = node->visible_pos + 1; + else if (ITER_INDEX (iter) != 0) + pos = visible_find_order_pos (model, node->order_pos, TRUE); + else + return FALSE; - return FALSE; + if (pos >= model->visible_order->len) + return FALSE; + + ITER_INIT_FROM_INDEX (model, iter, + g_array_index (model->visible_order, guint, pos)); + return TRUE; } static gboolean @@ -676,14 +795,14 @@ gtk_file_system_model_iter_has_child (GtkTreeModel *tree_model, static gint gtk_file_system_model_iter_n_children (GtkTreeModel *tree_model, - GtkTreeIter *iter) + GtkTreeIter *iter) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); if (iter) return 0; - return node_get_tree_row (model, model->files->len - 1) + 1; + return model->visible_order->len; } static gboolean @@ -743,7 +862,7 @@ sort_data_init (SortData *data, GtkFileSystemModel *model) { GtkTreeDataSortHeader *header; - if (model->files->len <= 2) + if (model->order->len <= 1) return FALSE; switch (model->sort_column_id) @@ -757,7 +876,8 @@ sort_data_init (SortData *data, GtkFileSystemModel *model) case GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID: return FALSE; default: - header = _gtk_tree_data_list_get_header (model->sort_list, model->sort_column_id); + header = _gtk_tree_data_list_get_header (model->sort_list, + model->sort_column_id); if (header == NULL) return FALSE; data->func = header->func; @@ -771,14 +891,67 @@ sort_data_init (SortData *data, GtkFileSystemModel *model) } static int -compare_array_element (gconstpointer a, gconstpointer b, gpointer user_data) +compare_order_element (gconstpointer a, gconstpointer b, gpointer user_data) { SortData *data = user_data; GtkTreeIter itera, iterb; + guint ida = *(const guint *) a; + guint idb = *(const guint *) b; - ITER_INIT_FROM_INDEX (data->model, &itera, node_index (data->model, a)); - ITER_INIT_FROM_INDEX (data->model, &iterb, node_index (data->model, b)); - return data->func (GTK_TREE_MODEL (data->model), &itera, &iterb, data->data) * data->order; + ITER_INIT_FROM_INDEX (data->model, &itera, ida); + ITER_INIT_FROM_INDEX (data->model, &iterb, idb); + + return data->func (GTK_TREE_MODEL (data->model), + &itera, &iterb, data->data) * data->order; +} + +static void +visible_order_rebuild_after_sort (GtkFileSystemModel *model) +{ + guint old_len = model->visible_order->len; + int *new_order; + guint row = 0; + guint i; + FileModelNode *editable; + + if (old_len == 0) + return; + + new_order = g_new (int, old_len); + g_array_set_size (model->visible_order, 0); + + editable = get_node (model, 0); + if (editable->visible) + { + guint id = 0; + new_order[row] = editable->visible_pos; + g_array_append_val (model->visible_order, id); + editable->visible_pos = row++; + } + + for (i = 0; i < model->order->len; i++) + { + guint id = g_array_index (model->order, guint, i); + FileModelNode *node = get_node (model, id); + + if (!node->visible) + continue; + + new_order[row] = node->visible_pos; + g_array_append_val (model->visible_order, id); + node->visible_pos = row++; + } + + g_assert (row == old_len); + + { + GtkTreePath *path = gtk_tree_path_new (); + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + path, NULL, new_order); + gtk_tree_path_free (path); + } + + g_free (new_order); } static void @@ -786,67 +959,26 @@ gtk_file_system_model_sort (GtkFileSystemModel *model) { SortData data; - if (model->frozen) + if (model->directory_loading || model->editing_hold) { - model->sort_on_thaw = TRUE; + model->resort_pending = TRUE; return; } if (sort_data_init (&data, model)) { - GtkTreePath *path; - guint i; - guint r, n_visible_rows; - - node_validate_rows (model, G_MAXUINT, G_MAXUINT); - n_visible_rows = node_get_tree_row (model, model->files->len - 1) + 1; - model->n_nodes_valid = 0; - g_hash_table_remove_all (model->file_lookup); - _gtk_sort_array (get_node (model, 1), /* start at index 1; don't sort the editable row */ - model->files->len - 1, - model->node_size, - compare_array_element, + _gtk_sort_array (model->order->data, + model->order->len, + sizeof (guint), + compare_order_element, &data); - g_assert (model->n_nodes_valid == 0); - g_assert (g_hash_table_size (model->file_lookup) == 0); - if (n_visible_rows) - { - int *new_order = g_new (int, n_visible_rows); - - r = 0; - for (i = 0; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); - if (!node->visible) - { - node->row = r; - continue; - } - - new_order[r] = node->row - 1; - r++; - node->row = r; - } - g_assert (r == n_visible_rows); - path = gtk_tree_path_new (); - gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), - path, - NULL, - new_order); - gtk_tree_path_free (path); - g_free (new_order); - } + order_update_positions_from (model, 0); + visible_order_rebuild_after_sort (model); } - model->sort_on_thaw = FALSE; + model->resort_pending = FALSE; } -static void -gtk_file_system_model_sort_node (GtkFileSystemModel *model, guint node) -{ - /* FIXME: improve */ - gtk_file_system_model_sort (model); -} static gboolean gtk_file_system_model_get_sort_column_id (GtkTreeSortable *sortable, @@ -1023,6 +1155,280 @@ enum { static guint file_system_model_signals[LAST_SIGNAL] = { 0 }; +static void +pending_monitor_change_free (gpointer data) +{ + PendingMonitorChange *change = data; + + g_object_unref (change->file); + g_free (change); +} + +static void +gtk_file_system_model_prepare_merge (GtkFileSystemModel *model) +{ + guint n = model->order->len; + + model->sort_mid = MIN (model->sort_left + model->sort_width, n); + model->sort_right = MIN (model->sort_left + 2 * model->sort_width, n); + model->sort_i = model->sort_left; + model->sort_j = model->sort_mid; + model->sort_k = model->sort_left; +} + +static void +gtk_file_system_model_prepare_visible (GtkFileSystemModel *model) +{ + FileModelNode *editable = get_node (model, 0); + + g_array_set_size (model->visible_order, 0); + if (editable->visible) + { + guint id = 0; + + editable->visible_pos = 0; + g_array_append_val (model->visible_order, id); + } + + model->sort_left = 0; + model->finalize_phase = MODEL_FINALIZE_VISIBLE; + model->refilter_pending = FALSE; +} + +static void +gtk_file_system_model_restart_initial_sort (GtkFileSystemModel *model) +{ + SortData data; + guint n = model->order->len; + + model->resort_pending = FALSE; + + if (sort_data_init (&data, model)) + { + if (model->sort_scratch == NULL) + model->sort_scratch = + g_array_sized_new (FALSE, FALSE, sizeof (guint), n); + g_array_set_size (model->sort_scratch, n); + + model->sort_width = 1; + model->sort_left = 0; + gtk_file_system_model_prepare_merge (model); + model->finalize_phase = MODEL_FINALIZE_MERGE; + } + else + gtk_file_system_model_prepare_visible (model); +} + +static void +gtk_file_system_model_schedule_work (GtkFileSystemModel *model) +{ + GSourceFunc func; + + if (model->drain_source != 0 || model->editing_hold) + return; + + if (model->pending_files != NULL) + func = gtk_file_system_model_drain; + else if (model->finalize_phase != MODEL_FINALIZE_NONE) + func = gtk_file_system_model_finalize_load; + else + return; + + model->drain_source = + gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE, + func, + g_object_ref (model), + g_object_unref); +} + +static void +gtk_file_system_model_begin_finalize_load (GtkFileSystemModel *model, + GError *error) +{ + g_assert (model->directory_loading); + g_assert (model->finalize_phase == MODEL_FINALIZE_NONE); + g_assert (model->pending_files == NULL); + + g_clear_error (&model->load_error); + model->load_error = error ? g_error_copy (error) : NULL; + + gtk_file_system_model_restart_initial_sort (model); + gtk_file_system_model_schedule_work (model); +} + +static gboolean +gtk_file_system_model_finalize_load (gpointer data) +{ + GtkFileSystemModel *model = data; + gint64 deadline = g_get_monotonic_time () + MODEL_DRAIN_SLICE_US; + guint processed = 0; + + if (model->editing_hold && model->directory_loading) + { + model->drain_source = 0; + return FALSE; + } + + if (model->resort_pending) + gtk_file_system_model_restart_initial_sort (model); + + while (TRUE) + { + guint n = model->order->len; + + if (model->finalize_phase == MODEL_FINALIZE_MERGE) + { + SortData data; + + if (!sort_data_init (&data, model)) + { + gtk_file_system_model_prepare_visible (model); + continue; + } + + while (model->sort_k < model->sort_right) + { + guint id; + + if (model->sort_i < model->sort_mid && + (model->sort_j >= model->sort_right || + compare_order_element + (&g_array_index (model->order, guint, model->sort_i), + &g_array_index (model->order, guint, model->sort_j), + &data) <= 0)) + id = g_array_index (model->order, guint, model->sort_i++); + else + id = g_array_index (model->order, guint, model->sort_j++); + + g_array_index (model->sort_scratch, guint, model->sort_k++) = id; + + if ((++processed % MODEL_FINALIZE_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + return TRUE; + } + + model->sort_left = model->sort_right; + if (model->sort_left < n) + { + gtk_file_system_model_prepare_merge (model); + continue; + } + + /* Make the completed pass canonical and reuse the old buffer. */ + { + GArray *tmp = model->order; + + model->order = model->sort_scratch; + model->sort_scratch = tmp; + } + + if (model->resort_pending) + { + gtk_file_system_model_restart_initial_sort (model); + continue; + } + + if (model->sort_width >= n / 2 + n % 2) + { + gtk_file_system_model_prepare_visible (model); + continue; + } + + model->sort_width *= 2; + model->sort_left = 0; + gtk_file_system_model_prepare_merge (model); + continue; + } + + if (model->finalize_phase == MODEL_FINALIZE_VISIBLE) + { + if (model->refilter_pending) + gtk_file_system_model_prepare_visible (model); + + while (model->sort_left < n) + { + guint pos = model->sort_left++; + guint id = g_array_index (model->order, guint, pos); + FileModelNode *node = get_node (model, id); + gboolean filtered_out; + gboolean visible; + + node->order_pos = pos; + filtered_out = node_should_be_filtered_out (model, id); + visible = node_should_be_visible (model, id, filtered_out); + + node->filtered_out = filtered_out; + node->visible = visible; + + if (visible) + { + node->visible_pos = model->visible_order->len; + g_array_append_val (model->visible_order, id); + } + + if ((++processed % MODEL_FINALIZE_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + return TRUE; + } + + if (model->resort_pending) + { + gtk_file_system_model_restart_initial_sort (model); + continue; + } + + if (model->refilter_pending) + { + gtk_file_system_model_prepare_visible (model); + continue; + } + + model->directory_loading = FALSE; + model->refilter_pending = FALSE; + model->resort_pending = FALSE; + g_clear_pointer (&model->sort_scratch, g_array_unref); + + model->finalize_phase = MODEL_FINALIZE_REPLAY; + if (g_get_monotonic_time () >= deadline) + return TRUE; + continue; + } + + if (model->finalize_phase == MODEL_FINALIZE_REPLAY) + { + while (model->pending_monitor_changes != NULL && + !g_queue_is_empty (model->pending_monitor_changes)) + { + PendingMonitorChange *change; + + change = g_queue_pop_head (model->pending_monitor_changes); + + gtk_file_system_model_apply_monitor_change (model, + change->file, + change->type); + pending_monitor_change_free (change); + + if ((++processed % MODEL_DRAIN_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + return TRUE; + } + + model->finalize_phase = MODEL_FINALIZE_NONE; + model->drain_source = 0; + + g_signal_emit (model, + file_system_model_signals[FINISHED_LOADING], + 0, model->load_error); + g_clear_error (&model->load_error); + + return FALSE; + } + + g_assert_not_reached (); + } +} + + G_DEFINE_TYPE_WITH_CODE (GtkFileSystemModel, _gtk_file_system_model, G_TYPE_OBJECT, @@ -1038,16 +1444,23 @@ gtk_file_system_model_dispose (GObject *object) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object); - if (model->dir_thaw_source) + if (model->drain_source != 0) { - g_source_remove (model->dir_thaw_source); - model->dir_thaw_source = 0; + g_source_remove (model->drain_source); + model->drain_source = 0; } g_cancellable_cancel (model->cancellable); + g_clear_pointer (&model->pending_monitor_queries, g_hash_table_destroy); + if (model->dir_monitor) g_file_monitor_cancel (model->dir_monitor); + g_list_free_full (model->pending_files, g_object_unref); + model->pending_files = NULL; + + g_clear_object (&model->dir_enumerator); + G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->dispose (object); } @@ -1058,35 +1471,41 @@ gtk_file_system_model_finalize (GObject *object) GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object); guint i; - for (i = 0; i < model->files->len; i++) + if (model->value_pages) { - int v; - - FileModelNode *node = get_node (model, i); - if (node->file) - g_object_unref (node->file); - if (node->info) - g_object_unref (node->info); - - for (v = 0; v < model->n_columns; v++) - if (G_VALUE_TYPE (&node->values[v]) != G_TYPE_INVALID) - g_value_unset (&node->values[v]); + for (i = 0; i < model->n_columns; i++) + if (model->value_pages[i]) + g_ptr_array_free (model->value_pages[i], TRUE); + g_free (model->value_pages); } - g_array_free (model->files, TRUE); + + g_clear_pointer (&model->files, g_array_unref); + + g_clear_pointer (&model->sort_scratch, g_array_unref); + g_clear_error (&model->load_error); + if (model->pending_monitor_changes) + g_queue_free_full (model->pending_monitor_changes, + pending_monitor_change_free); + + g_clear_pointer (&model->order, g_array_unref); + g_clear_pointer (&model->visible_order, g_array_unref); g_object_unref (model->cancellable); g_free (model->attributes); + if (model->dir) g_object_unref (model->dir); if (model->dir_monitor) g_object_unref (model->dir_monitor); + g_hash_table_destroy (model->file_lookup); + if (model->filter) g_object_unref (model->filter); g_slice_free1 (sizeof (GType) * model->n_columns, model->column_types); - _gtk_tree_data_list_header_free (model->sort_list); + if (model->default_sort_destroy) model->default_sort_destroy (model->default_sort_data); @@ -1133,95 +1552,113 @@ gtk_file_system_model_closed_enumerator (GObject *object, GAsyncResult *res, gpo g_file_enumerator_close_finish (G_FILE_ENUMERATOR (object), res, NULL); } + +static void +gtk_file_system_model_request_next_files (GtkFileSystemModel *model) +{ + if (model->dir_enumerator == NULL || + g_cancellable_is_cancelled (model->cancellable)) + return; + + g_file_enumerator_next_files_async (model->dir_enumerator, + FILES_PER_QUERY, + IO_PRIORITY, + model->cancellable, + gtk_file_system_model_got_files, + model); +} + static gboolean -thaw_func (gpointer data) +gtk_file_system_model_drain (gpointer data) { GtkFileSystemModel *model = data; + gint64 deadline; + guint processed = 0; - thaw_updates (model); - model->dir_thaw_source = 0; + if (model->editing_hold) + { + model->drain_source = 0; + return FALSE; + } + deadline = g_get_monotonic_time () + MODEL_DRAIN_SLICE_US; + + while (model->pending_files != NULL) + { + GList *link = model->pending_files; + GFileInfo *info = link->data; + const char *name; + + model->pending_files = + g_list_delete_link (model->pending_files, link); + + name = g_file_info_get_name (info); + if (name != NULL) + { + GFile *file = g_file_get_child (model->dir, name); + add_file (model, file, info); + g_object_unref (file); + } + + g_object_unref (info); + processed++; + + if ((processed % MODEL_DRAIN_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + break; + } + + if (model->pending_files != NULL) + return TRUE; + + model->drain_source = 0; + gtk_file_system_model_request_next_files (model); return FALSE; } static void -gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer data) +gtk_file_system_model_got_files (GObject *object, + GAsyncResult *res, + gpointer data) { GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object); - GtkFileSystemModel *model = data; - GList *walk, *files; + GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */ + GList *files; GError *error = NULL; - gdk_threads_enter (); - files = g_file_enumerator_next_files_finish (enumerator, res, &error); - if (files) + if (files != NULL) { - if (model->dir_thaw_source == 0) - { - freeze_updates (model); - model->dir_thaw_source = gdk_threads_add_timeout_full (IO_PRIORITY + 1, - 50, - thaw_func, - model, - NULL); - } - - for (walk = files; walk; walk = walk->next) - { - const char *name; - GFileInfo *info; - GFile *file; - - info = walk->data; - name = g_file_info_get_name (info); - if (name == NULL) - { - /* Shouldn't happen, but the APIs allow it */ - g_object_unref (info); - continue; - } - file = g_file_get_child (model->dir, name); - add_file (model, file, info); - g_object_unref (file); - g_object_unref (info); - } - g_list_free (files); - - g_file_enumerator_next_files_async (enumerator, - g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_got_files, - model); - } - else - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - { - g_file_enumerator_close_async (enumerator, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_closed_enumerator, - NULL); - if (model->dir_thaw_source != 0) - { - g_source_remove (model->dir_thaw_source); - model->dir_thaw_source = 0; - thaw_updates (model); - } - - g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error); - } - - if (error) - g_error_free (error); + gdk_threads_enter (); + g_assert (model->pending_files == NULL); + model->pending_files = files; + gtk_file_system_model_schedule_work (model); + gdk_threads_leave (); + return; } - gdk_threads_leave (); + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + gdk_threads_enter (); + + g_file_enumerator_close_async (enumerator, + IO_PRIORITY, + model->cancellable, + gtk_file_system_model_closed_enumerator, + NULL); + + g_clear_object (&model->dir_enumerator); + + gtk_file_system_model_begin_finalize_load (model, error); + gdk_threads_leave (); + } + + if (error) + g_error_free (error); } + static void gtk_file_system_model_query_done (GObject * object, GAsyncResult *res, @@ -1250,12 +1687,45 @@ gtk_file_system_model_query_done (GObject * object, gdk_threads_leave (); } + static void -gtk_file_system_model_monitor_change (GFileMonitor * monitor, - GFile * file, - GFile * other_file, - GFileMonitorEvent type, - GtkFileSystemModel *model) +gtk_file_system_model_monitor_query_done (GObject *object, + GAsyncResult *res, + gpointer data) +{ + PendingMonitorQuery *query = data; + GtkFileSystemModel *model = g_weak_ref_get (query); + GFileInfo *info = g_file_query_info_finish (G_FILE (object), res, NULL); + + if (model) + { + gdk_threads_enter (); + + if (model->pending_monitor_queries && + g_hash_table_lookup (model->pending_monitor_queries, + G_FILE (object)) == query) + { + g_hash_table_remove (model->pending_monitor_queries, + G_FILE (object)); + if (info) + _gtk_file_system_model_update_file (model, G_FILE (object), info); + } + + gdk_threads_leave (); + g_object_unref (model); + } + + g_clear_object (&info); + + g_weak_ref_clear (query); + g_free (query); +} + + +static void +gtk_file_system_model_apply_monitor_change (GtkFileSystemModel *model, + GFile *file, + GFileMonitorEvent type) { switch (type) { @@ -1263,21 +1733,43 @@ gtk_file_system_model_monitor_change (GFileMonitor * monitor, case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: /* We can treat all of these the same way */ - g_file_query_info_async (file, - model->attributes, - G_FILE_QUERY_INFO_NONE, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_query_done, - model); + { + PendingMonitorQuery *query; + + query = g_new (PendingMonitorQuery, 1); + g_weak_ref_init (query, G_OBJECT (model)); + + gdk_threads_enter (); + + if (model->pending_monitor_queries == NULL) + model->pending_monitor_queries = + g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + (GDestroyNotify) g_object_unref, NULL); + + g_hash_table_replace (model->pending_monitor_queries, + g_object_ref (file), query); + + gdk_threads_leave (); + + g_file_query_info_async (file, + model->attributes, + G_FILE_QUERY_INFO_NONE, + IO_PRIORITY, + model->cancellable, + gtk_file_system_model_monitor_query_done, + query); + } break; + case G_FILE_MONITOR_EVENT_DELETED: - gdk_threads_enter (); + gdk_threads_enter (); + if (model->pending_monitor_queries) + g_hash_table_remove (model->pending_monitor_queries, file); remove_file (model, file); - gdk_threads_leave (); + gdk_threads_leave (); break; + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: - /* FIXME: use freeze/thaw with this somehow? */ case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: case G_FILE_MONITOR_EVENT_UNMOUNTED: default: @@ -1287,46 +1779,73 @@ gtk_file_system_model_monitor_change (GFileMonitor * monitor, } static void -gtk_file_system_model_got_enumerator (GObject *dir, GAsyncResult *res, gpointer data) +gtk_file_system_model_monitor_change (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent type, + GtkFileSystemModel *model) { - GtkFileSystemModel *model = data; + if (model->directory_loading || + model->drain_source != 0) + { + PendingMonitorChange *change; + + change = g_new (PendingMonitorChange, 1); + change->file = g_object_ref (file); + change->type = type; + + if (model->pending_monitor_changes == NULL) + model->pending_monitor_changes = g_queue_new (); + + g_queue_push_tail (model->pending_monitor_changes, change); + return; + } + + gtk_file_system_model_apply_monitor_change (model, file, type); +} + +static void +gtk_file_system_model_got_enumerator (GObject *object, + GAsyncResult *res, + gpointer data) +{ + GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */ GFileEnumerator *enumerator; GError *error = NULL; - gdk_threads_enter (); - - enumerator = g_file_enumerate_children_finish (G_FILE (dir), res, &error); + enumerator = g_file_enumerate_children_finish (G_FILE (object), res, &error); if (enumerator == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - { - g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error); + { + gdk_threads_enter (); + gtk_file_system_model_begin_finalize_load (model, error); + gdk_threads_leave (); + } + + if (error) g_error_free (error); - } - } - else - { - g_file_enumerator_next_files_async (enumerator, - g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_got_files, - model); - g_object_unref (enumerator); - model->dir_monitor = g_file_monitor_directory (model->dir, - G_FILE_MONITOR_NONE, - model->cancellable, - NULL); /* we don't mind if directory monitoring isn't supported, so the GError is NULL here */ - if (model->dir_monitor) - g_signal_connect (model->dir_monitor, - "changed", - G_CALLBACK (gtk_file_system_model_monitor_change), - model); + return; } + gdk_threads_enter (); + + g_assert (model->dir_enumerator == NULL); + model->dir_enumerator = enumerator; + + model->dir_monitor = g_file_monitor_directory (model->dir, + G_FILE_MONITOR_NONE, + model->cancellable, + NULL); + if (model->dir_monitor) + g_signal_connect (model->dir_monitor, "changed", + G_CALLBACK (gtk_file_system_model_monitor_change), model); + + gtk_file_system_model_request_next_files (model); gdk_threads_leave (); } + static void gtk_file_system_model_set_n_columns (GtkFileSystemModel *model, gint n_columns, @@ -1339,27 +1858,34 @@ gtk_file_system_model_set_n_columns (GtkFileSystemModel *model, model->n_columns = n_columns; model->column_types = g_slice_alloc (sizeof (GType) * n_columns); - - model->node_size = sizeof (FileModelNode) + sizeof (GValue) * (n_columns - 1); /* minus 1 because FileModelNode.values[] has a default size of 1 */ + model->value_pages = g_new0 (GPtrArray *, n_columns); for (i = 0; i < (guint) n_columns; i++) { GType type = va_arg (args, GType); - if (! _gtk_tree_data_list_check_type (type)) - { - g_error ("%s: type %s cannot be a column type for GtkFileSystemModel\n", G_STRLOC, g_type_name (type)); - return; /* not reached */ - } + if (!_gtk_tree_data_list_check_type (type)) + { + g_error ("%s: type %s cannot be a column type for GtkFileSystemModel\n", + G_STRLOC, g_type_name (type)); + return; + } model->column_types[i] = type; } - model->sort_list = _gtk_tree_data_list_header_new (n_columns, model->column_types); + model->sort_list = + _gtk_tree_data_list_header_new (n_columns, model->column_types); - model->files = g_array_sized_new (FALSE, FALSE, model->node_size, FILES_PER_QUERY); - /* add editable node at start */ + model->files = + g_array_sized_new (FALSE, TRUE, sizeof (FileModelNode), FILES_PER_QUERY); + g_array_set_clear_func (model->files, file_model_node_clear); + model->order = + g_array_sized_new (FALSE, FALSE, sizeof (guint), FILES_PER_QUERY); + model->visible_order = + g_array_sized_new (FALSE, FALSE, sizeof (guint), FILES_PER_QUERY); g_array_set_size (model->files, 1); - memset (get_node (model, 0), 0, model->node_size); + + get_node (model, 0)->generation = 1; } static void @@ -1371,6 +1897,7 @@ gtk_file_system_model_set_directory (GtkFileSystemModel *model, model->dir = g_object_ref (dir); model->attributes = g_strdup (attributes); + model->directory_loading = TRUE; g_file_enumerate_children_async (model->dir, attributes, @@ -1478,22 +2005,22 @@ gtk_file_system_model_refilter_all (GtkFileSystemModel *model) { guint i; - if (model->frozen) + if (model->directory_loading || model->editing_hold) { - model->filter_on_thaw = TRUE; + model->refilter_pending = TRUE; return; } - freeze_updates (model); + for (i = 0; i < model->order->len; i++) + { + guint id = g_array_index (model->order, guint, i); + node_compute_visibility_and_filters (model, id); + } - /* start at index 1, don't change the editable */ - for (i = 1; i < model->files->len; i++) - node_compute_visibility_and_filters (model, i); - - model->filter_on_thaw = FALSE; - thaw_updates (model); + model->refilter_pending = FALSE; } + /** * _gtk_file_system_model_set_show_hidden: * @model: a #GtkFileSystemModel @@ -1622,12 +2149,13 @@ _gtk_file_system_model_get_cancellable (GtkFileSystemModel *model) **/ gboolean _gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE); g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE); node = get_node (model, ITER_INDEX (iter)); return node->visible; @@ -1648,12 +2176,13 @@ _gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model, **/ gboolean _gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE); g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE); node = get_node (model, ITER_INDEX (iter)); return node->filtered_out; @@ -1678,12 +2207,13 @@ _gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model, **/ GFileInfo * _gtk_file_system_model_get_info (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL); g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); node = get_node (model, ITER_INDEX (iter)); g_assert (node->info == NULL || G_IS_FILE_INFO (node->info)); @@ -1704,11 +2234,13 @@ _gtk_file_system_model_get_info (GtkFileSystemModel *model, **/ GFile * _gtk_file_system_model_get_file (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); node = get_node (model, ITER_INDEX (iter)); return node->file; @@ -1733,63 +2265,43 @@ _gtk_file_system_model_get_file (GtkFileSystemModel *model, **/ const GValue * _gtk_file_system_model_get_value (GtkFileSystemModel *model, - GtkTreeIter * iter, + GtkTreeIter *iter, int column) { FileModelNode *node; + GValue *value; + guint id; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL); - g_return_val_if_fail (column >= 0 && (guint) column < model->n_columns, NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); + g_return_val_if_fail (column >= 0 && + (guint) column < model->n_columns, NULL); - node = get_node (model, ITER_INDEX (iter)); + id = ITER_INDEX (iter); + node = get_node (model, id); + value = value_cache_slot (model, id, column, TRUE); - if (!G_VALUE_TYPE (&node->values[column])) + if (G_VALUE_TYPE (value) == G_TYPE_INVALID) { - g_value_init (&node->values[column], model->column_types[column]); - if (!model->get_func (model, - node->file, - node->info, - column, - &node->values[column], - model->get_data)) + g_value_init (value, model->column_types[column]); + + if (!model->get_func (model, node->file, node->info, column, + value, model->get_data)) { - g_value_unset (&node->values[column]); + g_value_unset (value); return NULL; } } - return &node->values[column]; + return value; } static guint node_get_for_file (GtkFileSystemModel *model, - GFile * file) + GFile *file) { - guint i; - - i = GPOINTER_TO_UINT (g_hash_table_lookup (model->file_lookup, file)); - if (i != 0) - return i; - - /* Node 0 is the editable row and has no associated file or entry in the table, so we start counting from 1. - * - * The invariant here is that the files in model->files[n] for n < g_hash_table_size (model->file_lookup) - * are already added to the hash table. The table can get cleared when we re-sort; this loop merely rebuilds - * our (file -> index) mapping on demand. - * - * If we exit the loop, the next pending batch of mappings will be resolved when this function gets called again - * with another file that is not yet in the mapping. - */ - for (i = g_hash_table_size (model->file_lookup) + 1; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); - - g_hash_table_insert (model->file_lookup, node->file, GUINT_TO_POINTER (i)); - if (g_file_equal (node->file, file)) - return i; - } - - return 0; + return GPOINTER_TO_UINT (g_hash_table_lookup (model->file_lookup, file)); } /** @@ -1832,26 +2344,6 @@ _gtk_file_system_model_get_iter_for_file (GtkFileSystemModel *model, * if the index is equal or after the specified id. We use this to slide the * mappings up or down when a node is added or removed, respectively. */ -static void -adjust_file_lookup (GtkFileSystemModel *model, guint id, int increment) -{ - GHashTableIter iter; - gpointer key; - gpointer value; - - g_hash_table_iter_init (&iter, model->file_lookup); - - while (g_hash_table_iter_next (&iter, &key, &value)) - { - guint index = GPOINTER_TO_UINT (value); - - if (index >= id) - { - index += increment; - g_hash_table_iter_replace (&iter, GUINT_TO_POINTER (index)); - } - } -} /** * add_file: @@ -1864,30 +2356,71 @@ adjust_file_lookup (GtkFileSystemModel *model, guint id, int increment) **/ static void add_file (GtkFileSystemModel *model, - GFile *file, - GFileInfo *info) + GFile *file, + GFileInfo *info) { FileModelNode *node; + guint id; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (G_IS_FILE (file)); g_return_if_fail (G_IS_FILE_INFO (info)); - node = g_slice_alloc0 (model->node_size); + if (node_get_for_file (model, file) != 0) + return; + + id = node_allocate_slot (model); + node = get_node (model, id); node->file = g_object_ref (file); - if (info) - node->info = g_object_ref (info); - node->frozen_add = model->frozen ? TRUE : FALSE; + node->info = g_object_ref (info); - g_array_append_vals (model->files, node, 1); - g_slice_free1 (model->node_size, node); + g_hash_table_insert (model->file_lookup, + node->file, GUINT_TO_POINTER (id)); - if (!model->frozen) - node_compute_visibility_and_filters (model, model->files->len -1); + g_array_append_val (model->order, id); + node->order_pos = model->order->len - 1; - gtk_file_system_model_sort_node (model, model->files->len -1); + if (model->directory_loading) + return; + + if (model->editing_hold) + { + node->held_add = TRUE; + model->resort_pending = TRUE; + return; + } + + { + SortData data; + guint lo = 0; + guint hi = node->order_pos; + + if (sort_data_init (&data, model)) + { + while (lo < hi) + { + guint mid = lo + (hi - lo) / 2; + guint mid_id = g_array_index (model->order, guint, mid); + int cmp = compare_order_element (&id, &mid_id, &data); + + if (cmp < 0) + hi = mid; + else + lo = mid + 1; + } + + if (lo != node->order_pos) + { + g_array_remove_index (model->order, node->order_pos); + g_array_insert_val (model->order, lo, id); + order_update_positions_from (model, lo); + } + } + } + node_compute_visibility_and_filters (model, id); } + /** * remove_file: * @model: the model @@ -1899,12 +2432,12 @@ add_file (GtkFileSystemModel *model, **/ static void remove_file (GtkFileSystemModel *model, - GFile *file) + GFile *file) { FileModelNode *node; gboolean was_visible; + guint row = G_MAXUINT; guint id; - guint row; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (G_IS_FILE (file)); @@ -1915,20 +2448,22 @@ remove_file (GtkFileSystemModel *model, node = get_node (model, id); was_visible = node->visible; - row = node_get_tree_row (model, id); - node_invalidate_index (model, id); + if (was_visible) + { + row = node->visible_pos; + visible_remove_node (model, id); + } + g_assert (id != 0); + { + guint pos = node->order_pos; + + g_array_remove_index (model->order, pos); + order_update_positions_from (model, pos); + } g_hash_table_remove (model->file_lookup, file); - g_object_unref (node->file); - adjust_file_lookup (model, id, -1); - - if (node->info) - g_object_unref (node->info); - - g_array_remove_index (model->files, id); - - /* We don't need to resort, as removing a row doesn't change the sorting order of the other rows */ + node_retire_slot (model, id); if (was_visible) emit_row_deleted_for_row (model, row); @@ -1950,7 +2485,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, GFileInfo *info) { FileModelNode *node; - guint i, id; + guint id; GFileInfo *old_info; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); @@ -1961,7 +2496,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, if (id == 0) { add_file (model, file, info); - id = node_get_for_file (model, file); + return; } node = get_node (model, id); @@ -1972,11 +2507,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, if (old_info) g_object_unref (old_info); - for (i = 0; i < model->n_columns; i++) - { - if (G_VALUE_TYPE (&node->values[i])) - g_value_unset (&node->values[i]); - } + value_cache_clear_node (model, id); if (node->visible) emit_row_changed_for_node (model, id); @@ -2012,62 +2543,6 @@ _gtk_file_system_model_set_filter (GtkFileSystemModel *model, gtk_file_system_model_refilter_all (model); } -/** - * freeze_updates: - * @model: a #GtkFileSystemModel - * - * Freezes most updates on the model, so that performing multiple operations on - * the files in the model do not cause any events. Use thaw_updates() to resume - * proper operations. It is fine to call this function multiple times as long as - * freeze and thaw calls are balanced. - **/ -static void -freeze_updates (GtkFileSystemModel *model) -{ - g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - - model->frozen++; -} - -/** - * thaw_updates: - * @model: a #GtkFileSystemModel - * - * Undoes the effect of a previous call to freeze_updates() - **/ -static void -thaw_updates (GtkFileSystemModel *model) -{ - gboolean stuff_added; - - g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - g_return_if_fail (model->frozen > 0); - - model->frozen--; - if (model->frozen > 0) - return; - - stuff_added = get_node (model, model->files->len - 1)->frozen_add; - - if (model->filter_on_thaw) - gtk_file_system_model_refilter_all (model); - if (model->sort_on_thaw) - gtk_file_system_model_sort (model); - if (stuff_added) - { - guint i; - - for (i = 0; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); - - if (!node->frozen_add) - continue; - node->frozen_add = FALSE; - node_compute_visibility_and_filters (model, i); - } - } -} /** * _gtk_file_system_model_clear_cache: @@ -2082,16 +2557,17 @@ thaw_updates (GtkFileSystemModel *model) **/ void _gtk_file_system_model_clear_cache (GtkFileSystemModel *model, - int column) + int column) { - guint i; + guint8 *changed; int start, end; - gboolean changed; + int c; + guint i; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (column >= -1 && (guint) column < model->n_columns); - if (column > -1) + if (column >= 0) { start = column; end = column + 1; @@ -2102,42 +2578,75 @@ _gtk_file_system_model_clear_cache (GtkFileSystemModel *model, end = model->n_columns; } - for (i = 0; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); + changed = g_new0 (guint8, model->files->len); - changed = FALSE; - for (column = start; column < end; column++) + for (c = start; c < end; c++) + { + GPtrArray *pages = model->value_pages[c]; + guint page_index; + + if (pages == NULL) + continue; + + for (page_index = 0; page_index < pages->len; page_index++) { - if (!G_VALUE_TYPE (&node->values[column])) + ValuePage *page = pages->pdata[page_index]; + guint slot; + guint base; + + if (page == NULL) continue; - g_value_unset (&node->values[column]); - changed = TRUE; - } + base = page_index << VALUE_PAGE_SHIFT; + for (slot = 0; slot < VALUE_PAGE_SIZE; slot++) + { + guint id = base + slot; + GValue *value; - if (changed && node->visible) - emit_row_changed_for_node (model, i); + if (id >= model->files->len) + break; + + value = &page->values[slot]; + if (G_VALUE_TYPE (value) == G_TYPE_INVALID) + continue; + + g_value_unset (value); + changed[id] = TRUE; + } + } } + /* Emit at most one change signal for each visible row, in row order. */ + for (i = 0; i < model->visible_order->len; i++) + { + guint id = g_array_index (model->visible_order, guint, i); + if (changed[id]) + emit_row_changed_for_node (model, id); + } + + g_free (changed); + /* FIXME: resort? */ } + void _gtk_file_system_model_clear_cache_for_iter (GtkFileSystemModel *model, - GtkTreeIter *iter, - int column) + GtkTreeIter *iter, + int column) { FileModelNode *node; - int start, end; gboolean changed = FALSE; + guint id; + int start, end; + int c; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (iter != NULL); g_return_if_fail (ITER_IS_VALID (model, iter)); g_return_if_fail (column >= -1 && (guint) column < model->n_columns); - if (column > -1) + if (column >= 0) { start = column; end = column + 1; @@ -2148,19 +2657,14 @@ _gtk_file_system_model_clear_cache_for_iter (GtkFileSystemModel *model, end = model->n_columns; } - node = get_node (model, ITER_INDEX (iter)); + id = ITER_INDEX (iter); + node = get_node (model, id); - for (column = start; column < end; column++) - { - if (!G_VALUE_TYPE (&node->values[column])) - continue; - - g_value_unset (&node->values[column]); - changed = TRUE; - } + for (c = start; c < end; c++) + changed |= value_cache_clear_slot (model, id, c); if (changed && node->visible) - emit_row_changed_for_node (model, ITER_INDEX (iter)); + emit_row_changed_for_node (model, id); } /** @@ -2202,20 +2706,26 @@ _gtk_file_system_model_add_and_query_file (GtkFileSystemModel *model, * using this temporary row, call _gtk_file_system_model_remove_editable(). **/ void -_gtk_file_system_model_add_editable (GtkFileSystemModel *model, GtkTreeIter *iter) +_gtk_file_system_model_add_editable (GtkFileSystemModel *model, + GtkTreeIter *iter) { g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); + g_return_if_fail (iter != NULL); g_return_if_fail (!get_node (model, 0)->visible); node_set_visible_and_filtered_out (model, 0, TRUE, FALSE); ITER_INIT_FROM_INDEX (model, iter, 0); - /* we don't want file system changes to affect the model while - * editing is in place - */ - freeze_updates (model); + model->editing_hold = TRUE; + + if (model->directory_loading && model->drain_source != 0) + { + g_source_remove (model->drain_source); + model->drain_source = 0; + } } + /** * _gtk_file_system_model_remove_editable: * @model: a #GtkFileSystemModel @@ -2227,10 +2737,32 @@ _gtk_file_system_model_add_editable (GtkFileSystemModel *model, GtkTreeIter *ite void _gtk_file_system_model_remove_editable (GtkFileSystemModel *model) { + guint i; + g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - g_return_if_fail (get_node (model, 0)->visible); + g_return_if_fail (model->editing_hold); - thaw_updates (model); + model->editing_hold = FALSE; - node_set_visible_and_filtered_out (model, 0, FALSE, FALSE); + if (model->resort_pending) + gtk_file_system_model_sort (model); + if (model->refilter_pending) + gtk_file_system_model_refilter_all (model); + + for (i = 0; i < model->order->len; i++) + { + guint id = g_array_index (model->order, guint, i); + FileModelNode *node = get_node (model, id); + + if (!node->held_add) + continue; + + node->held_add = FALSE; + node_compute_visibility_and_filters (model, id); + } + + if (get_node (model, 0)->visible) + node_set_visible_and_filtered_out (model, 0, FALSE, FALSE); + + gtk_file_system_model_schedule_work (model); }