diff --git a/include/ghostty.h b/include/ghostty.h index b099741fc..fbfe3ee2c 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -1054,6 +1054,7 @@ typedef union { // apprt.ipc.Action.Key typedef enum { GHOSTTY_IPC_ACTION_NEW_WINDOW, + GHOSTTY_IPC_ACTION_TOGGLE_QUICK_TERMINAL, } ghostty_ipc_action_tag_e; //------------------------------------------------------------------- diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 730913eba..7310159cc 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -336,6 +336,7 @@ pub const App = struct { ) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool { switch (action) { .new_window => return false, + .toggle_quick_terminal => return false, } } }; diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 39c13c19d..8a7b3a8e5 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -13,6 +13,7 @@ const CoreApp = @import("../../App.zig"); const Application = @import("class/application.zig").Application; const Surface = @import("Surface.zig"); const ipcNewWindow = @import("ipc/new_window.zig").newWindow; +const ipcToggleQuickTerminal = @import("ipc/toggle_quick_terminal.zig").toggleQuickTerminal; const log = std.log.scoped(.gtk); @@ -84,6 +85,7 @@ pub fn performIpc( ) !bool { switch (action) { .new_window => return try ipcNewWindow(alloc, target, value), + .toggle_quick_terminal => return try ipcToggleQuickTerminal(alloc, target), } } diff --git a/src/apprt/gtk/class/application.zig b/src/apprt/gtk/class/application.zig index 873674cec..107510b43 100644 --- a/src/apprt/gtk/class/application.zig +++ b/src/apprt/gtk/class/application.zig @@ -1419,6 +1419,7 @@ pub const Application = extern struct { .init("present-surface", actionPresentSurface, t_variant_type), .init("quit", actionQuit, null), .init("reload-config", actionReloadConfig, null), + .init("toggle-quick-terminal", actionToggleQuickTerminal, null), }; ext.actions.add(Self, self, &actions); @@ -1669,6 +1670,17 @@ pub const Application = extern struct { }; } + fn actionToggleQuickTerminal( + _: *gio.SimpleAction, + _: ?*glib.Variant, + self: *Self, + ) callconv(.c) void { + const priv = self.private(); + priv.core_app.performAction(self.rt(), .toggle_quick_terminal) catch |err| { + log.warn("error toggling quick terminal err={}", .{err}); + }; + } + fn actionQuit( _: *gio.SimpleAction, _: ?*glib.Variant, diff --git a/src/apprt/gtk/ipc/toggle_quick_terminal.zig b/src/apprt/gtk/ipc/toggle_quick_terminal.zig new file mode 100644 index 000000000..702ad3df3 --- /dev/null +++ b/src/apprt/gtk/ipc/toggle_quick_terminal.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const apprt = @import("../../../apprt.zig"); +const DBus = @import("DBus.zig"); + +/// Use a D-Bus method call to toggle the quick terminal on GTK. +/// +/// `ghostty +toggle-quick-terminal` is equivalent to the following command +/// (on a release build): +/// +/// ```sh +/// gdbus call --session \ +/// --dest com.mitchellh.ghostty \ +/// --object-path /com/mitchellh/ghostty \ +/// --method org.gtk.Actions.Activate \ +/// toggle-quick-terminal [] [] +/// ``` +pub fn toggleQuickTerminal(alloc: Allocator, target: apprt.ipc.Target) (Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors)!bool { + var dbus = try DBus.init(alloc, target, "toggle-quick-terminal"); + defer dbus.deinit(alloc); + try dbus.send(); + return true; +} diff --git a/src/apprt/ipc.zig b/src/apprt/ipc.zig index b37647e02..dda794ae1 100644 --- a/src/apprt/ipc.zig +++ b/src/apprt/ipc.zig @@ -73,6 +73,9 @@ pub const Action = union(enum) { /// The arguments to pass to Ghostty as the command. new_window: NewWindow, + /// Toggle the quick terminal. + toggle_quick_terminal: void, + pub const NewWindow = struct { /// A list of command arguments to launch in the new window. If this is /// `null` the command configured in the config or the user's default @@ -113,6 +116,7 @@ pub const Action = union(enum) { /// Sync with: ghostty_ipc_action_tag_e pub const Key = enum(c_int) { new_window, + toggle_quick_terminal, test "ghostty.h Action.Key" { try lib.checkGhosttyHEnum(Key, "GHOSTTY_IPC_ACTION_"); diff --git a/src/cli/ghostty.zig b/src/cli/ghostty.zig index 3acb90043..e44bdf9ed 100644 --- a/src/cli/ghostty.zig +++ b/src/cli/ghostty.zig @@ -20,6 +20,7 @@ const crash_report = @import("crash_report.zig"); const show_face = @import("show_face.zig"); const boo = @import("boo.zig"); const new_window = @import("new_window.zig"); +const toggle_quick_terminal = @import("toggle_quick_terminal.zig"); /// Special commands that can be invoked via CLI flags. These are all /// invoked by using `+` as a CLI flag. The only exception is @@ -73,6 +74,9 @@ pub const Action = enum { // Use IPC to tell the running Ghostty to open a new window. @"new-window", + // Use IPC to tell the running Ghostty to toggle the quick terminal. + @"toggle-quick-terminal", + pub fn detectSpecialCase(arg: []const u8) ?SpecialCase(Action) { // If we see a "-e" and we haven't seen a command yet, then // we are done looking for commands. This special case enables @@ -152,6 +156,7 @@ pub const Action = enum { .@"show-face" => try show_face.run(alloc), .boo => try boo.run(alloc), .@"new-window" => try new_window.run(alloc), + .@"toggle-quick-terminal" => try toggle_quick_terminal.run(alloc), }; } @@ -192,6 +197,7 @@ pub const Action = enum { .@"show-face" => show_face.Options, .boo => boo.Options, .@"new-window" => new_window.Options, + .@"toggle-quick-terminal" => toggle_quick_terminal.Options, }; } } diff --git a/src/cli/toggle_quick_terminal.zig b/src/cli/toggle_quick_terminal.zig new file mode 100644 index 000000000..16d2a1473 --- /dev/null +++ b/src/cli/toggle_quick_terminal.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Action = @import("../cli.zig").ghostty.Action; +const apprt = @import("../apprt.zig"); + +pub const Options = struct { + /// If set, connect to a custom instance of Ghostty. + class: ?[:0]const u8 = null, + + pub fn deinit(self: *Options) void { + self.* = undefined; + } + + /// Enables "-h" and "--help" to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } +}; + +/// The `+toggle-quick-terminal` command will use native platform IPC to toggle +/// the quick terminal in a running instance of Ghostty. +/// +/// If the `--class` flag is not set, the command will try and connect to the +/// default running Ghostty instance. Otherwise it will contact a Ghostty +/// instance configured with the given `class`. +/// +/// On GTK, D-Bus activation must be properly configured. Ghostty does not need +/// to be running, as D-Bus will handle launching a new instance if it is not +/// already running. +/// +/// Only supported on GTK. +/// +/// Flags: +/// +/// * `--class=`: If set, connect to a custom instance of Ghostty. +/// The class must be a valid GTK application ID. +/// +/// Available since: 1.3.0 +pub fn run(alloc: Allocator) !u8 { + var buf: [256]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&buf); + const stderr = &stderr_writer.interface; + + if (apprt.App.performIpc( + alloc, + .detect, + .toggle_quick_terminal, + {}, + ) catch |err| switch (err) { + error.IPCFailed => { + return 1; + }, + else => { + try stderr.print("Sending the IPC failed: {}\n", .{err}); + return 1; + }, + }) return 0; + + try stderr.print("+toggle-quick-terminal is not supported on this platform.\n", .{}); + return 1; +}