diff --git a/PACKAGING.md b/PACKAGING.md index d85f55de7..e85a9e987 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -83,7 +83,8 @@ zig build \ --prefix /usr \ --system /tmp/offline-cache/p \ -Doptimize=ReleaseFast \ - -Dcpu=baseline + -Dcpu=baseline \ + -Dsystem-package=true ``` The build options are covered in the next section, but this will build diff --git a/dist/linux/app.desktop b/dist/linux/app.desktop index 6e464ea87..7dc0062f7 100644 --- a/dist/linux/app.desktop +++ b/dist/linux/app.desktop @@ -1,13 +1,15 @@ [Desktop Entry] -Name=Ghostty +Version=1.0 +Name=Ghostty@@NAME@@ Type=Application Comment=A terminal emulator -Exec=ghostty +TryExec=@@GHOSTTY@@ +Exec=@@GHOSTTY@@ --launched-from=desktop Icon=com.mitchellh.ghostty Categories=System;TerminalEmulator; Keywords=terminal;tty;pty; StartupNotify=true -StartupWMClass=com.mitchellh.ghostty +StartupWMClass=com.mitchellh.ghostty@@DEBUG@@ Terminal=false Actions=new-window; X-GNOME-UsesNotifications=true @@ -16,7 +18,8 @@ X-TerminalArgTitle=--title= X-TerminalArgAppId=--class= X-TerminalArgDir=--working-directory= X-TerminalArgHold=--wait-after-command +DBusActivatable=true [Desktop Action new-window] Name=New Window -Exec=ghostty +Exec=ghostty --launched-from=desktop diff --git a/dist/linux/dbus.service b/dist/linux/dbus.service new file mode 100644 index 000000000..f9c5ce7a4 --- /dev/null +++ b/dist/linux/dbus.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=com.mitchellh.ghostty@@DEBUG@@ +SystemdService=com.mitchellh.ghostty@@DEBUG@@.service +Exec=@@GHOSTTY@@ --launched-from=dbus diff --git a/dist/linux/systemd.service b/dist/linux/systemd.service new file mode 100644 index 000000000..2d1acdaea --- /dev/null +++ b/dist/linux/systemd.service @@ -0,0 +1,7 @@ +[Unit] +Description=Ghostty@@NAME@@ + +[Service] +Type=dbus +BusName=com.mitchellh.ghostty@@DEBUG@@ +ExecStart=@@GHOSTTY@@ --launched-from=systemd diff --git a/nix/package.nix b/nix/package.nix index 08dfd710b..f24b401cc 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -99,6 +99,7 @@ in "-Dgtk-x11=${lib.boolToString enableX11}" "-Dgtk-wayland=${lib.boolToString enableWayland}" "-Dstrip=${lib.boolToString strip}" + "-Dsystem-package=true" ]; outputs = [ diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 7c9c15191..93e069376 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -405,11 +405,15 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { // This just calls the `activate` signal but its part of the normal startup // routine so we just call it, but only if the config allows it (this allows // for launching Ghostty in the "background" without immediately opening - // a window) + // a window). An initial window will not be immediately created if we were + // launched by D-Bus activation or systemd. D-Bus activation will send it's + // own `activate` or `new-window` signal later. // // https://gitlab.gnome.org/GNOME/glib/-/blob/bd2ccc2f69ecfd78ca3f34ab59e42e2b462bad65/gio/gapplication.c#L2302 - if (config.@"initial-window") - gio_app.activate(); + if (config.@"initial-window") switch (config.@"launched-from".?) { + .desktop, .cli => gio_app.activate(), + .dbus, .systemd => {}, + }; // Internally, GTK ensures that only one instance of this provider exists in the provider list // for the display. @@ -1683,6 +1687,17 @@ fn gtkActionShowGTKInspector( }; } +fn gtkActionNewWindow( + _: *gio.SimpleAction, + _: ?*glib.Variant, + self: *App, +) callconv(.c) void { + log.info("received new window action", .{}); + _ = self.core_app.mailbox.push(.{ + .new_window = .{}, + }, .{ .forever = {} }); +} + /// This is called to setup the action map that this application supports. /// This should be called only once on startup. fn initActions(self: *App) void { @@ -1702,7 +1717,9 @@ fn initActions(self: *App) void { .{ "reload-config", gtkActionReloadConfig, null }, .{ "present-surface", gtkActionPresentSurface, t }, .{ "show-gtk-inspector", gtkActionShowGTKInspector, null }, + .{ "new-window", gtkActionNewWindow, null }, }; + inline for (actions) |entry| { const action = gio.SimpleAction.new(entry[0], entry[2]); defer action.unref(); diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index cf8d651dd..5c886e663 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -2325,6 +2325,15 @@ pub fn defaultTermioEnv(self: *Surface) !std.process.EnvMap { env.remove("GDK_DISABLE"); env.remove("GSK_RENDERER"); + // Remove some environment variables that are set when Ghostty is launched + // from a `.desktop` file, by D-Bus activation, or systemd. + env.remove("GIO_LAUNCHED_DESKTOP_FILE"); + env.remove("GIO_LAUNCHED_DESKTOP_FILE_PID"); + env.remove("DBUS_STARTER_ADDRESS"); + env.remove("DBUS_STARTER_BUS_TYPE"); + env.remove("INVOCATION_ID"); + env.remove("JOURNAL_STREAM"); + // Unset environment varies set by snaps if we're running in a snap. // This allows Ghostty to further launch additional snaps. if (env.get("SNAP")) |_| { diff --git a/src/build/Config.zig b/src/build/Config.zig index 8974e1f0c..ede42c738 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -45,6 +45,7 @@ version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }, pie: bool = false, strip: bool = false, patch_rpath: ?[]const u8 = null, +system_package: bool = false, /// Artifacts flatpak: bool = false, @@ -87,7 +88,11 @@ pub fn init(b: *std.Build) !Config { // This is set to true when we're building a system package. For now // this is trivially detected using the "system_package_mode" bool // but we may want to make this more sophisticated in the future. - const system_package: bool = b.graph.system_package_mode; + const system_package = b.option( + bool, + "system-package", + "Controls whether we build a system package", + ) orelse b.graph.system_package_mode; // This specifies our target wasm runtime. For now only one semi-usable // one exists so this is hardcoded. @@ -261,6 +266,8 @@ pub fn init(b: *std.Build) !Config { .ReleaseFast, .ReleaseSmall => true, }; + config.system_package = system_package; + //--------------------------------------------------------------- // Artifacts to Emit diff --git a/src/build/GhosttyResources.zig b/src/build/GhosttyResources.zig index 640491fd6..f3b169de1 100644 --- a/src/build/GhosttyResources.zig +++ b/src/build/GhosttyResources.zig @@ -224,10 +224,57 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources { // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html // Desktop file so that we have an icon and other metadata - try steps.append(&b.addInstallFile( - b.path("dist/linux/app.desktop"), - "share/applications/com.mitchellh.ghostty.desktop", - ).step); + try steps.append( + formatService( + b, + cfg, + b.path("dist/linux/app.desktop"), + b.fmt( + "share/applications/com.mitchellh.ghostty{s}.desktop", + .{ + switch (cfg.optimize) { + .Debug, .ReleaseSafe => "-debug", + .ReleaseFast, .ReleaseSmall => "", + }, + }, + ), + ), + ); + // Service for DBus activation. + try steps.append( + formatService( + b, + cfg, + b.path("dist/linux/dbus.service"), + b.fmt( + "share/dbus-1/services/com.mitchellh.ghostty{s}.service", + .{ + switch (cfg.optimize) { + .Debug, .ReleaseSafe => "-debug", + .ReleaseFast, .ReleaseSmall => "", + }, + }, + ), + ), + ); + // systemd user service + try steps.append( + formatService( + b, + cfg, + b.path("dist/linux/systemd.service"), + b.fmt( + "{s}/systemd/user/com.mitchellh.ghostty{s}.service", + .{ + if (cfg.system_package) "lib" else "share", + switch (cfg.optimize) { + .Debug, .ReleaseSafe => "-debug", + .ReleaseFast, .ReleaseSmall => "", + }, + }, + ), + ), + ); // AppStream metainfo so that application has rich metadata within app stores try steps.append(&b.addInstallFile( @@ -303,3 +350,34 @@ pub fn install(self: *const GhosttyResources) void { const b = self.steps[0].owner; for (self.steps) |step| b.getInstallStep().dependOn(step); } + +pub fn formatService(b: *std.Build, cfg: *const Config, src: std.Build.LazyPath, dest: []const u8) *std.Build.Step { + var cmd = b.addSystemCommand(&.{"sed"}); + cmd.setStdIn(.{ .lazy_path = src }); + const output = cmd.captureStdOut(); + + cmd.addArg(b.fmt( + "-e s!@@NAME@@!{s}!g", + .{ + switch (cfg.optimize) { + .Debug, .ReleaseSafe => " Debug", + .ReleaseFast, .ReleaseSmall => "", + }, + }, + )); + cmd.addArg(b.fmt( + "-e s!@@DEBUG@@!{s}!g", + .{ + switch (cfg.optimize) { + .Debug, .ReleaseSafe => "-debug", + .ReleaseFast, .ReleaseSmall => "", + }, + }, + )); + cmd.addArg(b.fmt( + "-e s!@@GHOSTTY@@!{s}/bin/ghostty!g", + .{b.install_prefix}, + )); + + return &b.addInstallFile(output, dest).step; +} diff --git a/src/config/Config.zig b/src/config/Config.zig index be59ae94f..64b00eb1c 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1029,12 +1029,17 @@ title: ?[:0]const u8 = null, /// The setting that will change the application class value. /// /// This controls the class field of the `WM_CLASS` X11 property (when running -/// under X11), and the Wayland application ID (when running under Wayland). +/// under X11), the Wayland application ID (when running under Wayland), and the +/// bus name that Ghostty uses to connect to DBus. /// /// Note that changing this value between invocations will create new, separate /// instances, of Ghostty when running with `gtk-single-instance=true`. See that /// option for more details. /// +/// Changing this value may break launching Ghostty from `.desktop` files, via +/// DBus activation, or systemd user services as the system is expecting Ghostty +/// to connect to DBus using the default `class` when it is launched. +/// /// The class name must follow the requirements defined [in the GTK /// documentation](https://docs.gtk.org/gio/type_func.Application.id_is_valid.html). ///