This PR implements a more lightweight alternative to #5326 that contains
features that I personally think Just Make Sense for the bell.
No configs, no GStreamer stuff, just sane defaults to get us started.
This introduces a syntax for `command` and `initial-command` that allows
the user to specify whether it should be run via `/bin/sh -c` or not.
The syntax is a prefix `direct:` or `shell:` prior to the command,
with no prefix implying a default behavior as documented.
Previously, we unconditionally ran commands via `/bin/sh -c`, primarily
to avoid having to do any shell expansion ourselves. We also leaned on
it as a crutch for PATH-expansion but this is an easy problem compared
to shell expansion.
For the principle of least surprise, this worked well for configurations
specified via the config file, and is still the default. However, these
configurations are also set via the `-e` special flag to the CLI, and it
is very much not the principle of least surprise to have the command run via
`/bin/sh -c` in that scenario since a shell has already expanded all the
arguments and given them to us in a nice separated format. But we had no
way to toggle this behavior.
This commit introduces the ability to do this, and changes the defaults
so that `-e` doesn't shell expand. Further, we also do PATH lookups
ourselves for the non-shell expanded case because thats easy (using
execvpe style extensions but implemented as part of the Zig stdlib). We don't
do path expansion (e.g. `~/`) because thats a shell expansion.
So to be clear, there are no two polar opposite behavioes here with
clear semantics:
1. Direct commands are passed to `execvpe` directly, space separated.
This will not handle quoted strings, environment variables, path
expansion (e.g. `~/`), command expansion (e.g. `$()`), etc.
2. Shell commands are passed to `/bin/sh -c` and will be shell expanded
as per the shell's rules. This will handle everything that `sh`
supports.
In doing this work, I also stumbled upon a variety of smaller
improvements that could be made:
- A number of allocations have been removed from the startup path that
only existed to add a null terminator to various strings. We now
have null terminators from the beginning since we are almost always
on a system that's going to need it anyways.
- For bash shell integration, we no longer wrap the new bash command
in a shell since we've formed a full parsed command line.
- The process of creating the command to execute by termio is now unit
tested, so we can test the various complex cases particularly on
macOS of wrapping commands in the login command.
- `xdg-terminal-exec` on Linux uses the `direct:` method by default
since it is also assumed to be executed via a shell environment.
This change consolidates all three opt-out shell integration environment
variables into a single opt-in $GHOSTTY_SHELL_FEATURES variable. Its
value is a comma-delimited list of the enabled shell feature names (e.g.
"cursor,title").
$GHOSTTY_SHELL_FEATURES is set at runtime and automatically added to the
shell environment. Its value is based on the shell-integration-features
configuration option.
$GHOSTTY_SHELL_FEATURES is only set when at least one shell feature is
enabled. It won't be set when 'shell-integration-features = false'.
$GHOSTTY_SHELL_FEATURES lists only the enabled shell feature names. We
could have alternatively gone in the opposite direction and listed the
disabled features, letting the scripts assume each feature is on by
default like we did before, but I think this explicit approach is a
little safer and easier to reason about / debug.
It also doesn't support the "no-" negation prefix used by the config
system (e.g. "cursor,no-title"). This simplifies the implementation
requirements of our (multiple) shell integration scripts, and because
$GHOSTTY_SHELL_FEATURES is derived from shell-integration-features,
the user-facing configuration interface retains that expressiveness.
$GHOSTTY_SHELL_FEATURES is intended to primarily be an internal concern:
an interface between the runtime and our shell integration scripts. It
could be used by people with particular use cases who want to manually
source those scripts, but that isn't the intended audience.
... and because the previous $GHOSTTY_SHELL_INTEGRATION_NO_* variables
were also meant to be an internal concern, this change does not include
backwards compatibility support for those names.
One last advantage of a using a single $GHOSTTY_SHELL_FEATURES variable
is that it can be easily forwarded to e.g. ssh sessions or other shell
environments.
This allows `termio.Exec` to track processes spawned via
`FlatpakHostCommand`, finally allowing Ghostty to function as a
Flatpak.
Alongside this is a few bug fixes:
* Don't add ghostty to PATH when running in flatpak mode since it's
unreachable.
* Correctly handle exit status returned by Flatpak. Previously this was
not processed and contains extra status bits.
* Use correct type for PID returned by Flatpak.
Related to #3224
Previously, Ghostty used a static API for async event handling: io_uring
on Linux, kqueue on macOS. This commit changes the backend to be dynamic
on Linux so that epoll will be used if io_uring isn't available, or if
the user explicitly chooses it.
This introduces a new config `async-backend` (default "auto") which can
be set by the user to change the async backend in use. This is a
best-effort setting: if the user requests io_uring but it isn't
available, Ghostty will fall back to something that is and that choice
is up to us.
Basic benchmarking both in libxev and Ghostty (vtebench) show no
noticeable performance differences introducing the dynamic API, nor
choosing epoll over io_uring.
In Termio.init, we make a copy of backend and modify it by calling
initTerminal. However, we used the original in the struct definition.
This lead to the pty being opened with a size 0,0.
Fixes#5257
Specify environment variables to pass to commands launched in a terminal
surface. The format is `env=KEY=VALUE`.
`env = foo=bar`
`env = bar=baz`
Setting `env` to an empty string will reset the entire map to default
(empty).
`env =`
Setting a key to an empty string will remove that particular key and
corresponding value from the map.
`env = foo=bar`
`env = foo=`
will result in `foo` not being passed to the launched commands. Setting
a key multiple times will overwrite previous entries.
`env = foo=bar`
`env = foo=baz`
will result in `foo=baz` being passed to the launched commands.
These environment variables _will not_ be passed to commands run by
Ghostty for other purposes, like `open` or `xdg-open` used to open URLs
in your browser.
Fixes#5257
Specify environment variables to pass to commands launched in a terminal
surface. The format is `env=KEY=VALUE`.
`env = foo=bar`
`env = bar=baz`
Setting `env` to an empty string will reset the entire map to default
(empty).
`env =`
Setting a key to an empty string will remove that particular key and
corresponding value from the map.
`env = foo=bar`
`env = foo=`
will result in `foo` not being passed to the launched commands.
Setting a key multiple times will overwrite previous entries.
`env = foo=bar`
`env = foo=baz`
will result in `foo=baz` being passed to the launched commands.
These environment variables _will not_ be passed to commands run by Ghostty
for other purposes, like `open` or `xdg-open` used to open URLs in your
browser.
In Termio.init, we make a copy of backend and modify it by calling
initTerminal. However, we used the original in the struct definition.
This lead to the pty being opened with a size 0,0.
The Ghostty implementation of OSC 21 (Kitty color protocol) currently
responds to *all* OSC 21 sequences. It should not respond to a set, nor
a reset command. Fix the implementation so that we only respond if a
query was received.
Caused by #5650
I actually don't understand how this didn't happen before or why we
didn't notice it but it seems like the envmap was never freed. In the
latest debug builds prior to this build GPA reports the leak.
We should free the envmap when the subprocess is deinitialized. But also
we can free the env map as soon as we start the subprocess which saves
some small amount of memory at runtime.
Additionally, we should only be freeing the envmap on error if we
created it.
`WINDOWID` is the conventional environment variable for scripts that
want to know the X11 window ID of the terminal, so that it may call
tools like `xprop` or `xdotool`. We already know the window ID for
window protocol handling, so we might as well throw this in for
convenience.
Fixes#4884
When our command exits, it will close the pty slave fd. This will
trigger a HUP on our poll. Previously, we only checked for IN. When a fd
is closed, IN triggers forever which would leave to an infinite loop and
100% CPU.
Now, detect the HUP and exit the read thread.
Multiple fixes to prevent file descriptor leaks:
- libxev eventfd now uses CLOEXEC
- linux: cgroup clone now uses CLOEXEC for the cgroup fd
- termio pipe uses pipe2 with CLOEXEC
- pty master always sets CLOEXEC because the child doesn't need it
- termio exec now closes pty slave fd after fork
There still appear to be some fd leaks happening. They seem related to
GTK, they aren't things we're accessig directly. I still want to
investigate them but this at least cleans up the major sources of fd
leakage.
A '-' or '--' argument signals the end of bash's own options. All
remaining arguments are treated as filenames and arguments. We shouldn't
perform any additional argument processing once we see this signal.
We could also assume a non-interactive shell session in this case unless
the '-i' (interactive) shell option has been explicitly specified, but
let's wait on that until we know that doing so would solve a real user
problem (and avoid any false negatives).
## Descriptions
The code was short-circuiting the shell integration setup when
`shell-integration = none`, which prevented the feature environment
variables from being set. These environment variables are needed even
for manual shell integration to work properly.
## Changes
- Extracted feature environment variables setup into a separate
`setup_features` function
- Modified the shell integration initialization to ensure features are
set up even when `shell-integration = none`
<img width="1126" alt="image"
src="https://github.com/user-attachments/assets/ceeb33f5-26ee-4a3b-a6d5-eed57848c96c"
/>
Fixes https://github.com/ghostty-org/ghostty/issues/5046
login(1)'s .hushlogin logic was "fixed" in macOS Sonoma 14.4, so this
comment (and our workaround) is only relevant for versions earlier than
that.
The relevant change to login/login.c is part of system_cmds-979.100.8.
> login.c: look for .hushlogin in home directory (112854361)
- 1bca46ecc5
- https://github.com/apple-oss-distributions/distribution-macOS/tree/macos-144
If the read thread has already exited, it will have closed the read end
of the quit pipe. Unless SIGPIPE is masked with signal(SIGPIPE, SIG_IGN),
or the macOS-specific fcntl(F_SETNOSIGPIPE), writing to the write end of
a broken pipe kills the writer with SIGPIPE instead of returning -EPIPE
as an error. This causes a crash if the read thread exits before
threadExit.
This was already a possible race condition if read() returns
error.NotOpenForReading or error.InputOutput, but it's now much easier to
trigger due to the recent "termio/exec: fix 100% CPU usage after
wait-after-command process exits" fix.
Fix this by closing the quit pipe instead of writing to it.
'--posix' starts bash in POSIX mode (like /bin/sh). This is rarely used
for interactive shells, and removing automatic shell integration support
for this option allows us to simply/remove some exceptional code paths.
Users are still able to manually source the shell integration script.
Also fix an issue where we would still inject GHOSTTY_BASH_RCFILE if we
aborted the automatic shell integration path _after_ seeing an --rcfile
or --init-file argument.
The renderer must track if the foreground, background, and cursor colors
are explicitly set by an OSC so that changes are not overridden when the
config file is reloaded.
`std.fs.accessAbsolute` asserts if the user proposed path is absolute,
which we are seemingly passing as-is with no validating that it is.
When running with safety checks on, passing non-absolute path to
--working-directory will make ghostty crash.
I changed it to use `Dir.access`, which is just `accessAbsolute` without
the check.
This has the side effect of also allowing relative working directory.
We're packaging more and more application-specific data directories in
our application bundle. It's helpful to add that path to XDG_DATA_DIRS
so those applications (that support XDG_DATA_DIRS) can locate their data
directories without additional user-level configuration.
This also fixes a typo ("MATHPATH") in the nearby MANPATH-building code.
Bash shell detection was originally disabled in #1823 due to problems
with /bin/bash on macOS.
Apple distributes their own patched version of Bash 3.2 on macOS that
disables the POSIX-style $ENV-based startup path:
e5397a7e74/bash-3.2/shell.c (L1112-L1114)
This means we're unable to perform our automatic shell integration
sequence in this specific environment. Standard Bash 3.2 works fine.
Knowing this, we can re-enable bash shell detection by default unless
we're running "/bin/bash" on Darwin. We can safely assume that's the
unsupported Bash executable because /bin is non-writable on modern macOS
installations due to System Integrity Protection.
macOS users can either manually source our shell integration script
(which otherwise works fine with Apple's Bash) or install a standard
version of Bash from Homebrew or elsewhere.
Fixes#2857
Some terminal modes always reset, but there are others that should be
conditional based on how the terminal's default state is configured.
Primarily from #2857 is the grapheme clustering mode (mode 2027) which
was always resetting to false but should be conditional based on the
the `grapheme-width-method` configuration.
Make the foreground_color and background_color fields in the Terminal
struct optional values so that we can determine if a foreground or
background color was explicitly set with an OSC 10 or OSC 11 sequence.
This makes the logic a bit simpler to reason about (i.e.
`foreground_color` is now always "the color set by an OSC 10 sequence"
while `default_foreground_color` is always "the color set by the config
file") and also fixes an issue where an OSC 10 or OSC 11 query would not
report the correct color after a config update changed the foreground or
background color.
The `cursor_color` field was already optional, with the same semantics
(it is only non-null when explicitly set with an OSC 12) so this brings
all three of these fields into alignment.
Related #2755
From the mode 2031 spec[1]:
> Send CSI ? 2031 h to the terminal to enable unsolicited DSR (device status
> report) messages for color palette updates and CSI ? 2031 l respectively to
> disable it again.
>
> The sent out DSR looks equivalent to the already above mentioned. This
> notification is not just sent when dark/light mode has been changed by the
> operating system / desktop, but also if the user explicitly changed color
> scheme, e.g. by configuration.
My reading of this paired with the original discussion is that this is
meant to be sent out for anything that could possibly change terminal
colors.
Previous to this commit, we only sent out the DSR when the actual system
light/dark mode changed. This commit changes it to send out the DSR on
any operation that _may_ change the terminal colors.
[1]: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/#example-source-code