Fixes#7500
Supersedes #7582
This commit moves the child exit handling logic from the IO thead to the
apprt thread. The IO thread now only sends a `child_exited` message to
the apprt thread with metadata about the exit conditions (exit code,
runtime).
From there, the apprt thread can handle the exit situation however is
necessary. This commit doesn't change the behavior but it does fix the
issue #7500. The behavior is: exit immediately, show abnormal exit
message, wait for user input, etc.
This also gets us closer to #7649.
We previously only showed this message if the user had
`wait-after-command` set to true, since if its false the surface would
close anyways.
With the latest undo feature on macOS, this is no longer the case; a
exited process can be undone and reopened. I considered disallowing
undoing an exited surface, but I think there is value in being able to
go back and recapture output in scrollback if you wanted to.
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 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.
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.
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.
## 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.
`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.
Fixes#2273
On macOS, killpg is expected to fail with EPERM because of the way we
launch a login process around it. Before this commit, this caused us to
never call waitpid and reap the child process, which caused the child
process to stick around as a zombie.
This commit allows killpg to fail with EPERM on macOS and fall through
to waitpid.
Quoting `man man`:
> If MANPATH begins with a colon, it is appended to the default list;
Alternatively we can think about:
> if it ends with a colon, it is prepended to the default list;
To take preference over existing values, but that shouldn't be really a
problem, as there rather isn't much of another projects named `ghostty`.