diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index d1eb144e9..5c3bf18b6 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,9 @@ 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. + gtk_enable_primary_paste: bool = true, + pub var offset: c_int = 0; }; @@ -1511,19 +1515,17 @@ 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.get(.@"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: // 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; } @@ -1767,6 +1769,9 @@ pub const Surface = extern struct { priv.im_composing = false; priv.im_len = 0; + // 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 // of these types matter. @@ -2685,6 +2690,11 @@ pub const Surface = extern struct { // Report the event const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); + + if (button == .middle and !priv.gtk_enable_primary_paste) { + return; + } + const consumed = consumed: { const gtk_mods = event.getModifierState(); const mods = gtk_key.translateMods(gtk_mods); @@ -2736,6 +2746,10 @@ pub const Surface = extern struct { const gtk_mods = event.getModifierState(); const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); + if (button == .middle and !priv.gtk_enable_primary_paste) { + 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..8cf7f12d2 --- /dev/null +++ b/src/apprt/gtk/gsettings.zig @@ -0,0 +1,101 @@ +const std = @import("std"); +const gtk = @import("gtk"); +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", + + 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.Type()) { + bool => c_int, + c_int => c_int, + []const u8 => ?[*:0]const u8, + else => @compileError("Unsupported type for GTK settings"), + }; + } + + /// Returns true if this setting type requires memory allocation. + /// Types that do not need allocation must be explicitly marked. + fn requiresAllocation(comptime self: Key) bool { + const T = self.Type(); + return switch (T) { + bool, c_int => false, + else => true, + }; + } +}; + +/// 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"); + } + const settings = gtk.Settings.getDefault() orelse return null; + return getImpl(settings, null, key) catch unreachable; +} + +/// 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); +} + +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(@tagName(key).ptr, &value); + + return switch (key.Type()) { + bool => value.getInt() != 0, + c_int => value.getInt(), + []const u8 => blk: { + const alloc = allocator.?; + const ptr = value.getString() orelse break :blk null; + const str = std.mem.span(ptr); + break :blk try alloc.dupe(u8, str); + }, + else => @compileError("Unsupported type for GTK settings"), + }; +} + +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 "@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")); +}