Skip to content

Commit babe1b6

Browse files
feat(wrapperModules.ghostty): init
Writes settings to a Nix store config file and loads it via --config-default-files=false --config-file=<path>, isolating the wrapper from the host config in both installed and ephemeral use. The host config path varies by platform: ~/.config/ghostty/config on Linux, and ~/Library/Application Support/com.mitchellh.ghostty/config on macOS. Ghostty +actions and --help use minimal option parsers that reject unknown flags and exit silently, so argv0type bypasses flag injection for these. +show-config and +validate-config are subject to the same parser limitation and will show the host config rather than the generated one. On macOS, ghostty-bin is used by default (the pre-built binary distribution recommended for nix-darwin by the Ghostty installation docs).
1 parent c2b76d3 commit babe1b6

3 files changed

Lines changed: 162 additions & 0 deletions

File tree

maintainers/default.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,9 @@
111111
github = "allen-liaoo";
112112
githubId = 16383622;
113113
};
114+
trustworthyadult = {
115+
name = "Michael Ross";
116+
github = "TrustworthyAdult";
117+
githubId = 104172948;
118+
};
114119
}

wrapperModules/g/ghostty/check.nix

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
pkgs,
3+
self,
4+
tlib,
5+
...
6+
}:
7+
let
8+
inherit (tlib) isFile fileContains test;
9+
wrapper = self.wrappers.ghostty.wrap {
10+
inherit pkgs;
11+
settings = {
12+
font-size = 14;
13+
theme = "catppuccin-mocha";
14+
window-decoration = false;
15+
keybind = [
16+
"ctrl+a>-=new_split:down"
17+
"ctrl+a>==new_split:right"
18+
];
19+
};
20+
};
21+
configFile = "${wrapper}/ghostty-config";
22+
in
23+
test { wrapper = "ghostty"; } {
24+
"ghostty wrapper binary should exist" = [ (isFile "${wrapper}/bin/ghostty") ];
25+
26+
"ghostty wrapper should disable default config and point to generated file" = [
27+
(fileContains "${wrapper}/bin/ghostty" "--config-default-files=false")
28+
(fileContains "${wrapper}/bin/ghostty" "--config-file=")
29+
];
30+
31+
"ghostty wrapper should bypass config flags for +actions and --help" = [
32+
(fileContains "${wrapper}/bin/ghostty" "_ghostty_arg")
33+
(fileContains "${wrapper}/bin/ghostty" "--help")
34+
];
35+
36+
"ghostty config file should exist" = [ (isFile configFile) ];
37+
38+
"ghostty settings should appear in generated config" = [
39+
(fileContains configFile "font-size = 14")
40+
(fileContains configFile "theme = catppuccin-mocha")
41+
(fileContains configFile "window-decoration = false")
42+
];
43+
44+
"ghostty list settings should serialize as duplicate keys" = [
45+
(fileContains configFile "keybind = ctrl+a>-=new_split:down")
46+
(fileContains configFile "keybind = ctrl+a>==new_split:right")
47+
];
48+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
{
2+
config,
3+
wlib,
4+
lib,
5+
pkgs,
6+
...
7+
}:
8+
let
9+
toGhosttyConf = lib.generators.toKeyValue {
10+
listsAsDuplicateKeys = true;
11+
mkKeyValue = lib.generators.mkKeyValueDefault {
12+
mkValueString = v: if builtins.isBool v then lib.boolToString v else toString v;
13+
} " = ";
14+
};
15+
in
16+
{
17+
imports = [ wlib.modules.default ];
18+
19+
options.settings = lib.mkOption {
20+
type =
21+
let
22+
atom = lib.types.oneOf [
23+
lib.types.bool
24+
lib.types.float
25+
lib.types.int
26+
lib.types.str
27+
];
28+
in
29+
lib.types.attrsOf (lib.types.either (lib.types.listOf atom) atom);
30+
default = { };
31+
example = lib.literalExpression ''
32+
{
33+
font-size = 14;
34+
theme = "catppuccin-mocha";
35+
window-decoration = false;
36+
keybind = [
37+
"ctrl+a>-=new_split:down"
38+
"ctrl+a>==new_split:right"
39+
];
40+
}
41+
'';
42+
description = ''
43+
Ghostty configuration written to a generated config file. The wrapper
44+
passes {option}`--config-default-files=false` and
45+
{option}`--config-file=<generated>`, so the host config is never loaded
46+
-- including when the wrapper is used ephemerally on a machine that has
47+
its own Ghostty config. The host config path varies by platform:
48+
{file}`~/.config/ghostty/config` on Linux, and
49+
{file}`~/Library/Application Support/com.mitchellh.ghostty/config` on
50+
macOS.
51+
See <https://ghostty.org/docs/config/reference> for all available options.
52+
53+
Note: if you pass an additional {option}`--config-file` flag at runtime,
54+
Ghostty will merge it on top of the generated config (later files take
55+
precedence for conflicting keys).
56+
57+
Note: {command}`ghostty +show-config` and {command}`ghostty
58+
+validate-config` bypass the generated config entirely (their option
59+
parsers reject {option}`--config-file`), so they will show the host
60+
config if one exists -- this affects both installed and ephemeral use.
61+
62+
On macOS, `ghostty-bin` is used by default, which is the pre-built
63+
binary distribution [recommended for nix-darwin](https://ghostty.org/docs/install/binary)
64+
by the Ghostty installation docs.
65+
'';
66+
};
67+
68+
config = {
69+
package = lib.mkDefault (if pkgs.stdenv.isDarwin then pkgs.ghostty-bin else pkgs.ghostty);
70+
constructFiles.ghosttyConfig = {
71+
content = toGhosttyConf config.settings;
72+
relPath = "${config.binName}-config";
73+
};
74+
addFlag = [
75+
"--config-default-files=false"
76+
"--config-file=${config.constructFiles.ghosttyConfig.path}"
77+
];
78+
# Ghostty has no environment variable for specifying a config file, so CLI
79+
# flags are the only option: --config-default-files=false prevents loading
80+
# the host's ~/.config/ghostty/config (including on machines where one
81+
# exists), and --config-file points at the generated one.
82+
#
83+
# However, Ghostty +actions and --help have their own minimal Options
84+
# struct with no _diagnostics field. Any flag unrecognised by the action
85+
# parser causes error.InvalidField and a silent exit with no output.
86+
# argv0type detects these and execs Ghostty directly without injecting any
87+
# flags.
88+
#
89+
# Consequence: +show-config and +validate-config will show the host's
90+
# ~/.config/ghostty/config if present, not the generated config, because
91+
# their option parsers also reject --config-file. This applies equally to
92+
# installed and ephemeral use.
93+
argv0type =
94+
let
95+
binPath = lib.escapeShellArg config.wrapperPaths.input;
96+
in
97+
cmd: ''
98+
for _ghostty_arg in "$@"; do
99+
case "$_ghostty_arg" in
100+
+*|--help) exec -a "$0" ${binPath} "$@";;
101+
esac
102+
done
103+
exec -a "$0" ${cmd}
104+
'';
105+
meta.description = "Ghostty terminal emulator";
106+
meta.maintainers = [ wlib.maintainers.trustworthyadult ];
107+
meta.platforms = lib.platforms.linux ++ lib.platforms.darwin;
108+
};
109+
}

0 commit comments

Comments
 (0)