From 78a503491e619ea9c69ee307167bc398f9f4cc28 Mon Sep 17 00:00:00 2001 From: evertonstz Date: Thu, 15 Jan 2026 10:53:00 -0300 Subject: [PATCH 01/12] initial commit --- src/apprt/gtk/class/surface.zig | 33 ++++++++++++++++++++++++++------ src/apprt/gtk/gsettings.zig | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 src/apprt/gtk/gsettings.zig diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index d1eb144e9..8f219677d 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -19,6 +19,7 @@ const terminal = @import("../../../terminal/main.zig"); const CoreSurface = @import("../../../Surface.zig"); const gresource = @import("../build/gresource.zig"); const ext = @import("../ext.zig"); +const gsettings = @import("../gsettings.zig"); const gtk_key = @import("../key.zig"); const ApprtSurface = @import("../Surface.zig"); const Common = @import("../class.zig").Common; @@ -674,6 +675,11 @@ pub const Surface = extern struct { /// The context for this surface (window, tab, or split) context: apprt.surface.NewSurfaceContext = .window, + /// Whether primary paste (middle-click paste) is enabled via GNOME settings. + /// If null, the setting could not be read (non-GNOME system or schema missing). + /// If true, middle-click paste is enabled. If false, it's disabled. + gtk_enable_primary_paste: ?bool = null, + pub var offset: c_int = 0; }; @@ -1511,12 +1517,10 @@ pub const Surface = extern struct { const xft_dpi_scale = xft_scale: { // gtk-xft-dpi is font DPI multiplied by 1024. See // https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html - const settings = gtk.Settings.getDefault() orelse break :xft_scale 1.0; - var value = std.mem.zeroes(gobject.Value); - defer value.unset(); - _ = value.init(gobject.ext.typeFor(c_int)); - settings.as(gobject.Object).getProperty("gtk-xft-dpi", &value); - const gtk_xft_dpi = value.getInt(); + const gtk_xft_dpi = gsettings.readSetting(c_int, "gtk-xft-dpi") orelse { + log.warn("gtk-xft-dpi was not set, using default value", .{}); + break :xft_scale 1.0; + }; // Use a value of 1.0 for the XFT DPI scale if the setting is <= 0 // See: @@ -1767,6 +1771,10 @@ pub const Surface = extern struct { priv.im_composing = false; priv.im_len = 0; + // Read GNOME desktop interface settings for primary paste (middle-click) + // This is only relevant on Linux systems with GNOME settings available + priv.gtk_enable_primary_paste = gsettings.readSetting(bool, "gtk-enable-primary-paste"); + // Set up to handle items being dropped on our surface. Files can be dropped // from Nautilus and strings can be dropped from many programs. The order // of these types matter. @@ -2685,6 +2693,13 @@ pub const Surface = extern struct { // Report the event const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); + + // Check if middle button paste should be disabled based on GNOME settings + // If gtk_enable_primary_paste is explicitly false, skip processing middle button + if (button == .middle and priv.gtk_enable_primary_paste == false) { + return; + } + const consumed = consumed: { const gtk_mods = event.getModifierState(); const mods = gtk_key.translateMods(gtk_mods); @@ -2736,6 +2751,12 @@ pub const Surface = extern struct { const gtk_mods = event.getModifierState(); const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); + // Check if middle button paste should be disabled based on GNOME settings + // If gtk_enable_primary_paste is explicitly false, skip processing middle button + if (button == .middle and priv.gtk_enable_primary_paste == false) { + return; + } + const mods = gtk_key.translateMods(gtk_mods); const consumed = surface.mouseButtonCallback( .release, diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig new file mode 100644 index 000000000..1e9f14a1f --- /dev/null +++ b/src/apprt/gtk/gsettings.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const gtk = @import("gtk"); +const gobject = @import("gobject"); + +/// Reads a GTK setting using the GTK Settings API. +/// This automatically uses XDG Desktop Portal in Flatpak environments. +/// Returns null if not on a GTK-supported platform or if the setting cannot be read. +/// +/// Supported platforms: Linux, FreeBSD +/// Supported types: bool, c_int +/// +/// Example usage: +/// const enabled = readSetting(bool, "gtk-enable-primary-paste"); +/// const dpi = readSetting(c_int, "gtk-xft-dpi"); +pub fn readSetting(comptime T: type, key: [*:0]const u8) ?T { + // Only available on systems that use GTK (Linux, FreeBSD) + if (comptime builtin.os.tag != .linux and builtin.os.tag != .freebsd) return null; + + const settings = gtk.Settings.getDefault() orelse return null; + + // For bool and c_int, we use c_int as the underlying GObject type + // because GTK boolean properties are stored as integers + var value = gobject.ext.Value.new(c_int); + defer value.unset(); + + settings.as(gobject.Object).getProperty(key, &value); + + return switch (T) { + bool => value.getInt() != 0, + c_int => value.getInt(), + else => @compileError("Unsupported type '" ++ @typeName(T) ++ "' for GTK setting. Supported types: bool, c_int"), + }; +} From 7a306e52c2b4ca4bef038e1c9f576ffaea5b52d9 Mon Sep 17 00:00:00 2001 From: Everton Correia <1169768+evertonstz@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:49:23 -0300 Subject: [PATCH 02/12] Update src/apprt/gtk/class/surface.zig Co-authored-by: Leah Amelia Chen --- src/apprt/gtk/class/surface.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 8f219677d..cb7ac7d5f 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -2753,7 +2753,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and priv.gtk_enable_primary_paste == false) { + if (button == .middle and !priv.gtk_enable_primary_paste) { return; } From 29adcf4b6481d849896d9d0fe9bbcb61dc786e38 Mon Sep 17 00:00:00 2001 From: evertonstz Date: Thu, 15 Jan 2026 17:16:11 -0300 Subject: [PATCH 03/12] Enhance GTK settings handling with well-defined types and utility functions --- src/apprt/gtk/gsettings.zig | 147 +++++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 18 deletions(-) diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig index 1e9f14a1f..2964f3b62 100644 --- a/src/apprt/gtk/gsettings.zig +++ b/src/apprt/gtk/gsettings.zig @@ -3,32 +3,143 @@ const builtin = @import("builtin"); const gtk = @import("gtk"); const gobject = @import("gobject"); -/// Reads a GTK setting using the GTK Settings API. +/// GTK Settings keys with well-defined types. +pub const Key = enum { + gtk_enable_primary_paste, + gtk_xft_dpi, + gtk_font_name, + + fn Type(comptime self: Key) type { + return switch (self) { + .gtk_enable_primary_paste => bool, + .gtk_xft_dpi => c_int, + .gtk_font_name => []const u8, + }; + } + + fn GValueType(comptime self: Key) type { + return switch (self) { + // Booleans are stored as integers in GTK's internal representation + .gtk_enable_primary_paste, + => c_int, + + // Integer types + .gtk_xft_dpi, + => c_int, + + // String types (returned as null-terminated C strings from GTK) + .gtk_font_name, + => ?[*:0]const u8, + }; + } + + fn propertyName(comptime self: Key) [*:0]const u8 { + return switch (self) { + .gtk_enable_primary_paste => "gtk-enable-primary-paste", + .gtk_xft_dpi => "gtk-xft-dpi", + .gtk_font_name => "gtk-font-name", + }; + } + + /// Returns true if this setting type requires memory allocation. + /// this is defensive: types that do not need allocation need to be + /// explicitly marked here + fn requiresAllocation(comptime self: Key) bool { + const T = self.Type(); + return switch (T) { + bool, c_int => false, + else => true, + }; + } +}; + +/// Reads a GTK setting using the GTK Settings API for non-allocating types. /// This automatically uses XDG Desktop Portal in Flatpak environments. -/// Returns null if not on a GTK-supported platform or if the setting cannot be read. /// -/// Supported platforms: Linux, FreeBSD -/// Supported types: bool, c_int +/// No allocator is required or used. Returns null if the setting is not available or cannot be read. /// /// Example usage: -/// const enabled = readSetting(bool, "gtk-enable-primary-paste"); -/// const dpi = readSetting(c_int, "gtk-xft-dpi"); -pub fn readSetting(comptime T: type, key: [*:0]const u8) ?T { - // Only available on systems that use GTK (Linux, FreeBSD) - if (comptime builtin.os.tag != .linux and builtin.os.tag != .freebsd) return null; - +/// const enabled = get(.gtk_enable_primary_paste); +/// const dpi = get(.gtk_xft_dpi); +pub fn get(comptime key: Key) ?key.Type() { + comptime { + if (key.requiresAllocation()) { + @compileError("Allocating types require an allocator; use getAlloc() instead"); + } + } const settings = gtk.Settings.getDefault() orelse return null; + return getImpl(settings, null, key) catch unreachable; +} - // For bool and c_int, we use c_int as the underlying GObject type - // because GTK boolean properties are stored as integers - var value = gobject.ext.Value.new(c_int); +/// Reads a GTK setting using the GTK Settings API, allocating if necessary. +/// This automatically uses XDG Desktop Portal in Flatpak environments. +/// +/// The caller must free any returned allocated memory with the provided allocator. +/// Returns null if the setting is not available or cannot be read. +/// May return an allocation error if memory allocation fails. +/// +/// Example usage: +/// const theme = try getAlloc(allocator, .gtk_theme_name); +/// defer if (theme) |t| allocator.free(t); +pub fn getAlloc(allocator: std.mem.Allocator, comptime key: Key) !?key.Type() { + const settings = gtk.Settings.getDefault() orelse return null; + return getImpl(settings, allocator, key); +} + +/// Shared implementation for reading GTK settings. +/// If allocator is null, only non-allocating types can be used. +/// Note: When adding a new type, research if it requires allocation (strings and boxed types do) +/// if allocation is NOT needed, list it inside the switch statement in the function requiresAllocation() +fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: Key) !?key.Type() { + const GValType = key.GValueType(); + var value = gobject.ext.Value.new(GValType); defer value.unset(); - settings.as(gobject.Object).getProperty(key, &value); + settings.as(gobject.Object).getProperty(key.propertyName(), &value); - return switch (T) { - bool => value.getInt() != 0, - c_int => value.getInt(), - else => @compileError("Unsupported type '" ++ @typeName(T) ++ "' for GTK setting. Supported types: bool, c_int"), + return switch (key) { + // Booleans are stored as integers in GTK, convert to bool + .gtk_enable_primary_paste, + => value.getInt() != 0, + + // Integer types are returned directly + .gtk_xft_dpi, + => value.getInt(), + + // Strings: GTK owns the GValue's pointer, so we must duplicate it + // before the GValue is destroyed by defer value.unset() + .gtk_font_name, + => blk: { + // This is defensive: we have already checked at compile-time that + // an allocator is provided for allocating types + const alloc = allocator.?; + const ptr = value.getString() orelse break :blk null; + const str = std.mem.span(ptr); + break :blk try alloc.dupe(u8, str); + }, }; } + +test "Key.Type returns correct types" { + try std.testing.expectEqual(bool, Key.gtk_enable_primary_paste.Type()); + try std.testing.expectEqual(c_int, Key.gtk_xft_dpi.Type()); + try std.testing.expectEqual([]const u8, Key.gtk_font_name.Type()); +} + +test "Key.requiresAllocation identifies allocating types" { + try std.testing.expectEqual(false, Key.gtk_enable_primary_paste.requiresAllocation()); + try std.testing.expectEqual(false, Key.gtk_xft_dpi.requiresAllocation()); + try std.testing.expectEqual(true, Key.gtk_font_name.requiresAllocation()); +} + +test "Key.GValueType returns correct GObject types" { + try std.testing.expectEqual(c_int, Key.gtk_enable_primary_paste.GValueType()); + try std.testing.expectEqual(c_int, Key.gtk_xft_dpi.GValueType()); + try std.testing.expectEqual(?[*:0]const u8, Key.gtk_font_name.GValueType()); +} + +test "Key.propertyName returns correct GTK property names" { + try std.testing.expectEqualSlices(u8, "gtk-enable-primary-paste", std.mem.span(Key.gtk_enable_primary_paste.propertyName())); + try std.testing.expectEqualSlices(u8, "gtk-xft-dpi", std.mem.span(Key.gtk_xft_dpi.propertyName())); + try std.testing.expectEqualSlices(u8, "gtk-font-name", std.mem.span(Key.gtk_font_name.propertyName())); +} From db7df92a81adb4c1ad9951afc0009d0dcb5f2cec Mon Sep 17 00:00:00 2001 From: evertonstz Date: Thu, 15 Jan 2026 18:15:31 -0300 Subject: [PATCH 04/12] Refactor gsettings usage for gtk-xft-dpi and gtk-enable-primary-paste with improved logging --- src/apprt/gtk/class/surface.zig | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index cb7ac7d5f..7ba4b9c51 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1517,7 +1517,7 @@ pub const Surface = extern struct { const xft_dpi_scale = xft_scale: { // gtk-xft-dpi is font DPI multiplied by 1024. See // https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html - const gtk_xft_dpi = gsettings.readSetting(c_int, "gtk-xft-dpi") orelse { + const gtk_xft_dpi = gsettings.get(.gtk_xft_dpi) orelse { log.warn("gtk-xft-dpi was not set, using default value", .{}); break :xft_scale 1.0; }; @@ -1527,7 +1527,7 @@ pub const Surface = extern struct { // https://gitlab.gnome.org/GNOME/libadwaita/-/commit/a7738a4d269bfdf4d8d5429ca73ccdd9b2450421 // https://gitlab.gnome.org/GNOME/libadwaita/-/commit/9759d3fd81129608dd78116001928f2aed974ead if (gtk_xft_dpi <= 0) { - log.warn("gtk-xft-dpi was not set, using default value", .{}); + log.warn("gtk-xft-dpi has invalid value ({}), using default", .{gtk_xft_dpi}); break :xft_scale 1.0; } @@ -1773,7 +1773,10 @@ pub const Surface = extern struct { // Read GNOME desktop interface settings for primary paste (middle-click) // This is only relevant on Linux systems with GNOME settings available - priv.gtk_enable_primary_paste = gsettings.readSetting(bool, "gtk-enable-primary-paste"); + priv.gtk_enable_primary_paste = gsettings.get(.gtk_enable_primary_paste) orelse blk: { + log.warn("gtk-enable-primary-paste was not set, using default value", .{}); + break :blk false; + }; // Set up to handle items being dropped on our surface. Files can be dropped // from Nautilus and strings can be dropped from many programs. The order @@ -2696,7 +2699,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and priv.gtk_enable_primary_paste == false) { + if (button == .middle and !(priv.gtk_enable_primary_paste orelse true)) { return; } @@ -2753,7 +2756,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and !priv.gtk_enable_primary_paste) { + if (button == .middle and !(priv.gtk_enable_primary_paste orelse false)) { return; } From 04a7bcd138dcbc69a8b62294b67d813253b98b24 Mon Sep 17 00:00:00 2001 From: evertonstz Date: Thu, 15 Jan 2026 18:20:44 -0300 Subject: [PATCH 05/12] Fix middle button paste condition to respect GNOME settings --- src/apprt/gtk/class/surface.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 7ba4b9c51..360ce3f88 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -2756,7 +2756,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and !(priv.gtk_enable_primary_paste orelse false)) { + if (button == .middle and !(priv.gtk_enable_primary_paste orelse true)) { return; } From c553296d7a8721e9445b7369771d81de09fa2bc8 Mon Sep 17 00:00:00 2001 From: evertonstz Date: Thu, 15 Jan 2026 19:08:37 -0300 Subject: [PATCH 06/12] Remove unused import of 'builtin' in gsettings.zig --- src/apprt/gtk/gsettings.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig index 2964f3b62..97b671c7a 100644 --- a/src/apprt/gtk/gsettings.zig +++ b/src/apprt/gtk/gsettings.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const builtin = @import("builtin"); const gtk = @import("gtk"); const gobject = @import("gobject"); From 60da5eb3a671bb6889adc6c76b090fdb148c003b Mon Sep 17 00:00:00 2001 From: Everton Correia <1169768+evertonstz@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:25:49 -0300 Subject: [PATCH 07/12] Apply suggestion from @pluiedev Co-authored-by: Leah Amelia Chen --- src/apprt/gtk/gsettings.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig index 97b671c7a..f96e9f01c 100644 --- a/src/apprt/gtk/gsettings.zig +++ b/src/apprt/gtk/gsettings.zig @@ -61,10 +61,8 @@ pub const Key = enum { /// const enabled = get(.gtk_enable_primary_paste); /// const dpi = get(.gtk_xft_dpi); pub fn get(comptime key: Key) ?key.Type() { - comptime { - if (key.requiresAllocation()) { - @compileError("Allocating types require an allocator; use getAlloc() instead"); - } + if (comptime key.requiresAllocation()) { + @compileError("Allocating types require an allocator; use getAlloc() instead"); } const settings = gtk.Settings.getDefault() orelse return null; return getImpl(settings, null, key) catch unreachable; From 1c2c2257d4aff4924afdb2c152106de8d7df89fa Mon Sep 17 00:00:00 2001 From: evertonstz Date: Fri, 16 Jan 2026 12:29:31 -0300 Subject: [PATCH 08/12] Set default value for gtk_enable_primary_paste to true and simplify condition checks --- src/apprt/gtk/class/surface.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 360ce3f88..ea5ca203f 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -676,9 +676,8 @@ pub const Surface = extern struct { context: apprt.surface.NewSurfaceContext = .window, /// Whether primary paste (middle-click paste) is enabled via GNOME settings. - /// If null, the setting could not be read (non-GNOME system or schema missing). /// If true, middle-click paste is enabled. If false, it's disabled. - gtk_enable_primary_paste: ?bool = null, + gtk_enable_primary_paste: ?bool = true, pub var offset: c_int = 0; }; @@ -2699,7 +2698,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and !(priv.gtk_enable_primary_paste orelse true)) { + if (button == .middle and !(priv.gtk_enable_primary_paste)) { return; } @@ -2756,7 +2755,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and !(priv.gtk_enable_primary_paste orelse true)) { + if (button == .middle and !(priv.gtk_enable_primary_paste)) { return; } From bff21222d438a7454b8d55c1e0afc72d85be599a Mon Sep 17 00:00:00 2001 From: evertonstz Date: Tue, 20 Jan 2026 12:12:04 -0300 Subject: [PATCH 09/12] Refactor gsettings keys to use string literals for GTK settings and update related tests --- src/apprt/gtk/class/surface.zig | 4 +-- src/apprt/gtk/gsettings.zig | 64 +++++++++++++++------------------ 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index ea5ca203f..56c39a86f 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1516,7 +1516,7 @@ pub const Surface = extern struct { const xft_dpi_scale = xft_scale: { // gtk-xft-dpi is font DPI multiplied by 1024. See // https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html - const gtk_xft_dpi = gsettings.get(.gtk_xft_dpi) orelse { + const gtk_xft_dpi = gsettings.get(.@"gtk-xft-dpi") orelse { log.warn("gtk-xft-dpi was not set, using default value", .{}); break :xft_scale 1.0; }; @@ -1772,7 +1772,7 @@ pub const Surface = extern struct { // Read GNOME desktop interface settings for primary paste (middle-click) // This is only relevant on Linux systems with GNOME settings available - priv.gtk_enable_primary_paste = gsettings.get(.gtk_enable_primary_paste) orelse blk: { + priv.gtk_enable_primary_paste = gsettings.get(.@"gtk-enable-primary-paste") orelse blk: { log.warn("gtk-enable-primary-paste was not set, using default value", .{}); break :blk false; }; diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig index f96e9f01c..3fc3d01de 100644 --- a/src/apprt/gtk/gsettings.zig +++ b/src/apprt/gtk/gsettings.zig @@ -4,42 +4,34 @@ const gobject = @import("gobject"); /// GTK Settings keys with well-defined types. pub const Key = enum { - gtk_enable_primary_paste, - gtk_xft_dpi, - gtk_font_name, + @"gtk-enable-primary-paste", + @"gtk-xft-dpi", + @"gtk-font-name", fn Type(comptime self: Key) type { return switch (self) { - .gtk_enable_primary_paste => bool, - .gtk_xft_dpi => c_int, - .gtk_font_name => []const u8, + .@"gtk-enable-primary-paste" => bool, + .@"gtk-xft-dpi" => c_int, + .@"gtk-font-name" => []const u8, }; } fn GValueType(comptime self: Key) type { return switch (self) { // Booleans are stored as integers in GTK's internal representation - .gtk_enable_primary_paste, + .@"gtk-enable-primary-paste", => c_int, // Integer types - .gtk_xft_dpi, + .@"gtk-xft-dpi", => c_int, // String types (returned as null-terminated C strings from GTK) - .gtk_font_name, + .@"gtk-font-name", => ?[*:0]const u8, }; } - fn propertyName(comptime self: Key) [*:0]const u8 { - return switch (self) { - .gtk_enable_primary_paste => "gtk-enable-primary-paste", - .gtk_xft_dpi => "gtk-xft-dpi", - .gtk_font_name => "gtk-font-name", - }; - } - /// Returns true if this setting type requires memory allocation. /// this is defensive: types that do not need allocation need to be /// explicitly marked here @@ -58,8 +50,8 @@ pub const Key = enum { /// No allocator is required or used. Returns null if the setting is not available or cannot be read. /// /// Example usage: -/// const enabled = get(.gtk_enable_primary_paste); -/// const dpi = get(.gtk_xft_dpi); +/// const enabled = get(.@"gtk-enable-primary-paste"); +/// const dpi = get(.@"gtk-xft-dpi"); pub fn get(comptime key: Key) ?key.Type() { if (comptime key.requiresAllocation()) { @compileError("Allocating types require an allocator; use getAlloc() instead"); @@ -92,20 +84,20 @@ fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: var value = gobject.ext.Value.new(GValType); defer value.unset(); - settings.as(gobject.Object).getProperty(key.propertyName(), &value); + settings.as(gobject.Object).getProperty(@tagName(key).ptr, &value); return switch (key) { // Booleans are stored as integers in GTK, convert to bool - .gtk_enable_primary_paste, + .@"gtk-enable-primary-paste", => value.getInt() != 0, // Integer types are returned directly - .gtk_xft_dpi, + .@"gtk-xft-dpi", => value.getInt(), // Strings: GTK owns the GValue's pointer, so we must duplicate it // before the GValue is destroyed by defer value.unset() - .gtk_font_name, + .@"gtk-font-name", => blk: { // This is defensive: we have already checked at compile-time that // an allocator is provided for allocating types @@ -118,25 +110,25 @@ fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: } test "Key.Type returns correct types" { - try std.testing.expectEqual(bool, Key.gtk_enable_primary_paste.Type()); - try std.testing.expectEqual(c_int, Key.gtk_xft_dpi.Type()); - try std.testing.expectEqual([]const u8, Key.gtk_font_name.Type()); + try std.testing.expectEqual(bool, Key.@"gtk-enable-primary-paste".Type()); + try std.testing.expectEqual(c_int, Key.@"gtk-xft-dpi".Type()); + try std.testing.expectEqual([]const u8, Key.@"gtk-font-name".Type()); } test "Key.requiresAllocation identifies allocating types" { - try std.testing.expectEqual(false, Key.gtk_enable_primary_paste.requiresAllocation()); - try std.testing.expectEqual(false, Key.gtk_xft_dpi.requiresAllocation()); - try std.testing.expectEqual(true, Key.gtk_font_name.requiresAllocation()); + try std.testing.expectEqual(false, Key.@"gtk-enable-primary-paste".requiresAllocation()); + try std.testing.expectEqual(false, Key.@"gtk-xft-dpi".requiresAllocation()); + try std.testing.expectEqual(true, Key.@"gtk-font-name".requiresAllocation()); } test "Key.GValueType returns correct GObject types" { - try std.testing.expectEqual(c_int, Key.gtk_enable_primary_paste.GValueType()); - try std.testing.expectEqual(c_int, Key.gtk_xft_dpi.GValueType()); - try std.testing.expectEqual(?[*:0]const u8, Key.gtk_font_name.GValueType()); + try std.testing.expectEqual(c_int, Key.@"gtk-enable-primary-paste".GValueType()); + try std.testing.expectEqual(c_int, Key.@"gtk-xft-dpi".GValueType()); + try std.testing.expectEqual(?[*:0]const u8, Key.@"gtk-font-name".GValueType()); } -test "Key.propertyName returns correct GTK property names" { - try std.testing.expectEqualSlices(u8, "gtk-enable-primary-paste", std.mem.span(Key.gtk_enable_primary_paste.propertyName())); - try std.testing.expectEqualSlices(u8, "gtk-xft-dpi", std.mem.span(Key.gtk_xft_dpi.propertyName())); - try std.testing.expectEqualSlices(u8, "gtk-font-name", std.mem.span(Key.gtk_font_name.propertyName())); +test "@tagName returns correct GTK property names" { + try std.testing.expectEqualStrings("gtk-enable-primary-paste", @tagName(Key.@"gtk-enable-primary-paste")); + try std.testing.expectEqualStrings("gtk-xft-dpi", @tagName(Key.@"gtk-xft-dpi")); + try std.testing.expectEqualStrings("gtk-font-name", @tagName(Key.@"gtk-font-name")); } From 7b6147aa28280d4c761db42fe69fcca0a0ab2169 Mon Sep 17 00:00:00 2001 From: evertonstz Date: Tue, 20 Jan 2026 14:23:10 -0300 Subject: [PATCH 10/12] Refactor GValueType and getImpl functions to use type-based switches for improved clarity and maintainability --- src/apprt/gtk/gsettings.zig | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig index 3fc3d01de..43a1467c6 100644 --- a/src/apprt/gtk/gsettings.zig +++ b/src/apprt/gtk/gsettings.zig @@ -17,18 +17,11 @@ pub const Key = enum { } fn GValueType(comptime self: Key) type { - return switch (self) { - // Booleans are stored as integers in GTK's internal representation - .@"gtk-enable-primary-paste", - => c_int, - - // Integer types - .@"gtk-xft-dpi", - => c_int, - - // String types (returned as null-terminated C strings from GTK) - .@"gtk-font-name", - => ?[*:0]const u8, + return switch (self.Type()) { + bool => c_int, // Booleans are stored as integers in GTK's internal representation + c_int => c_int, + []const u8 => ?[*:0]const u8, // Strings (returned as null-terminated C strings from GTK) + else => @compileError("Unsupported type for GTK settings"), }; } @@ -86,19 +79,12 @@ fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: settings.as(gobject.Object).getProperty(@tagName(key).ptr, &value); - return switch (key) { - // Booleans are stored as integers in GTK, convert to bool - .@"gtk-enable-primary-paste", - => value.getInt() != 0, - - // Integer types are returned directly - .@"gtk-xft-dpi", - => value.getInt(), - - // Strings: GTK owns the GValue's pointer, so we must duplicate it - // before the GValue is destroyed by defer value.unset() - .@"gtk-font-name", - => blk: { + return switch (key.Type()) { + bool => value.getInt() != 0, // Booleans are stored as integers in GTK, convert to bool + c_int => value.getInt(), // Integer types are returned directly + []const u8 => blk: { + // Strings: GTK owns the GValue's pointer, so we must duplicate it + // before the GValue is destroyed by defer value.unset() // This is defensive: we have already checked at compile-time that // an allocator is provided for allocating types const alloc = allocator.?; @@ -106,6 +92,7 @@ fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: const str = std.mem.span(ptr); break :blk try alloc.dupe(u8, str); }, + else => @compileError("Unsupported type for GTK settings"), }; } From bc067fc782ed32b5c20a25bbee061debf0ad1612 Mon Sep 17 00:00:00 2001 From: evertonstz Date: Tue, 20 Jan 2026 14:23:22 -0300 Subject: [PATCH 11/12] Refactor gtk_enable_primary_paste to remove optional type and simplify condition checks --- src/apprt/gtk/class/surface.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 56c39a86f..c439a10c3 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -677,7 +677,7 @@ pub const Surface = extern struct { /// Whether primary paste (middle-click paste) is enabled via GNOME settings. /// If true, middle-click paste is enabled. If false, it's disabled. - gtk_enable_primary_paste: ?bool = true, + gtk_enable_primary_paste: bool = true, pub var offset: c_int = 0; }; @@ -2698,7 +2698,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and !(priv.gtk_enable_primary_paste)) { + if (button == .middle and !priv.gtk_enable_primary_paste) { return; } @@ -2755,7 +2755,7 @@ pub const Surface = extern struct { // Check if middle button paste should be disabled based on GNOME settings // If gtk_enable_primary_paste is explicitly false, skip processing middle button - if (button == .middle and !(priv.gtk_enable_primary_paste)) { + if (button == .middle and !priv.gtk_enable_primary_paste) { return; } From 8c9891b5de515dfe8f9717441437123f205a2b63 Mon Sep 17 00:00:00 2001 From: evertonstz Date: Tue, 20 Jan 2026 14:32:47 -0300 Subject: [PATCH 12/12] Refactor primary paste settings handling and documentation for clarity --- src/apprt/gtk/class/surface.zig | 15 +++-------- src/apprt/gtk/gsettings.zig | 44 +++++++++------------------------ 2 files changed, 15 insertions(+), 44 deletions(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index c439a10c3..5c3bf18b6 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -675,8 +675,7 @@ pub const Surface = extern struct { /// The context for this surface (window, tab, or split) context: apprt.surface.NewSurfaceContext = .window, - /// Whether primary paste (middle-click paste) is enabled via GNOME settings. - /// If true, middle-click paste is enabled. If false, it's disabled. + /// Whether primary paste (middle-click paste) is enabled. gtk_enable_primary_paste: bool = true, pub var offset: c_int = 0; @@ -1770,12 +1769,8 @@ pub const Surface = extern struct { priv.im_composing = false; priv.im_len = 0; - // Read GNOME desktop interface settings for primary paste (middle-click) - // This is only relevant on Linux systems with GNOME settings available - priv.gtk_enable_primary_paste = gsettings.get(.@"gtk-enable-primary-paste") orelse blk: { - log.warn("gtk-enable-primary-paste was not set, using default value", .{}); - break :blk false; - }; + // Read GTK primary paste setting + priv.gtk_enable_primary_paste = gsettings.get(.@"gtk-enable-primary-paste") orelse true; // Set up to handle items being dropped on our surface. Files can be dropped // from Nautilus and strings can be dropped from many programs. The order @@ -2696,8 +2691,6 @@ pub const Surface = extern struct { // Report the event const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); - // Check if middle button paste should be disabled based on GNOME settings - // If gtk_enable_primary_paste is explicitly false, skip processing middle button if (button == .middle and !priv.gtk_enable_primary_paste) { return; } @@ -2753,8 +2746,6 @@ pub const Surface = extern struct { const gtk_mods = event.getModifierState(); const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); - // Check if middle button paste should be disabled based on GNOME settings - // If gtk_enable_primary_paste is explicitly false, skip processing middle button if (button == .middle and !priv.gtk_enable_primary_paste) { return; } diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig index 43a1467c6..8cf7f12d2 100644 --- a/src/apprt/gtk/gsettings.zig +++ b/src/apprt/gtk/gsettings.zig @@ -18,16 +18,15 @@ pub const Key = enum { fn GValueType(comptime self: Key) type { return switch (self.Type()) { - bool => c_int, // Booleans are stored as integers in GTK's internal representation + bool => c_int, c_int => c_int, - []const u8 => ?[*:0]const u8, // Strings (returned as null-terminated C strings from GTK) + []const u8 => ?[*:0]const u8, else => @compileError("Unsupported type for GTK settings"), }; } /// Returns true if this setting type requires memory allocation. - /// this is defensive: types that do not need allocation need to be - /// explicitly marked here + /// Types that do not need allocation must be explicitly marked. fn requiresAllocation(comptime self: Key) bool { const T = self.Type(); return switch (T) { @@ -37,14 +36,9 @@ pub const Key = enum { } }; -/// Reads a GTK setting using the GTK Settings API for non-allocating types. -/// This automatically uses XDG Desktop Portal in Flatpak environments. -/// -/// No allocator is required or used. Returns null if the setting is not available or cannot be read. -/// -/// Example usage: -/// const enabled = get(.@"gtk-enable-primary-paste"); -/// const dpi = get(.@"gtk-xft-dpi"); +/// Reads a GTK setting for non-allocating types. +/// Automatically uses XDG Desktop Portal in Flatpak environments. +/// Returns null if the setting is unavailable. pub fn get(comptime key: Key) ?key.Type() { if (comptime key.requiresAllocation()) { @compileError("Allocating types require an allocator; use getAlloc() instead"); @@ -53,25 +47,15 @@ pub fn get(comptime key: Key) ?key.Type() { return getImpl(settings, null, key) catch unreachable; } -/// Reads a GTK setting using the GTK Settings API, allocating if necessary. -/// This automatically uses XDG Desktop Portal in Flatpak environments. -/// -/// The caller must free any returned allocated memory with the provided allocator. -/// Returns null if the setting is not available or cannot be read. -/// May return an allocation error if memory allocation fails. -/// -/// Example usage: -/// const theme = try getAlloc(allocator, .gtk_theme_name); -/// defer if (theme) |t| allocator.free(t); +/// Reads a GTK setting, allocating memory if necessary. +/// Automatically uses XDG Desktop Portal in Flatpak environments. +/// Caller must free returned memory with the provided allocator. +/// Returns null if the setting is unavailable. pub fn getAlloc(allocator: std.mem.Allocator, comptime key: Key) !?key.Type() { const settings = gtk.Settings.getDefault() orelse return null; return getImpl(settings, allocator, key); } -/// Shared implementation for reading GTK settings. -/// If allocator is null, only non-allocating types can be used. -/// Note: When adding a new type, research if it requires allocation (strings and boxed types do) -/// if allocation is NOT needed, list it inside the switch statement in the function requiresAllocation() fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: Key) !?key.Type() { const GValType = key.GValueType(); var value = gobject.ext.Value.new(GValType); @@ -80,13 +64,9 @@ fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: settings.as(gobject.Object).getProperty(@tagName(key).ptr, &value); return switch (key.Type()) { - bool => value.getInt() != 0, // Booleans are stored as integers in GTK, convert to bool - c_int => value.getInt(), // Integer types are returned directly + bool => value.getInt() != 0, + c_int => value.getInt(), []const u8 => blk: { - // Strings: GTK owns the GValue's pointer, so we must duplicate it - // before the GValue is destroyed by defer value.unset() - // This is defensive: we have already checked at compile-time that - // an allocator is provided for allocating types const alloc = allocator.?; const ptr = value.getString() orelse break :blk null; const str = std.mem.span(ptr);