From 72e47cf8bc9d846e6be5b2dd2f6fef8f33e76fb9 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 12 Jul 2025 22:59:44 -0500 Subject: [PATCH] cli/gtk: move actual IPC code tp apprt --- src/apprt.zig | 2 + src/apprt/embedded.zig | 3 + src/apprt/gtk.zig | 1 + src/apprt/gtk/IPC.zig | 8 ++ .../gtk.zig => apprt/gtk/ipc/new_window.zig} | 75 ++++++++++--------- src/apprt/none.zig | 2 + src/apprt/structs.zig | 20 +++++ src/cli/new_window.zig | 57 +++++++------- 8 files changed, 107 insertions(+), 61 deletions(-) create mode 100644 src/apprt/gtk/IPC.zig rename src/{cli/new_window/gtk.zig => apprt/gtk/ipc/new_window.zig} (63%) diff --git a/src/apprt.zig b/src/apprt.zig index cb542875e..2cad25f2f 100644 --- a/src/apprt.zig +++ b/src/apprt.zig @@ -32,6 +32,7 @@ pub const ColorScheme = structs.ColorScheme; pub const CursorPos = structs.CursorPos; pub const IMEPos = structs.IMEPos; pub const Selection = structs.Selection; +pub const OpenNewWindowIPCOptions = structs.OpenNewWindowIPCOptions; pub const SurfaceSize = structs.SurfaceSize; /// The implementation to use for the app runtime. This is comptime chosen @@ -49,6 +50,7 @@ pub const runtime = switch (build_config.artifact) { pub const App = runtime.App; pub const Surface = runtime.Surface; +pub const IPC = runtime.IPC; /// Runtime is the runtime to use for Ghostty. All runtimes do not provide /// equivalent feature sets. diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 30a2d9ff6..9ffcf53e6 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1153,6 +1153,9 @@ pub const Inspector = struct { } }; +/// Functions for inter-process communication. +pub const IPC = struct {}; + // C API pub const CAPI = struct { const global = &@import("../global.zig").state; diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index 3193065c4..970923d37 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -3,6 +3,7 @@ pub const App = @import("gtk/App.zig"); pub const Surface = @import("gtk/Surface.zig"); pub const resourcesDir = @import("gtk/flatpak.zig").resourcesDir; +pub const IPC = @import("gtk/IPC.zig"); test { @import("std").testing.refAllDecls(@This()); diff --git a/src/apprt/gtk/IPC.zig b/src/apprt/gtk/IPC.zig new file mode 100644 index 000000000..83529aae3 --- /dev/null +++ b/src/apprt/gtk/IPC.zig @@ -0,0 +1,8 @@ +//! Functions for inter-process communication. +const IPC = @This(); + +pub const openNewWindow = @import("ipc/new_window.zig").openNewWindow; + +test { + _ = openNewWindow; +} diff --git a/src/cli/new_window/gtk.zig b/src/apprt/gtk/ipc/new_window.zig similarity index 63% rename from src/cli/new_window/gtk.zig rename to src/apprt/gtk/ipc/new_window.zig index 351ff1394..6b360c952 100644 --- a/src/cli/new_window/gtk.zig +++ b/src/apprt/gtk/ipc/new_window.zig @@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator; const gio = @import("gio"); const glib = @import("glib"); -const Options = @import("../new_window.zig").Options; +const apprt = @import("../../../apprt.zig"); // Use a D-Bus method call to open a new window on GTK. // See: https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI @@ -17,42 +17,46 @@ const Options = @import("../new_window.zig").Options; // `ghostty +new-window --release -e echo hello` would be equivalent to the following command: // // ``` -// gdbus call --session --dest con.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new-window-command '[<@as ["echo" "hello"]>]' [] +// gdbus call --session --dest com.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new-window-command '[<@as ["echo" "hello"]>]' [] // ``` -pub fn new_window(alloc: Allocator, stderr: std.fs.File.Writer, opts: Options) (Allocator.Error || std.posix.WriteError)!u8 { +pub fn openNewWindow(alloc: Allocator, stderr: std.fs.File.Writer, opts: apprt.OpenNewWindowIPCOptions) (Allocator.Error || std.posix.WriteError)!u8 { // Get the appropriate bus name and object path for contacting the // Ghostty instance we're interested in. const bus_name: [:0]const u8, const object_path: [:0]const u8 = result: { - // Force the usage of the class specified on the CLI to determine the - // bus name and object path. - if (opts.class) |class| { - const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class}); + switch (opts.instance) { + .class => |class| { + // Force the usage of the class specified on the CLI to determine the + // bus name and object path. + const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class}); - std.mem.replaceScalar(u8, object_path, '.', '/'); - std.mem.replaceScalar(u8, object_path, '-', '_'); + std.mem.replaceScalar(u8, object_path, '.', '/'); + std.mem.replaceScalar(u8, object_path, '-', '_'); - break :result .{ class, object_path }; - } - // Force the usage of the release bus name and object path. - if (opts.release) { - break :result .{ "com.mitchellh.ghostty", "/com/mitchellh/ghostty" }; - } - // Force the usage of the debug bus name and object path. - if (opts.debug) { - break :result .{ "com.mitchellh.ghostty-debug", "/com/mitchellh/ghostty_debug" }; - } - // If there is a `GHOSTTY_CLASS` environment variable, use that as the basis - // for the bus name and object path. - if (std.posix.getenv("GHOSTTY_CLASS")) |class| { - const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class}); + break :result .{ class, object_path }; + }, + .release => { + // Force the usage of the release bus name and object path. + break :result .{ "com.mitchellh.ghostty", "/com/mitchellh/ghostty" }; + }, + .debug => { + // Force the usage of the debug bus name and object path. + break :result .{ "com.mitchellh.ghostty-debug", "/com/mitchellh/ghostty_debug" }; + }, + .detect => { + // If there is a `GHOSTTY_CLASS` environment variable, use that as the basis + // for the bus name and object path. + if (std.posix.getenv("GHOSTTY_CLASS")) |class| { + const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class}); - std.mem.replaceScalar(u8, object_path, '.', '/'); - std.mem.replaceScalar(u8, object_path, '-', '_'); + std.mem.replaceScalar(u8, object_path, '.', '/'); + std.mem.replaceScalar(u8, object_path, '-', '_'); - break :result .{ class, object_path }; + break :result .{ class, object_path }; + } + // Otherwise fall back to the release bus name and object path. + break :result .{ "com.mitchellh.ghostty", "/com/mitchellh/ghostty" }; + }, } - // Otherwise fall back to the release bus name and object path. - break :result .{ "com.mitchellh.ghostty", "/com/mitchellh/ghostty" }; }; if (gio.Application.idIsValid(bus_name.ptr) == 0) { @@ -96,7 +100,7 @@ pub fn new_window(alloc: Allocator, stderr: std.fs.File.Writer, opts: Options) ( errdefer builder.unref(); // action - if (opts._arguments.items.len == 0) { + if (opts.arguments.len == 0) { builder.add("s", "new-window"); } else { builder.add("s", "new-window-command"); @@ -110,10 +114,11 @@ pub fn new_window(alloc: Allocator, stderr: std.fs.File.Writer, opts: Options) ( var parameters: glib.VariantBuilder = undefined; parameters.init(av); - if (opts._arguments.items.len > 0) { - // If `-e` was specified on the command line, he first parameter - // is an array of strings that contain the arguments that came - // afer `-e`, which will be interpreted as a command to run. + if (opts.arguments.len > 0) { + // If `-e` was specified on the command line, the first + // parameter is an array of strings that contain the arguments + // that came after `-e`, which will be interpreted as a command + // to run. { const as = glib.VariantType.new("as"); defer as.free(); @@ -121,7 +126,7 @@ pub fn new_window(alloc: Allocator, stderr: std.fs.File.Writer, opts: Options) ( var command: glib.VariantBuilder = undefined; command.init(as); - for (opts._arguments.items) |argument| { + for (opts.arguments) |argument| { command.add("s", argument.ptr); } @@ -134,7 +139,7 @@ pub fn new_window(alloc: Allocator, stderr: std.fs.File.Writer, opts: Options) ( { const platform_data = glib.VariantType.new("a{sv}"); - defer glib.free(platform_data); + defer platform_data.free(); builder.open(platform_data); defer builder.close(); diff --git a/src/apprt/none.zig b/src/apprt/none.zig index 76faa88af..695735c3b 100644 --- a/src/apprt/none.zig +++ b/src/apprt/none.zig @@ -2,3 +2,5 @@ const internal_os = @import("../os/main.zig"); pub const resourcesDir = internal_os.resourcesDir; pub const App = struct {}; pub const Surface = struct {}; +/// Functions for inter-process communication. +pub const IPC = struct {}; diff --git a/src/apprt/structs.zig b/src/apprt/structs.zig index e2e9b913d..a993d61d2 100644 --- a/src/apprt/structs.zig +++ b/src/apprt/structs.zig @@ -72,3 +72,23 @@ pub const Selection = struct { offset_start: u32, offset_len: u32, }; + +pub const OpenNewWindowIPCOptions = struct { + instance: union(enum) { + /// Open up a new window in a release instance of Ghostty. + release, + + /// Open up a new window in a debug instance of Ghostty. + debug, + + /// Open up a new window in a custom instance of Ghostty. + class: [:0]const u8, + + /// Detect which instance to open a new window in. + detect, + }, + + /// If `-e` is found in the arguments, this will contain all of the + /// arguments to pass to Ghostty as the command. + arguments: [][:0]const u8, +}; diff --git a/src/cli/new_window.zig b/src/cli/new_window.zig index e46923b21..5ca4be147 100644 --- a/src/cli/new_window.zig +++ b/src/cli/new_window.zig @@ -1,14 +1,10 @@ const std = @import("std"); -const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; -const build_config = @import("../build_config.zig"); const Action = @import("../cli.zig").ghostty.Action; +const apprt = @import("../apprt.zig"); const args = @import("args.zig"); const diagnostics = @import("diagnostics.zig"); -const font = @import("../font/main.zig"); -const configpkg = @import("../config.zig"); -const Config = configpkg.Config; pub const Options = struct { /// This is set by the CLI parser for deinit. @@ -65,12 +61,26 @@ pub const Options = struct { /// The `new-window` will use native platform IPC to open up a new window in a /// running instance of Ghostty. /// +/// If none of `--release`, `--debug`, and `--class` flags are not set, the +/// `new-window` command will try and find the class of the running Ghostty +/// instance in the `GHOSTTY_CLASS` environment variable. If this environment +/// variable is not set, a release instance of Ghostty will be opened. +/// /// If the `-e` flag is included on the command line, any arguments that follow /// will be sent to the running Ghostty instance and used as the command to run /// in the new window rather than the default. If `-e` is not specified, Ghostty /// will use the default command (either specified with `command` in your config /// or your default shell as configured on your system). /// +/// GTK uses an application ID to identify instances of applications. If Ghostty +/// is compiled with release optimizations, the default application ID will be +/// `com.mitchellh.ghostty`. If Ghostty is compiled with debug optimizations, +/// the default application ID will be `com.mitchellh.ghostty-debug`. The +/// `class` configuration entry can be used to set up a custom application +/// ID. The class name must follow the requirements defined [in the GTK +/// documentation](https://docs.gtk.org/gio/type_func.Application.id_is_valid.html) +/// or it will be ignored and Ghostty will use the default as defined above. +/// /// On GTK, D-Bus activation must be properly configured. Ghostty does not need /// to be running for this to open a new window, making it suitable for binding /// to keys in your window manager (if other methods for configuring global @@ -78,22 +88,6 @@ pub const Options = struct { /// of Ghostty if it is not already running. See the Ghostty website for /// information on properly configuring D-Bus activation. /// -/// GTK uses an application ID to identify instances of applications. If -/// Ghostty is compiled with debug optimizations, the application ID will -/// be `com.mitchellh.ghostty-debug`. If Ghostty is compiled with release -/// optimizations, the application ID will be `com.mitchellh.ghostty`. -/// -/// The `class` configuration entry can be used to set up a custom application -/// ID. The class name must follow the requirements defined [in the GTK -/// documentation](https://docs.gtk.org/gio/type_func.Application.id_is_valid.html) -/// or it will be ignored and Ghostty will use the default application ID as -/// defined above. -/// -/// The `new-window` command will try and find the application ID of the running -/// Ghostty instance in the `GHOSTTY_CLASS` environment variable. If this -/// environment variable is not set, and any of the command line flags defined -/// below are not set, a release instance of Ghostty will be opened. -/// /// Only supported on GTK. /// /// Flags: @@ -170,12 +164,23 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { defer arena.deinit(); const alloc = arena.allocator(); - if (comptime build_config.app_runtime == .gtk) { - const new_window = @import("new_window/gtk.zig").new_window; - return try new_window(alloc, stderr, opts); + if (@hasDecl(apprt.IPC, "openNewWindow")) { + return try apprt.IPC.openNewWindow( + alloc, + stderr, + .{ + .instance = instance: { + if (opts.class) |class| break :instance .{ .class = class }; + if (opts.release) break :instance .release; + if (opts.debug) break :instance .debug; + break :instance .detect; + }, + .arguments = opts._arguments.items, + }, + ); } - // If we get here, the platform is unsupported. - try stderr.print("+new-window is unsupported on this platform.\n", .{}); + // If we get here, the platform is not supported. + try stderr.print("+new-window is not supported on this platform.\n", .{}); return 1; }