From 4ad749607aaafdba457aa21e5a5645c19976b341 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 8 Feb 2025 15:56:29 -0600 Subject: [PATCH 1/6] core: performAction now returns a bool This is to facilitate the `performable:` prefix on keybinds that are implemented using app runtime actions. --- src/App.zig | 24 +++++------ src/Surface.zig | 92 +++++++++++++++++++++--------------------- src/apprt/embedded.zig | 19 +++++---- src/apprt/glfw.zig | 7 +++- src/apprt/gtk/App.zig | 7 +++- 5 files changed, 79 insertions(+), 70 deletions(-) diff --git a/src/App.zig b/src/App.zig index a6b54db23..15859d115 100644 --- a/src/App.zig +++ b/src/App.zig @@ -161,7 +161,7 @@ pub fn updateConfig(self: *App, rt_app: *apprt.App, config: *const Config) !void const applied: *const configpkg.Config = if (applied_) |*c| c else config; // Notify the apprt that the app has changed configuration. - try rt_app.performAction( + _ = try rt_app.performAction( .app, .config_change, .{ .config = applied }, @@ -180,7 +180,7 @@ pub fn addSurface( // Since we have non-zero surfaces, we can cancel the quit timer. // It is up to the apprt if there is a quit timer at all and if it // should be canceled. - rt_surface.app.performAction( + _ = rt_surface.app.performAction( .app, .quit_timer, .stop, @@ -214,7 +214,7 @@ pub fn deleteSurface(self: *App, rt_surface: *apprt.Surface) void { // If we have no surfaces, we can start the quit timer. It is up to the // apprt to determine if this is necessary. - if (self.surfaces.items.len == 0) rt_surface.app.performAction( + if (self.surfaces.items.len == 0) _ = rt_surface.app.performAction( .app, .quit_timer, .start, @@ -294,7 +294,7 @@ pub fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void { break :target .app; }; - try rt_app.performAction( + _ = try rt_app.performAction( target, .new_window, {}, @@ -419,7 +419,7 @@ pub fn colorSchemeEvent( // Request our configuration be reloaded because the new scheme may // impact the colors of the app. - try rt_app.performAction( + _ = try rt_app.performAction( .app, .reload_config, .{ .soft = true }, @@ -437,13 +437,13 @@ pub fn performAction( switch (action) { .unbind => unreachable, .ignore => {}, - .quit => try rt_app.performAction(.app, .quit, {}), - .new_window => try self.newWindow(rt_app, .{ .parent = null }), - .open_config => try rt_app.performAction(.app, .open_config, {}), - .reload_config => try rt_app.performAction(.app, .reload_config, .{}), - .close_all_windows => try rt_app.performAction(.app, .close_all_windows, {}), - .toggle_quick_terminal => try rt_app.performAction(.app, .toggle_quick_terminal, {}), - .toggle_visibility => try rt_app.performAction(.app, .toggle_visibility, {}), + .quit => _ = try rt_app.performAction(.app, .quit, {}), + .new_window => _ = try self.newWindow(rt_app, .{ .parent = null }), + .open_config => _ = try rt_app.performAction(.app, .open_config, {}), + .reload_config => _ = try rt_app.performAction(.app, .reload_config, .{}), + .close_all_windows => _ = try rt_app.performAction(.app, .close_all_windows, {}), + .toggle_quick_terminal => _ = try rt_app.performAction(.app, .toggle_quick_terminal, {}), + .toggle_visibility => _ = try rt_app.performAction(.app, .toggle_visibility, {}), } } diff --git a/src/Surface.zig b/src/Surface.zig index e7e8e20af..13436f9ff 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -569,7 +569,7 @@ pub fn init( errdefer self.io.deinit(); // Report initial cell size on surface creation - try rt_app.performAction( + _ = try rt_app.performAction( .{ .surface = self }, .cell_size, .{ .width = size.cell.width, .height = size.cell.height }, @@ -581,7 +581,7 @@ pub fn init( const min_window_width_cells: u32 = 10; const min_window_height_cells: u32 = 4; - try rt_app.performAction( + _ = try rt_app.performAction( .{ .surface = self }, .size_limit, .{ @@ -645,7 +645,7 @@ pub fn init( size.padding.top + size.padding.bottom; - rt_app.performAction( + _ = rt_app.performAction( .{ .surface = self }, .initial_size, .{ .width = final_width, .height = final_height }, @@ -657,7 +657,7 @@ pub fn init( } if (config.title) |title| { - try rt_app.performAction( + _ = try rt_app.performAction( .{ .surface = self }, .set_title, .{ .title = title }, @@ -678,7 +678,7 @@ pub fn init( break :xdg; }; defer alloc.free(title); - try rt_app.performAction( + _ = try rt_app.performAction( .{ .surface = self }, .set_title, .{ .title = title }, @@ -831,7 +831,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { // We know that our title should end in 0. const slice = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(v)), 0); log.debug("changing title \"{s}\"", .{slice}); - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .set_title, .{ .title = slice }, @@ -867,7 +867,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { .color_change => |change| { // Notify our apprt, but don't send a mode 2031 DSR report // because VT sequences were used to change the color. - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .color_change, .{ @@ -886,7 +886,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { .set_mouse_shape => |shape| { log.debug("changing mouse shape: {}", .{shape}); - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_shape, shape, @@ -918,7 +918,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { const str = try self.alloc.dupeZ(u8, w.slice()); defer self.alloc.free(str); - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .pwd, .{ .pwd = str }, @@ -969,7 +969,7 @@ fn passwordInput(self: *Surface, v: bool) !void { } // Notify our apprt so it can do whatever it wants. - self.rt_app.performAction( + _ = self.rt_app.performAction( .{ .surface = self }, .secure_input, if (v) .on else .off, @@ -1058,7 +1058,7 @@ fn mouseRefreshLinks( self.renderer_state.mouse.point = pos_vp; self.mouse.over_link = true; self.renderer_state.terminal.screen.dirty.hyperlink_hover = true; - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_shape, .pointer, @@ -1071,7 +1071,7 @@ fn mouseRefreshLinks( .trim = false, }); defer self.alloc.free(str); - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_over_link, .{ .url = str }, @@ -1085,7 +1085,7 @@ fn mouseRefreshLinks( log.warn("failed to get URI for OSC8 hyperlink", .{}); break :link; }; - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_over_link, .{ .url = uri }, @@ -1095,12 +1095,12 @@ fn mouseRefreshLinks( try self.queueRender(); } else if (over_link) { - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_shape, self.io.terminal.mouse_shape, ); - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_over_link, .{ .url = "" }, @@ -1112,7 +1112,7 @@ fn mouseRefreshLinks( /// Called when our renderer health state changes. fn updateRendererHealth(self: *Surface, health: renderer.Health) void { log.warn("renderer health status change status={}", .{health}); - self.rt_app.performAction( + _ = self.rt_app.performAction( .{ .surface = self }, .renderer_health, health, @@ -1124,7 +1124,7 @@ fn updateRendererHealth(self: *Surface, health: renderer.Health) void { /// This should be called anytime `config_conditional_state` changes /// so that the apprt can reload the configuration. fn notifyConfigConditionalState(self: *Surface) void { - self.rt_app.performAction( + _ = self.rt_app.performAction( .{ .surface = self }, .reload_config, .{ .soft = true }, @@ -1204,14 +1204,14 @@ pub fn updateConfig( // If we have a title set then we update our window to have the // newly configured title. - if (config.title) |title| try self.rt_app.performAction( + if (config.title) |title| _ = try self.rt_app.performAction( .{ .surface = self }, .set_title, .{ .title = title }, ); // Notify the window - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .config_change, .{ .config = config }, @@ -1478,7 +1478,7 @@ fn setCellSize(self: *Surface, size: renderer.CellSize) !void { self.io.queueMessage(.{ .resize = self.size }, .unlocked); // Notify the window - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .cell_size, .{ .width = size.width, .height = size.height }, @@ -1774,12 +1774,12 @@ pub fn keyCallback( }; } else if (self.io.terminal.flags.mouse_event != .none and !self.mouse.mods.shift) { // If we have mouse reports on and we don't have shift pressed, we reset state - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_shape, self.io.terminal.mouse_shape, ); - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_over_link, .{ .url = "" }, @@ -1797,7 +1797,7 @@ pub fn keyCallback( .mods = self.mouse.mods, .over_link = self.mouse.over_link, .hidden = self.mouse.hidden, - }).keyToMouseShape()) |shape| try self.rt_app.performAction( + }).keyToMouseShape()) |shape| _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_shape, shape, @@ -1922,7 +1922,7 @@ fn maybeHandleBinding( } // Start or continue our key sequence - self.rt_app.performAction( + _ = self.rt_app.performAction( .{ .surface = self }, .key_sequence, .{ .trigger = entry.key_ptr.* }, @@ -2031,7 +2031,7 @@ fn endKeySequence( mem: KeySequenceMemory, ) void { // Notify apprt key sequence ended - self.rt_app.performAction( + _ = self.rt_app.performAction( .{ .surface = self }, .key_sequence, .end, @@ -3367,12 +3367,12 @@ pub fn cursorPosCallback( self.mouse.link_point = null; if (self.mouse.over_link) { self.mouse.over_link = false; - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_shape, self.io.terminal.mouse_shape, ); - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .mouse_over_link, .{ .url = "" }, @@ -3798,7 +3798,7 @@ fn scrollToBottom(self: *Surface) !void { fn hideMouse(self: *Surface) void { if (self.mouse.hidden) return; self.mouse.hidden = true; - self.rt_app.performAction( + _ = self.rt_app.performAction( .{ .surface = self }, .mouse_visibility, .hidden, @@ -3810,7 +3810,7 @@ fn hideMouse(self: *Surface) void { fn showMouse(self: *Surface) void { if (!self.mouse.hidden) return; self.mouse.hidden = false; - self.rt_app.performAction( + _ = self.rt_app.performAction( .{ .surface = self }, .mouse_visibility, .visible, @@ -4101,13 +4101,13 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool v, ), - .new_tab => try self.rt_app.performAction( + .new_tab => return try self.rt_app.performAction( .{ .surface = self }, .new_tab, {}, ), - .close_tab => try self.rt_app.performAction( + .close_tab => return try self.rt_app.performAction( .{ .surface = self }, .close_tab, {}, @@ -4117,7 +4117,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .next_tab, .last_tab, .goto_tab, - => |v, tag| try self.rt_app.performAction( + => |v, tag| return try self.rt_app.performAction( .{ .surface = self }, .goto_tab, switch (tag) { @@ -4129,13 +4129,13 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, ), - .move_tab => |position| try self.rt_app.performAction( + .move_tab => |position| return try self.rt_app.performAction( .{ .surface = self }, .move_tab, .{ .amount = position }, ), - .new_split => |direction| try self.rt_app.performAction( + .new_split => |direction| return try self.rt_app.performAction( .{ .surface = self }, .new_split, switch (direction) { @@ -4150,7 +4150,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, ), - .goto_split => |direction| try self.rt_app.performAction( + .goto_split => |direction| return try self.rt_app.performAction( .{ .surface = self }, .goto_split, switch (direction) { @@ -4161,7 +4161,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, ), - .resize_split => |value| try self.rt_app.performAction( + .resize_split => |value| return try self.rt_app.performAction( .{ .surface = self }, .resize_split, .{ @@ -4175,25 +4175,25 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, ), - .equalize_splits => try self.rt_app.performAction( + .equalize_splits => return try self.rt_app.performAction( .{ .surface = self }, .equalize_splits, {}, ), - .toggle_split_zoom => try self.rt_app.performAction( + .toggle_split_zoom => return try self.rt_app.performAction( .{ .surface = self }, .toggle_split_zoom, {}, ), - .toggle_maximize => try self.rt_app.performAction( + .toggle_maximize => return try self.rt_app.performAction( .{ .surface = self }, .toggle_maximize, {}, ), - .toggle_fullscreen => try self.rt_app.performAction( + .toggle_fullscreen => return try self.rt_app.performAction( .{ .surface = self }, .toggle_fullscreen, switch (self.config.macos_non_native_fullscreen) { @@ -4203,19 +4203,19 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, ), - .toggle_window_decorations => try self.rt_app.performAction( + .toggle_window_decorations => return try self.rt_app.performAction( .{ .surface = self }, .toggle_window_decorations, {}, ), - .toggle_tab_overview => try self.rt_app.performAction( + .toggle_tab_overview => return try self.rt_app.performAction( .{ .surface = self }, .toggle_tab_overview, {}, ), - .toggle_secure_input => try self.rt_app.performAction( + .toggle_secure_input => return try self.rt_app.performAction( .{ .surface = self }, .secure_input, .toggle, @@ -4229,7 +4229,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } }, - .inspector => |mode| try self.rt_app.performAction( + .inspector => |mode| return try self.rt_app.performAction( .{ .surface = self }, .inspector, switch (mode) { @@ -4676,7 +4676,7 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const self.app.last_notification_time = now; self.app.last_notification_digest = new_digest; - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .desktop_notification, .{ @@ -4696,7 +4696,7 @@ fn crashThreadState(self: *Surface) crash.sentry.ThreadState { /// Tell the surface to present itself to the user. This may involve raising the /// window and switching tabs. fn presentSurface(self: *Surface) !void { - try self.rt_app.performAction( + _ = try self.rt_app.performAction( .{ .surface = self }, .present_terminal, {}, diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 864e00205..3174f161e 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -478,13 +478,14 @@ pub const App = struct { surface.queueInspectorRender(); } - /// Perform a given action. + /// Perform a given action. Returns `true` if the action was able to be + /// performed, `false` otherwise. pub fn performAction( self: *App, target: apprt.Target, comptime action: apprt.Action.Key, value: apprt.Action.Value(action), - ) !void { + ) !bool { // Special case certain actions before they are sent to the // embedded apprt. self.performPreAction(target, action, value); @@ -499,6 +500,8 @@ pub const App = struct { target.cval(), @unionInit(apprt.Action, @tagName(action), value).cval(), ); + + return true; } fn performPreAction( @@ -1006,7 +1009,7 @@ pub const Surface = struct { } fn queueInspectorRender(self: *Surface) void { - self.app.performAction( + _ = self.app.performAction( .{ .surface = &self.core_surface }, .render_inspector, {}, @@ -1457,7 +1460,7 @@ pub const CAPI = struct { /// Open the configuration. export fn ghostty_app_open_config(v: *App) void { - v.performAction(.app, .open_config, {}) catch |err| { + _ = v.performAction(.app, .open_config, {}) catch |err| { log.err("error reloading config err={}", .{err}); return; }; @@ -1800,7 +1803,7 @@ pub const CAPI = struct { /// Request that the surface split in the given direction. export fn ghostty_surface_split(ptr: *Surface, direction: apprt.action.SplitDirection) void { - ptr.app.performAction( + _ = ptr.app.performAction( .{ .surface = &ptr.core_surface }, .new_split, direction, @@ -1815,7 +1818,7 @@ pub const CAPI = struct { ptr: *Surface, direction: apprt.action.GotoSplit, ) void { - ptr.app.performAction( + _ = ptr.app.performAction( .{ .surface = &ptr.core_surface }, .goto_split, direction, @@ -1834,7 +1837,7 @@ pub const CAPI = struct { direction: apprt.action.ResizeSplit.Direction, amount: u16, ) void { - ptr.app.performAction( + _ = ptr.app.performAction( .{ .surface = &ptr.core_surface }, .resize_split, .{ .direction = direction, .amount = amount }, @@ -1846,7 +1849,7 @@ pub const CAPI = struct { /// Equalize the size of all splits in the current window. export fn ghostty_surface_split_equalize(ptr: *Surface) void { - ptr.app.performAction( + _ = ptr.app.performAction( .{ .surface = &ptr.core_surface }, .equalize_splits, {}, diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 729decc0f..bcb7ee5ba 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -147,13 +147,14 @@ pub const App = struct { glfw.postEmptyEvent(); } - /// Perform a given action. + /// Perform a given action. Returns `true` if the action was able to be + /// performed, `false` otherwise. pub fn performAction( self: *App, target: apprt.Target, comptime action: apprt.Action.Key, value: apprt.Action.Value(action), - ) !void { + ) !bool { switch (action) { .quit => self.quit = true, @@ -240,6 +241,8 @@ pub const App = struct { .toggle_maximize, => log.info("unimplemented action={}", .{action}), } + + return true; } /// Reload the configuration. This should return the new configuration. diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index ed27f8394..85ec80f47 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -507,13 +507,14 @@ pub fn terminate(self: *App) void { self.config.deinit(); } -/// Perform a given action. +/// Perform a given action. Returns `true` if the action was able to be +/// performed, `false` otherwise. pub fn performAction( self: *App, target: apprt.Target, comptime action: apprt.Action.Key, value: apprt.Action.Value(action), -) !void { +) !bool { switch (action) { .quit => self.quit(), .new_window => _ = try self.newWindow(switch (target) { @@ -561,6 +562,8 @@ pub fn performAction( .color_change, => log.warn("unimplemented action={}", .{action}), } + + return true; } fn newTab(_: *App, target: apprt.Target) !void { From 57e7565b7f05f466621d33487da382d0b8c7809e Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 8 Feb 2025 16:23:23 -0600 Subject: [PATCH 2/6] gtk: make goto_split a performable action --- src/apprt/gtk/App.zig | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 85ec80f47..e7974495a 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -531,7 +531,7 @@ pub fn performAction( .new_split => try self.newSplit(target, value), .resize_split => self.resizeSplit(target, value), .equalize_splits => self.equalizeSplits(target), - .goto_split => self.gotoSplit(target, value), + .goto_split => return self.gotoSplit(target, value), .open_config => try configpkg.edit.open(self.core_app.alloc), .config_change => self.configChange(target, value.config), .reload_config => try self.reloadConfig(target, value), @@ -671,18 +671,24 @@ fn gotoSplit( _: *const App, target: apprt.Target, direction: apprt.action.GotoSplit, -) void { +) bool { switch (target) { - .app => {}, + .app => { + return false; + }, .surface => |v| { - const s = v.rt_surface.container.split() orelse return; + const s = v.rt_surface.container.split() orelse return false; const map = s.directionMap(switch (v.rt_surface.container) { .split_tl => .top_left, .split_br => .bottom_right, .none, .tab_ => unreachable, }); - const surface_ = map.get(direction) orelse return; - if (surface_) |surface| surface.grabFocus(); + const surface_ = map.get(direction) orelse return false; + if (surface_) |surface| { + surface.grabFocus(); + return true; + } + return false; }, } } From 69fd438370fb7d92ed7b2ccfc940b471b437494c Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 8 Feb 2025 17:30:27 -0600 Subject: [PATCH 3/6] gtk: make previous_tab, next_tab, last_tab, and goto_tab performable --- src/apprt/gtk/App.zig | 14 ++++++++------ src/apprt/gtk/Surface.zig | 2 +- src/apprt/gtk/Window.zig | 29 ++++++++++++++++------------- src/apprt/gtk/notebook.zig | 23 ++++++++++++++--------- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index e7974495a..41bc755c2 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -526,7 +526,7 @@ pub fn performAction( .new_tab => try self.newTab(target), .close_tab => try self.closeTab(target), - .goto_tab => self.gotoTab(target, value), + .goto_tab => return self.gotoTab(target, value), .move_tab => self.moveTab(target, value), .new_split => try self.newSplit(target, value), .resize_split => self.resizeSplit(target, value), @@ -600,24 +600,26 @@ fn closeTab(_: *App, target: apprt.Target) !void { } } -fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) void { +fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) bool { switch (target) { - .app => {}, + .app => { + return false; + }, .surface => |v| { const window = v.rt_surface.container.window() orelse { log.info( "gotoTab invalid for container={s}", .{@tagName(v.rt_surface.container)}, ); - return; + return false; }; - switch (tab) { + return switch (tab) { .previous => window.gotoPreviousTab(v.rt_surface), .next => window.gotoNextTab(v.rt_surface), .last => window.gotoLastTab(), else => window.gotoTab(@intCast(@intFromEnum(tab))), - } + }; }, } } diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index b9f8949fb..37ffb26cb 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -2124,7 +2124,7 @@ pub fn present(self: *Surface) void { if (self.container.window()) |window| { if (self.container.tab()) |tab| { if (window.notebook.getTabPosition(tab)) |position| - window.notebook.gotoNthTab(position); + _ = window.notebook.gotoNthTab(position); } c.gtk_window_present(window.window); } diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index bb49165b9..3daeffe76 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -506,23 +506,25 @@ pub fn closeTab(self: *Window, tab: *Tab) void { } /// Go to the previous tab for a surface. -pub fn gotoPreviousTab(self: *Window, surface: *Surface) void { +pub fn gotoPreviousTab(self: *Window, surface: *Surface) bool { const tab = surface.container.tab() orelse { log.info("surface is not attached to a tab bar, cannot navigate", .{}); - return; + return false; }; - self.notebook.gotoPreviousTab(tab); + if (!self.notebook.gotoPreviousTab(tab)) return false; self.focusCurrentTab(); + return true; } /// Go to the next tab for a surface. -pub fn gotoNextTab(self: *Window, surface: *Surface) void { +pub fn gotoNextTab(self: *Window, surface: *Surface) bool { const tab = surface.container.tab() orelse { log.info("surface is not attached to a tab bar, cannot navigate", .{}); - return; + return false; }; - self.notebook.gotoNextTab(tab); + if (!self.notebook.gotoNextTab(tab)) return false; self.focusCurrentTab(); + return true; } /// Move the current tab for a surface. @@ -535,19 +537,20 @@ pub fn moveTab(self: *Window, surface: *Surface, position: c_int) void { } /// Go to the last tab for a surface. -pub fn gotoLastTab(self: *Window) void { +pub fn gotoLastTab(self: *Window) bool { const max = self.notebook.nPages(); - self.gotoTab(@intCast(max)); + return self.gotoTab(@intCast(max)); } /// Go to the specific tab index. -pub fn gotoTab(self: *Window, n: usize) void { - if (n == 0) return; +pub fn gotoTab(self: *Window, n: usize) bool { + if (n == 0) return false; const max = self.notebook.nPages(); - if (max == 0) return; - const page_idx = std.math.cast(c_int, n - 1) orelse return; - self.notebook.gotoNthTab(@min(page_idx, max - 1)); + if (max == 0) return false; + const page_idx = std.math.cast(c_int, n - 1) orelse return false; + if (!self.notebook.gotoNthTab(@min(page_idx, max - 1))) return false; self.focusCurrentTab(); + return true; } /// Toggle tab overview (if present) diff --git a/src/apprt/gtk/notebook.zig b/src/apprt/gtk/notebook.zig index 4676c2529..548f2acaf 100644 --- a/src/apprt/gtk/notebook.zig +++ b/src/apprt/gtk/notebook.zig @@ -59,11 +59,14 @@ pub const Notebook = union(enum) { }; } - pub fn gotoNthTab(self: *Notebook, position: c_int) void { + pub fn gotoNthTab(self: *Notebook, position: c_int) bool { + const current_page_ = self.currentPage(); + if (current_page_) |current_page| if (current_page == position) return false; switch (self.*) { .adw => |*adw| adw.gotoNthTab(position), .gtk => |*gtk| gtk.gotoNthTab(position), } + return true; } pub fn getTabPosition(self: *Notebook, tab: *Tab) ?c_int { @@ -73,8 +76,8 @@ pub const Notebook = union(enum) { }; } - pub fn gotoPreviousTab(self: *Notebook, tab: *Tab) void { - const page_idx = self.getTabPosition(tab) orelse return; + pub fn gotoPreviousTab(self: *Notebook, tab: *Tab) bool { + const page_idx = self.getTabPosition(tab) orelse return false; // The next index is the previous or we wrap around. const next_idx = if (page_idx > 0) page_idx - 1 else next_idx: { @@ -83,19 +86,21 @@ pub const Notebook = union(enum) { }; // Do nothing if we have one tab - if (next_idx == page_idx) return; + if (next_idx == page_idx) return false; - self.gotoNthTab(next_idx); + return self.gotoNthTab(next_idx); } - pub fn gotoNextTab(self: *Notebook, tab: *Tab) void { - const page_idx = self.getTabPosition(tab) orelse return; + pub fn gotoNextTab(self: *Notebook, tab: *Tab) bool { + const page_idx = self.getTabPosition(tab) orelse return false; const max = self.nPages() -| 1; const next_idx = if (page_idx < max) page_idx + 1 else 0; - if (next_idx == page_idx) return; - self.gotoNthTab(next_idx); + // Do nothing if we have one tab + if (next_idx == page_idx) return false; + + return self.gotoNthTab(next_idx); } pub fn moveTab(self: *Notebook, tab: *Tab, position: c_int) void { From cbf562ecb339abe16466e8a21900a821ab3917fe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 11 Feb 2025 16:38:12 -0800 Subject: [PATCH 4/6] apprt/embedded: make performAction return the performable state --- include/ghostty.h | 2 +- macos/Sources/Ghostty/Ghostty.App.swift | 11 ++++++++--- src/apprt/embedded.zig | 6 ++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 246fb9ed3..99276cf23 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -644,7 +644,7 @@ typedef void (*ghostty_runtime_write_clipboard_cb)(void*, ghostty_clipboard_e, bool); typedef void (*ghostty_runtime_close_surface_cb)(void*, bool); -typedef void (*ghostty_runtime_action_cb)(ghostty_app_t, +typedef bool (*ghostty_runtime_action_cb)(ghostty_app_t, ghostty_target_s, ghostty_action_s); diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 43c0f245a..a6b35f8cf 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -423,7 +423,7 @@ extension Ghostty { // MARK: Actions (macOS) - static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) { + static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool { // Make sure it a target we understand so all our action handlers can assert switch (target.tag) { case GHOSTTY_TARGET_APP, GHOSTTY_TARGET_SURFACE: @@ -431,7 +431,7 @@ extension Ghostty { default: Ghostty.logger.warning("unknown action target=\(target.tag.rawValue)") - return + return false } // Action dispatch @@ -541,10 +541,15 @@ extension Ghostty { fallthrough case GHOSTTY_ACTION_QUIT_TIMER: Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)") - + return false default: Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)") + return false } + + // If we reached here then we assume performed since all unknown actions + // are captured in the switch and return false. + return true } private static func quit(_ app: ghostty_app_t) { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 3174f161e..02bbda0d9 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -46,7 +46,7 @@ pub const App = struct { wakeup: *const fn (AppUD) callconv(.C) void, /// Callback called to handle an action. - action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.C) void, + action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.C) bool, /// Read the clipboard value. The return value must be preserved /// by the host until the next call. If there is no valid clipboard @@ -495,13 +495,11 @@ pub const App = struct { action, value, }); - self.opts.action( + return self.opts.action( self, target.cval(), @unionInit(apprt.Action, @tagName(action), value).cval(), ); - - return true; } fn performPreAction( From 927cb4e6225f6fbe9a7de4b7a0c9aa0e68e26481 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 11 Feb 2025 16:52:25 -0800 Subject: [PATCH 5/6] apprt/gtk: some stylistic changes --- src/apprt/gtk/App.zig | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 41bc755c2..f9a3ab160 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -560,9 +560,14 @@ pub fn performAction( .render_inspector, .renderer_health, .color_change, - => log.warn("unimplemented action={}", .{action}), + => { + log.warn("unimplemented action={}", .{action}); + return false; + }, } + // We can assume it was handled because all unknown/unimplemented actions + // are caught above. return true; } @@ -602,9 +607,7 @@ fn closeTab(_: *App, target: apprt.Target) !void { fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) bool { switch (target) { - .app => { - return false; - }, + .app => return false, .surface => |v| { const window = v.rt_surface.container.window() orelse { log.info( @@ -675,9 +678,7 @@ fn gotoSplit( direction: apprt.action.GotoSplit, ) bool { switch (target) { - .app => { - return false; - }, + .app => return false, .surface => |v| { const s = v.rt_surface.container.split() orelse return false; const map = s.directionMap(switch (v.rt_surface.container) { From f26c96d51b4f8add3ccd61fc48d6226dade437dd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 11 Feb 2025 16:52:58 -0800 Subject: [PATCH 6/6] apprt/glfw: return false for unimplemented actions --- src/apprt/glfw.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index bcb7ee5ba..cb034cd86 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -239,7 +239,10 @@ pub const App = struct { .pwd, .config_change, .toggle_maximize, - => log.info("unimplemented action={}", .{action}), + => { + log.info("unimplemented action={}", .{action}); + return false; + }, } return true;