apprt/gtk-ng: more actions

This commit is contained in:
Mitchell Hashimoto
2025-07-25 14:36:11 -07:00
parent 7d33d39a45
commit a25a0011ea
2 changed files with 133 additions and 4 deletions

View File

@ -8,7 +8,9 @@ const gobject = @import("gobject");
const gtk = @import("gtk");
const i18n = @import("../../../os/main.zig").i18n;
const input = @import("../../../input.zig");
const CoreSurface = @import("../../../Surface.zig");
const gtk_version = @import("../gtk_version.zig");
const adw_version = @import("../adw_version.zig");
const gresource = @import("../build/gresource.zig");
const Common = @import("../class.zig").Common;
@ -32,6 +34,30 @@ pub const Window = extern struct {
});
pub const properties = struct {
/// The active surface is the focus that should be receiving all
/// surface-targeted actions. This is usually the focused surface,
/// but may also not be focused if the user has selected a non-surface
/// widget.
pub const @"active-surface" = struct {
pub const name = "active-surface";
const impl = gobject.ext.defineProperty(
name,
Self,
?*Surface,
.{
.nick = "Active Surface",
.blurb = "The currently active surface.",
.accessor = gobject.ext.typedAccessor(
Self,
?*Surface,
.{
.getter = Self.getActiveSurface,
},
),
},
);
};
pub const config = struct {
pub const name = "config";
const impl = gobject.ext.defineProperty(
@ -140,12 +166,21 @@ pub const Window = extern struct {
// config and such can affect our bindings which ar setup initially
// in initTemplate.
self.syncAppearance();
// We need to do this so that the title initializes properly,
// I think because its a dynamic getter.
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
}
/// Setup our action map.
fn initActionMap(self: *Self) void {
const actions = .{
.{ "about", actionAbout, null },
.{ "close", actionClose, null },
.{ "copy", actionCopy, null },
.{ "paste", actionPaste, null },
.{ "reset", actionReset, null },
.{ "clear", actionClear, null },
};
const action_map = self.as(gio.ActionMap);
@ -177,9 +212,29 @@ pub const Window = extern struct {
self.as(gobject.Object).notifyByPspec(properties.@"headerbar-visible".impl.param_spec);
}
/// Perform a binding action on the window's active surface.
fn performBindingAction(
self: *Window,
action: input.Binding.Action,
) void {
const surface = self.getActiveSurface() orelse return;
const core_surface = surface.core() orelse return;
_ = core_surface.performBindingAction(action) catch |err| {
log.warn("error performing binding action error={}", .{err});
return;
};
}
//---------------------------------------------------------------
// Properties
/// Get the currently active surface. See the "active-surface" property.
/// This does not ref the value.
fn getActiveSurface(self: *Self) ?*Surface {
const priv = self.private();
return priv.surface;
}
fn getHeaderbarVisible(self: *Self) bool {
// TODO: CSD/SSD
// TODO: QuickTerminal
@ -226,6 +281,38 @@ pub const Window = extern struct {
self.syncAppearance();
}
fn propMenuActive(
button: *gtk.MenuButton,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
// Debian 12 is stuck on GTK 4.8
if (!gtk_version.atLeast(4, 10, 0)) return;
// We only care if we're activating. If we're activating then
// we need to check the validity of our menu items.
const active = button.getActive() != 0;
if (!active) return;
const has_selection = selection: {
const surface = self.getActiveSurface() orelse
break :selection false;
const core_surface = surface.core() orelse
break :selection false;
break :selection core_surface.hasSelection();
};
const action_map: *gio.ActionMap = gobject.ext.cast(
gio.ActionMap,
self,
) orelse return;
const action: *gio.SimpleAction = gobject.ext.cast(
gio.SimpleAction,
action_map.lookupAction("copy") orelse return,
) orelse return;
action.setEnabled(@intFromBool(has_selection));
}
//---------------------------------------------------------------
// Virtual methods
@ -334,6 +421,47 @@ pub const Window = extern struct {
}
}
fn actionClose(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Self,
) callconv(.c) void {
// TODO: Confirmation
self.as(gtk.Window).destroy();
}
fn actionCopy(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.c) void {
self.performBindingAction(.copy_to_clipboard);
}
fn actionPaste(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.c) void {
self.performBindingAction(.paste_from_clipboard);
}
fn actionReset(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.c) void {
self.performBindingAction(.reset);
}
fn actionClear(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.c) void {
self.performBindingAction(.clear_screen);
}
const C = Common(Self, Private);
pub const as = C.as;
pub const ref = C.ref;
@ -359,6 +487,7 @@ pub const Window = extern struct {
// Properties
gobject.ext.registerProperties(class, &.{
properties.@"active-surface".impl,
properties.config.impl,
properties.debug.impl,
properties.@"headerbar-visible".impl,
@ -374,6 +503,7 @@ pub const Window = extern struct {
class.bindTemplateCallback("notify_config", &propConfig);
class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
class.bindTemplateCallback("notify_maximized", &propMaximized);
class.bindTemplateCallback("notify_menu_active", &propMenuActive);
// Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose);

View File

@ -14,9 +14,7 @@ template $GhosttyWindow: Adw.ApplicationWindow {
// GTK4 grabs F10 input by default to focus the menubar icon. We want
// to disable this so that terminal programs can capture F10 (such as htop)
handle-menubar-accel: false;
// A default, this will get overwritten by the terminal application
// pretty quickly, usually.
title: "Ghostty";
title: bind (template.active-surface as <$GhosttySurface>).title;
content: Box {
orientation: vertical;
@ -25,12 +23,13 @@ template $GhosttyWindow: Adw.ApplicationWindow {
visible: bind template.headerbar-visible;
title-widget: Adw.WindowTitle {
title: bind surface.title;
title: bind (template.active-surface as <$GhosttySurface>).title;
};
[end]
Gtk.Box {
Gtk.MenuButton {
notify::active => $notify_menu_active();
icon-name: "open-menu-symbolic";
menu-model: main_menu;
tooltip-text: _("Main Menu");