dbus and systemd activation - take 2

This replaces #7433. The improvements are:

1) Install the systemd user service in the proper directory depending
on if it's a 'user' install or a 'system' install. This is controlled
either by using the `--system` build flag (as most packages will) or by
the `-Dsystem-package` flag.

2) Add the absolute path to the `ghostty` binary in the application
file, the DBus service, and the systemd user service. This is done so
that they do not depend on `ghostty` being in the `PATH` of whatever
is launching Ghostty. That `PATH` is not necessarily the same as the
`PATH` in a user shell (especially for DBus activation and systemd user
services).

3) Adjust the DBus bus name that is expected by the system depending on
the optimization level that Ghostty is compiled with.
This commit is contained in:
Jeffrey C. Ollie
2025-06-24 22:56:17 -05:00
committed by Mitchell Hashimoto
parent fa47db5363
commit 81403f59ce
10 changed files with 146 additions and 14 deletions

View File

@ -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

View File

@ -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

4
dist/linux/dbus.service vendored Normal file
View File

@ -0,0 +1,4 @@
[D-BUS Service]
Name=com.mitchellh.ghostty@@DEBUG@@
SystemdService=com.mitchellh.ghostty@@DEBUG@@.service
Exec=@@GHOSTTY@@ --launched-from=dbus

7
dist/linux/systemd.service vendored Normal file
View File

@ -0,0 +1,7 @@
[Unit]
Description=Ghostty@@NAME@@
[Service]
Type=dbus
BusName=com.mitchellh.ghostty@@DEBUG@@
ExecStart=@@GHOSTTY@@ --launched-from=systemd

View File

@ -99,6 +99,7 @@ in
"-Dgtk-x11=${lib.boolToString enableX11}"
"-Dgtk-wayland=${lib.boolToString enableWayland}"
"-Dstrip=${lib.boolToString strip}"
"-Dsystem-package=true"
];
outputs = [

View File

@ -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();

View File

@ -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")) |_| {

View File

@ -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

View File

@ -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(
try steps.append(
formatService(
b,
cfg,
b.path("dist/linux/app.desktop"),
"share/applications/com.mitchellh.ghostty.desktop",
).step);
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;
}

View File

@ -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).
///