mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
dbus and systemd activation - take 2 (#7679)
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:
@@ -1,13 +1,15 @@
|
||||
[Desktop Entry]
|
||||
Name=Ghostty
|
||||
Version=1.0
|
||||
Name=@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=@APPID@
|
||||
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
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.mitchellh.ghostty</id>
|
||||
<launchable type="desktop-id">com.mitchellh.ghostty.desktop</launchable>
|
||||
<name>Ghostty</name>
|
||||
<id>@APPID@id>
|
||||
<launchable type="desktop-id">@APPID@desktop</launchable>
|
||||
<name>@NAME@name>
|
||||
<url type="homepage">https://ghostty.org</url>
|
||||
<url type="help">https://ghostty.org/docs</url>
|
||||
<url type="bugtracker">https://github.com/ghostty-org/ghostty/discussions</url>
|
3
dist/linux/dbus.service.flatpak.in
vendored
Normal file
3
dist/linux/dbus.service.flatpak.in
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[D-BUS Service]
|
||||
Name=@APPID@
|
||||
Exec=@GHOSTTY@ --launched-from=dbus
|
4
dist/linux/dbus.service.in
vendored
Normal file
4
dist/linux/dbus.service.in
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[D-BUS Service]
|
||||
Name=@APPID@
|
||||
SystemdService=@APPID@.service
|
||||
Exec=@GHOSTTY@ --launched-from=dbus
|
7
dist/linux/systemd.service.in
vendored
Normal file
7
dist/linux/systemd.service.in
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=@NAME@
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
BusName=@APPID@
|
||||
ExecStart=@GHOSTTY@ --launched-from=systemd
|
@@ -6,11 +6,7 @@ sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.ziglang
|
||||
default-branch: tip
|
||||
command: ghostty
|
||||
# Integrate the rename into zig build, maybe?
|
||||
rename-desktop-file: com.mitchellh.ghostty.desktop
|
||||
rename-appdata-file: com.mitchellh.ghostty.metainfo.xml
|
||||
rename-icon: com.mitchellh.ghostty
|
||||
desktop-file-name-suffix: " (Debug)"
|
||||
finish-args:
|
||||
# 3D rendering
|
||||
- --device=dri
|
||||
|
@@ -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();
|
||||
|
@@ -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")) |_| {
|
||||
|
@@ -87,7 +87,7 @@ 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.graph.system_package_mode;
|
||||
|
||||
// This specifies our target wasm runtime. For now only one semi-usable
|
||||
// one exists so this is hardcoded.
|
||||
|
@@ -2,6 +2,7 @@ const GhosttyResources = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const buildpkg = @import("main.zig");
|
||||
const Config = @import("Config.zig");
|
||||
const config_vim = @import("../config/vim.zig");
|
||||
@@ -220,83 +221,178 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources {
|
||||
}
|
||||
|
||||
// App (Linux)
|
||||
if (cfg.target.result.os.tag == .linux) {
|
||||
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
|
||||
if (cfg.target.result.os.tag == .linux) try addLinuxAppResources(
|
||||
b,
|
||||
cfg,
|
||||
&steps,
|
||||
);
|
||||
|
||||
return .{ .steps = steps.items };
|
||||
}
|
||||
|
||||
/// Add the resource files needed to make Ghostty a proper
|
||||
/// Linux desktop application (for various desktop environments).
|
||||
fn addLinuxAppResources(
|
||||
b: *std.Build,
|
||||
cfg: *const Config,
|
||||
steps: *std.ArrayList(*std.Build.Step),
|
||||
) !void {
|
||||
assert(cfg.target.result.os.tag == .linux);
|
||||
|
||||
// Background:
|
||||
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
|
||||
|
||||
const name = b.fmt("Ghostty{s}", .{
|
||||
switch (cfg.optimize) {
|
||||
.Debug, .ReleaseSafe => " (Debug)",
|
||||
.ReleaseFast, .ReleaseSmall => "",
|
||||
},
|
||||
});
|
||||
|
||||
const app_id = b.fmt("com.mitchellh.ghostty{s}", .{
|
||||
switch (cfg.optimize) {
|
||||
.Debug, .ReleaseSafe => "-debug",
|
||||
.ReleaseFast, .ReleaseSmall => "",
|
||||
},
|
||||
});
|
||||
|
||||
const exe_abs_path = b.fmt(
|
||||
"{s}/bin/ghostty",
|
||||
.{b.install_prefix},
|
||||
);
|
||||
|
||||
// The templates that we will process. The templates are in
|
||||
// cmake format and will be processed and saved to the
|
||||
// second element of the tuple.
|
||||
const Template = struct { std.Build.LazyPath, []const u8 };
|
||||
const templates: []const Template = templates: {
|
||||
var ts: std.ArrayList(Template) = .init(b.allocator);
|
||||
|
||||
// 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 ts.append(.{
|
||||
b.path("dist/linux/app.desktop.in"),
|
||||
b.fmt("share/applications/{s}.desktop", .{app_id}),
|
||||
});
|
||||
|
||||
// AppStream metainfo so that application has rich metadata within app stores
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("dist/linux/com.mitchellh.ghostty.metainfo.xml"),
|
||||
"share/metainfo/com.mitchellh.ghostty.metainfo.xml",
|
||||
).step);
|
||||
// Service for DBus activation.
|
||||
try ts.append(.{
|
||||
if (cfg.flatpak)
|
||||
b.path("dist/linux/dbus.service.flatpak.in")
|
||||
else
|
||||
b.path("dist/linux/dbus.service.in"),
|
||||
b.fmt("share/dbus-1/services/{s}.service", .{app_id}),
|
||||
});
|
||||
|
||||
// Right click menu action for Plasma desktop
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("dist/linux/ghostty_dolphin.desktop"),
|
||||
"share/kio/servicemenus/com.mitchellh.ghostty.desktop",
|
||||
).step);
|
||||
// systemd user service. This is kind of nasty but systemd
|
||||
// looks for user services in different paths depending on
|
||||
// if we are installed as a system package or not (lib vs.
|
||||
// share) so we have to handle that here. We might be able
|
||||
// to get away with always installing to both because it
|
||||
// only ever searches in one... but I don't want to do that hack
|
||||
// until we have to.
|
||||
if (!cfg.flatpak) try ts.append(.{
|
||||
b.path("dist/linux/systemd.service.in"),
|
||||
b.fmt(
|
||||
"{s}/systemd/user/{s}.service",
|
||||
.{
|
||||
if (b.graph.system_package_mode) "lib" else "share",
|
||||
app_id,
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
// Right click menu action for Nautilus. Note that this _must_ be named
|
||||
// `ghostty.py`. Using the full app id causes problems (see #5468).
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("dist/linux/ghostty_nautilus.py"),
|
||||
"share/nautilus-python/extensions/ghostty.py",
|
||||
).step);
|
||||
// AppStream metainfo so that application has rich metadata
|
||||
// within app stores
|
||||
try ts.append(.{
|
||||
b.path("dist/linux/com.mitchellh.ghostty.metainfo.xml.in"),
|
||||
b.fmt("share/metainfo/{s}.metainfo.xml", .{app_id}),
|
||||
});
|
||||
|
||||
// Various icons that our application can use, including the icon
|
||||
// that will be used for the desktop.
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_16.png"),
|
||||
"share/icons/hicolor/16x16/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_32.png"),
|
||||
"share/icons/hicolor/32x32/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_128.png"),
|
||||
"share/icons/hicolor/128x128/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_256.png"),
|
||||
"share/icons/hicolor/256x256/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_512.png"),
|
||||
"share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
// Flatpaks only support icons up to 512x512.
|
||||
if (!cfg.flatpak) {
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_1024.png"),
|
||||
"share/icons/hicolor/1024x1024/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
}
|
||||
break :templates ts.items;
|
||||
};
|
||||
|
||||
// Process all our templates
|
||||
for (templates) |template| {
|
||||
const tpl = b.addConfigHeader(.{
|
||||
.style = .{ .cmake = template[0] },
|
||||
}, .{
|
||||
.NAME = name,
|
||||
.APPID = app_id,
|
||||
.GHOSTTY = exe_abs_path,
|
||||
});
|
||||
|
||||
// Template output has a single header line we want to remove.
|
||||
// We use `tail` to do it since its part of the POSIX standard.
|
||||
const tail = b.addSystemCommand(&.{ "tail", "-n", "+2" });
|
||||
tail.setStdIn(.{ .lazy_path = tpl.getOutput() });
|
||||
|
||||
const copy = b.addInstallFile(
|
||||
tail.captureStdOut(),
|
||||
template[1],
|
||||
);
|
||||
|
||||
try steps.append(©.step);
|
||||
}
|
||||
|
||||
// Right click menu action for Plasma desktop
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("dist/linux/ghostty_dolphin.desktop"),
|
||||
"share/kio/servicemenus/com.mitchellh.ghostty.desktop",
|
||||
).step);
|
||||
|
||||
// Right click menu action for Nautilus. Note that this _must_ be named
|
||||
// `ghostty.py`. Using the full app id causes problems (see #5468).
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("dist/linux/ghostty_nautilus.py"),
|
||||
"share/nautilus-python/extensions/ghostty.py",
|
||||
).step);
|
||||
|
||||
// Various icons that our application can use, including the icon
|
||||
// that will be used for the desktop.
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_16.png"),
|
||||
"share/icons/hicolor/16x16/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_32.png"),
|
||||
"share/icons/hicolor/32x32/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_128.png"),
|
||||
"share/icons/hicolor/128x128/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_256.png"),
|
||||
"share/icons/hicolor/256x256/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_512.png"),
|
||||
"share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
// Flatpaks only support icons up to 512x512.
|
||||
if (!cfg.flatpak) {
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_16@2x.png"),
|
||||
"share/icons/hicolor/16x16@2/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_32@2x.png"),
|
||||
"share/icons/hicolor/32x32@2/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_128@2x.png"),
|
||||
"share/icons/hicolor/128x128@2/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_256@2x.png"),
|
||||
"share/icons/hicolor/256x256@2/apps/com.mitchellh.ghostty.png",
|
||||
b.path("images/icons/icon_1024.png"),
|
||||
"share/icons/hicolor/1024x1024/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
}
|
||||
|
||||
return .{ .steps = steps.items };
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_16@2x.png"),
|
||||
"share/icons/hicolor/16x16@2/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_32@2x.png"),
|
||||
"share/icons/hicolor/32x32@2/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_128@2x.png"),
|
||||
"share/icons/hicolor/128x128@2/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
try steps.append(&b.addInstallFile(
|
||||
b.path("images/icons/icon_256@2x.png"),
|
||||
"share/icons/hicolor/256x256@2/apps/com.mitchellh.ghostty.png",
|
||||
).step);
|
||||
}
|
||||
|
||||
pub fn install(self: *const GhosttyResources) void {
|
||||
|
@@ -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).
|
||||
///
|
||||
|
Reference in New Issue
Block a user