mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
gtk: add GSettings generic module and respect gtk-enable-primary-paste on gtk systems (#10328)
# Add GSettings Support for Primary Paste Implements support for `org.gnome.desktop.interface gtk-enable-primary-paste` to allow users to disable middle-click paste. Also refactors GTK Settings access into a reusable generic module. ## Changes - **NEW**: `src/apprt/gtk/gsettings.zig` - Generic GTK Settings reader supporting `bool` and `c_int` types, portal-aware for Flatpak/Snap - **MODIFIED**: `src/apprt/gtk/class/surface.zig` - Reads primary paste setting and refactors gtk-xft-dpi to use new module ## Behavior - Setting `false` → Middle-click paste blocked - Setting `true` or unavailable → Middle-click paste works (default) - Uses GTK Settings API which automatically uses XDG Desktop Portal in sandboxed environments Note: No unit tests added as this is a thin wrapper around GTK Settings API that's already tested indirectly through surface.zig. Happy to add tests if desired, though they would require an active display environment and skip on most CI systems.
This commit is contained in:
@@ -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,
|
||||
|
||||
101
src/apprt/gtk/gsettings.zig
Normal file
101
src/apprt/gtk/gsettings.zig
Normal file
@@ -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"));
|
||||
}
|
||||
Reference in New Issue
Block a user