use cmake formatting to template, avoids the custom binary

pull/7679/head
Mitchell Hashimoto 2025-06-26 10:28:17 -07:00
parent 73d5eb928c
commit 739b691a6d
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
7 changed files with 168 additions and 201 deletions

View File

@ -1,15 +1,15 @@
[Desktop Entry]
Version=1.0
Name=@@NAME@@
Name=@NAME@
Type=Application
Comment=A terminal emulator
TryExec=@@GHOSTTY@@
Exec=@@GHOSTTY@@ --launched-from=desktop
TryExec=@GHOSTTY@
Exec=@GHOSTTY@ --launched-from=desktop
Icon=com.mitchellh.ghostty
Categories=System;TerminalEmulator;
Keywords=terminal;tty;pty;
StartupNotify=true
StartupWMClass=@@APPID@@
StartupWMClass=@APPID@
Terminal=false
Actions=new-window;
X-GNOME-UsesNotifications=true
@ -22,4 +22,4 @@ DBusActivatable=true
[Desktop Action new-window]
Name=New Window
Exec=@@GHOSTTY@@ --launched-from=desktop
Exec=@GHOSTTY@ --launched-from=desktop

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>@@APPID@@</id>
<launchable type="desktop-id">@@APPID@@.desktop</launchable>
<name>@@NAME@@</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
View File

@ -0,0 +1,3 @@
[D-BUS Service]
Name=@APPID@
Exec=@GHOSTTY@ --launched-from=dbus

View File

@ -1,4 +1,4 @@
[D-BUS Service]
Name=@@APPID@@
SystemdService=@@APPID@@.service
Exec=@@GHOSTTY@@ --launched-from=dbus
Name=@APPID@
SystemdService=@APPID@service
Exec=@GHOSTTY@ --launched-from=dbus

View File

@ -1,7 +1,7 @@
[Unit]
Description=@@NAME@@
Description=@NAME@
[Service]
Type=dbus
BusName=@@APPID@@
ExecStart=@@GHOSTTY@@ --launched-from=systemd
BusName=@APPID@
ExecStart=@GHOSTTY@ --launched-from=systemd

View File

@ -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,182 +221,176 @@ 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,
);
const name = b.fmt("Ghostty{s}", .{
switch (cfg.optimize) {
.Debug, .ReleaseSafe => " (Debug)",
.ReleaseFast, .ReleaseSmall => "",
},
});
return .{ .steps = steps.items };
}
const app_id = b.fmt("com.mitchellh.ghostty{s}", .{
switch (cfg.optimize) {
.Debug, .ReleaseSafe => "-debug",
.ReleaseFast, .ReleaseSmall => "",
},
});
/// 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(
formatService(
b,
cfg,
name,
app_id,
b.path("dist/linux/app.desktop.in"),
b.fmt(
"share/applications/{s}.desktop",
.{app_id},
),
),
);
try ts.append(.{
b.path("dist/linux/app.desktop.in"),
b.fmt("share/applications/{s}.desktop", .{app_id}),
});
// Service for DBus activation.
try steps.append(
formatService(
b,
cfg,
name,
app_id,
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},
),
),
);
// systemd user service
if (!cfg.flatpak)
try steps.append(
formatService(
b,
cfg,
name,
b.fmt("share/dbus-1/services/{s}.service", .{app_id}),
});
// 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,
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,
},
),
),
);
// AppStream metainfo so that application has rich metadata within app stores
try steps.append(
formatService(
b,
cfg,
name,
app_id,
b.path("dist/linux/com.mitchellh.ghostty.metainfo.xml.in"),
b.fmt(
"share/metainfo/{s}.metainfo.xml",
.{app_id},
),
},
),
});
// 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}),
});
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,
});
const copy = b.addInstallFile(
tpl.getOutput(),
template[1],
);
// 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);
try steps.append(&copy.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);
// 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);
// 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);
}
// 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 {
const b = self.steps[0].owner;
for (self.steps) |step| b.getInstallStep().dependOn(step);
}
pub fn formatService(
b: *std.Build,
cfg: *const Config,
name: []const u8,
app_id: []const u8,
src: std.Build.LazyPath,
dest: []const u8,
) *std.Build.Step {
var cmd = b.addExecutable(
.{
.name = "desktop-template",
.root_source_file = b.path("src/build/desktop_template.zig"),
.target = b.graph.host,
},
);
const options = b.addOptions();
options.addOption([]const u8, "app_id", app_id);
options.addOption([]const u8, "name", name);
options.addOption([]const u8, "ghostty", b.fmt("{s}/bin/ghostty", .{b.install_prefix}));
options.addOption(bool, "flatpak", cfg.flatpak);
cmd.root_module.addOptions("cfg", options);
const run = b.addRunArtifact(cmd);
run.setStdIn(.{ .lazy_path = src });
const output = run.captureStdOut();
return &b.addInstallFile(output, dest).step;
}

View File

@ -1,31 +0,0 @@
const std = @import("std");
const cfg = @import("cfg");
pub fn main() !void {
var debug: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug.deinit();
const alloc = debug.allocator();
const stdin = std.io.getStdIn();
const stdout = std.io.getStdOut();
var input = stdin.reader();
while (try input.readUntilDelimiterOrEofAlloc(alloc, '\n', 4096)) |line| {
defer alloc.free(line);
const buf1 = try std.mem.replaceOwned(u8, alloc, line, "@@APPID@@", cfg.app_id);
defer alloc.free(buf1);
const buf2 = try std.mem.replaceOwned(u8, alloc, buf1, "@@NAME@@", cfg.name);
defer alloc.free(buf2);
const buf3 = try std.mem.replaceOwned(u8, alloc, buf2, "@@GHOSTTY@@", cfg.ghostty);
defer alloc.free(buf3);
if (cfg.flatpak and std.mem.startsWith(u8, buf3, "SystemdService=")) continue;
try stdout.writeAll(buf3);
try stdout.writeAll("\n");
}
}