Replaces #9785
This adds session search to the command palette on macOS. Session search
lets you jump to any running terminal based on title or working
directory. The command palette shows you the title, working directory,
and tab color (if any) to help you identify the terminal you care about.
This also enhances our command palette in general to support better
sorting, more stable sorts when keys are identical, etc.
## Demo
https://github.com/user-attachments/assets/602a9424-e182-4651-bf08-378e9c5e1616
## Future
Since this inherits the command palette infrastructure, we don't have
fuzzy search capabilities yet, but I still think this is useful already.
We should add fuzzy searching in the future.
Thanks @phaistonian for the inspiration here but I did do a full
rewrite.
**AI disclosure:** I used AI to assist with this in places, but I
understand everything and touched up almost everything manually.
This PR fixes an issue in the change I merged yesterday (#9921), which
was reported by @quonb. Apology
I verified the fix by testing a wide range of URL schemes:
```
echo "https://example.com"
echo "http://example.com"
echo "mailto:test@example.com"
echo "ftp://example.com/file.txt"
echo "file:/Users/you/file.txt"
echo "ssh:user@example.com"
echo "git://github.com/user/repo.git"
echo "ssh://example.com/path"
echo "tel:+12123456789"
echo "ipns://example.com/path"
echo "gemini://example.com/"
echo "gopher://example.com/1menu"
echo "news:comp.lang.zig"
```
#1123 added a warning when the OpenGL version is too old, but it is
never used because GTK enforces the version set with
gl_area.setRequiredVersion() before prepareContext() is called: we end
up with a generic "failed to make GL context" error:
```
warning(gtk_ghostty_surface): failed to make GL context current: Unable to create a GL context warning(gtk_ghostty_surface): this error is almost always due to a library, driver, or GTK issue warning(gtk_ghostty_surface): this is a common cause of this issue: https://ghostty.org/docs/help/gtk-opengl-context
```
This patch removes the requirement at the GTK level and lets the ghostty
renderer check, now failing as follow:
```
info(opengl): loaded OpenGL 4.2
error(opengl): OpenGL version is too old. Ghostty requires OpenGL 4.3 warning(gtk_ghostty_surface): failed to initialize surface err=error.OpenGLOutdated warning(gtk_ghostty_surface): surface failed to initialize err=error.SurfaceError
```
(Note that this does not render a ghostty window, unlike the previous
error which rendered the "Unable to acquire an OpenGL context for
rendering." view, so while the error itself is easier to understand it
might be harder to view)
-----------------------
I looked for why I couldn't get a context at all mostly to see what
OpenGL 4.3 functions were required to see if there'd be easy fallbacks,
but it also took me a good 15 minutes this morning to understand that
there was nothing wrong with my gtk/mesa versions and it's just my
hardware being old, so I think it'd good to take in itself even if the
displaying itself is a bit meh.
Happy to try to display it in the error page if you think it's important
and I can figure out how (haven't looked much yet)
#1123 added a warning when the OpenGL version is too old, but
it is never used because GTK enforces the version set with
gl_area.setRequiredVersion() before prepareContext() is called:
we end up with a generic "failed to make GL context" error:
warning(gtk_ghostty_surface): failed to make GL context current: Unable to create a GL context
warning(gtk_ghostty_surface): this error is almost always due to a library, driver, or GTK issue
warning(gtk_ghostty_surface): this is a common cause of this issue: https://ghostty.org/docs/help/gtk-opengl-context
This patch removes the requirement at the GTK level and lets the ghostty
renderer check, now failing as follow:
info(opengl): loaded OpenGL 4.2
error(opengl): OpenGL version is too old. Ghostty requires OpenGL 4.3
warning(gtk_ghostty_surface): failed to initialize surface err=error.OpenGLOutdated
warning(gtk_ghostty_surface): surface failed to initialize err=error.SurfaceError
(Note that this does not render a ghostty window, unlike the previous
error which rendered the "Unable to acquire an OpenGL context for
rendering." view, so while the error itself is easier to understand it
might be harder to view)
This is a subcommand I've been using for some time. It takes an optional
issue/PR number as context and produces a prompt to review a branch for
how well it addresses the issue along with any isolated issues it spots.
Example:
https://ampcode.com/threads/T-019b2877-475f-758d-ae88-93c722561576
This is a subcommand I've been using for some time. It takes an optional
issue/PR number as context and produces a prompt to review a branch for
how well it addresses the issue along with any isolated issues it spots.
Example: https://ampcode.com/threads/T-019b2877-475f-758d-ae88-93c722561576
Related Issue: #5047
Discussion: #4664
### Investigation
The behavior of iTerm mentioned on the Discussion thread was as follows:
- `cmd + u` can be used to toggle "Use Transparency"
- The "Use Transparency" toggle operates on a per-surface basis
- The "Use Transparency" state persists even after reloading the config
### Summary
Based on the investigation and discussions in the preceding pull
requests, this implements the `toggle_background_opacity` keybind action
for macOS with the following specifications:
- Switches background opacity on a per-surface basis
- The background opacity state persists even after reloading the config
- Background opacity switching functionality is also available in Quick
Terminal
- Does nothing if `background-opacity` is set to 1 or higher
- Does nothing if in fullscreen mode
### Verification
This functionality has been tested across following scenarios,
confirming correct action behavior for:
- Split Window
- Each split window maintains synchronized background opacity states
- Tab Group
- Background opacity states do not synchronize between tabs
- Quick Terminal
- Background opacity states remain synchronized even when windows are
closed
- Command Palette
### AI Disclosure
This pull request was made with assistance from Claude Code.
I reviewed all AI-generated code and wrote the final output manually.
https://github.com/user-attachments/assets/e46ff8f0-42f2-442f-97dd-d5f5c33b10f1
This PR fixes an issue #9563 where relative file paths were not being
resolved against the terminal’s current working directory before
opening.
#### Verification
Tested with directories containing:
```
/tmp/test/test
❯ du -h .
0B ./spaces-end
0B ./with dot.
0B ./space middle
8.0K .
```
Parent directory resolution also works as expected:
```
/tmp/test/test
❯ du -h ..
0B ../test/spaces-end
0B ../test/with dot.
0B ../test/space middle
8.0K ../test
16K ..
```
@mitchellh
In your original description you mentioned that “Links should work for all situations as they do in iTerm2.”
I noticed that, for example, when running `ls`, the paths are not clickable, while they are clickable in iTerm2.
If you think this case should also be handled, I can open a separate PR for it once this one is accepted.
Implements #8399.
I didn't save `position` in this pr, since I don't see its necessity.
Changing `quick-terminal-position` requires a relaunch; saving and
restoring this would have to deal with conflicts. I also don't see why a
user would change this frequently.
> [!NOTE]
> Used GPT to proofread my comments
This adds a couple optimizations to our CI:
1. macOS matrix gone, only test that freetype builds in addition to
Coretext.
2. Move Flatpak out to a triggered build on main similar to snaps. This
was our longest build and avoiding it in PRs is a win since it rarely
fails.
This changes our GHA that updates our color schemes to also upload it to
our dependency mirror at the same time. This prevents issues where the
upstream disappears, which we've had many times.
This disables a bunch of configurations that we don't need to actually
test for. The main one we want to keep building is Freetype because we
sometimes use this to compare behaviors, but Coretext is the default.
This is one of the primary drivers of CI CPU time.
This changes our GHA that updates our color schemes to also upload it
to our dependency mirror at the same time. This prevents issues where
the upstream disappears, which we've had many times.
I had a bit of the same annoyance as #9064. I agree that with balanced
padding, you should expect horizontal jitter when resizing horizontally,
and vertical jitter when resizing vertically. Diagonal jitter, however,
happened because the top padding was upper bounded by the left padding,
coupling the vertical and horizontal wobbling. This looks kind of janky,
and it's surprising that the balance between top and bottom padding
changes as you vary only the width of the window.
With this PR, the upper bound is instead equal to the maximum left
padding. Since this only depends on the config, not the current window
width, the diagonal wobbling is avoided. Both the top and left padding
still have the same range of motion as before.
An alternative could be to bound by the minimum or median left padding
instead. Open to suggestions.
**Before**
https://github.com/user-attachments/assets/d12c5870-f05d-450f-89fc-c59eab90e199
**After**
https://github.com/user-attachments/assets/35b80bb0-9ea2-41c1-8502-3a8eec51dbc6
## Description
This PR implements the basic functionality for "Liquid Glass" style
background support (#8155). I used OpenCode (Claude 4) in some capacity
to write this as I'm not super familiar with AppKit / SwiftUI. A good
chunk of this I still needed to write by hand since Claude doesn't
understand the Glass APIs, but I'm not 100% if the implementation here
makes the best decisions since the practices in Ghostty config and
separation of the AppKit code and SwiftUI seemed inconsistent to me.
Some of the combinations of options obviously create entirely unreadable
terminals, but I've found that regular glass and transparent with
opacity to be fairly readable. We *don't* enable this feature by default
since it would of course break existing users setups.
## Open Questions
- [x] How to determine the correct cornerRadius? For now this is
eyeballed, I can't see any macOS public API or clearly documented
constants.
- [x] Should boolean options be exposed for reasonable defaults?
- [x] Should the option need to be namespaced to macos-\*?
## Screenshots
0% Opacity, Regular
<img width="917" height="683" alt="image"
src="https://github.com/user-attachments/assets/ccb96ba7-5df2-4284-8526-e07bbb62e3e5"
/>
50% Opacity, Transparent
<img width="880" height="680" alt="image"
src="https://github.com/user-attachments/assets/5bdf12f9-3209-4aa9-8a4f-9a6eb4f95894"
/>
0% Opacity, Transparent
<img width="860" height="681" alt="image"
src="https://github.com/user-attachments/assets/1b33d400-4d8b-479a-94d7-47b844743e52"
/>
Reported by Mr. Hashimoto in discord.
These changes removedthe following warning:
`evaluation warning: 'system' has been renamed to/replaced by
'stdenv.hostPlatform.system'`
This adds a couple of Nix-based VM integration tests:
- Does `ghostty +version` run successfully?
- Can we create a new terminal window? (This is detected by setting the
background to a color that doesn't appear
normally on the desktop and seeing if we can detect that color in a
screenshot).
Obviously more can be done but I thought that these would be a couple of
good first steps.
The whole test suite can be run with `nix flake check`. Individual tests
can be run with a command like this:
```
nix run .#check.x86_64-linux.<test name>.driver
```
Bug: When toggling a Ghostty window between fullscreen and windowed mode
in the i3 window manager, or between tabbed mode and default tiling
mode, window borders would permanently stop being drawn around the
terminal window.
I bisected to find that the issue was first introduced in aa2dbe2.
The below explanation + the code in this PR was 100% generated by claude
code, since I don't know the first thing about x11 or terminal
development, and have never written any Zig before. I verified this code
change builds and solves the issue for me which I hope is more helpful
than just a bug report. If this code is actually garbage and the issue
should be fixed a different way it wouldn't shock me and this PR can
just be closed in favor of one from someone who knows what they're
doing.
Anyway this is the explanation that the llm generated:
Root cause was that syncAppearance() was updating X11 properties on
every call during window transitions, even when values hadn't changed.
These redundant property updates interfered with i3's border management.
The fix adds caching to syncBlur() and syncDecorations() to only update
X11 properties when values actually change, eliminating unnecessary
property changes during fullscreen transitions.
When toggling a Ghostty window between fullscreen and windowed mode in
the i3 window manager, window borders would disappear and not return.
Root cause was that syncAppearance() was updating X11 properties on
every call during window transitions, even when values hadn't changed.
These redundant property updates interfered with i3's border management.
The fix adds caching to syncBlur() and syncDecorations() to only update
X11 properties when values actually change, eliminating unnecessary
property changes during fullscreen transitions.
Closes#9894
**Problem**: Since #9850, a local build incorrectly identifies itself as
1.3.0 stable (in lieu of tip w/ commit info).
**Bug**: `@import("root")` in `Config.zig` resolves to the compilation
root (`main.zig`), not `build.zig` where the marker was defined. So
`@hasDecl` always returned false (i.e. all builds treated as
dependencies).
**Solution**: More or less @rockorager's original approach from #9850.
> _Use `@src().file` to get ghostty's source directory and compare it
with `b.build_root`. When they differ, ghostty is a dependency and we
skip git detection._
However, there is a potential edge case where if a downstream project
also has a `src/build/Config.zig` this will fail silently - seems
unlikely, but worth noting.
**Testing**:
```
$ zig build -p $HOME/.local -Doptimize=ReleaseFast
$ ghostty --version
Ghostty 1.3.0-main+a0a915a06
Version
- version: 1.3.0-main+a0a915a06
- channel: tip
```
---
> **AI Disclosure**: I used Claude Code to review and identify edge
cases.
Bumps
[actions/download-artifact](https://github.com/actions/download-artifact)
from 6.0.0 to 7.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/download-artifact/releases">actions/download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>v7 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/download-artifact@v7 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v6 had preliminary
support for Node 24, however this action was by default still running on
Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li>Download Artifact Node24 support by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
<li>fix: update <code>@actions/artifact</code> to fix Node.js 24
punycode deprecation by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/451">actions/download-artifact#451</a></li>
<li>prepare release v7.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/452">actions/download-artifact#452</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/patrikpolyak"><code>@patrikpolyak</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li><a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0">https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="37930b1c2a"><code>37930b1</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/452">#452</a>
from actions/download-artifact-v7-release</li>
<li><a
href="72582b9e0a"><code>72582b9</code></a>
doc: update readme</li>
<li><a
href="0d2ec9d4cb"><code>0d2ec9d</code></a>
chore: release v7.0.0 for Node.js 24 support</li>
<li><a
href="fd7ae8fda6"><code>fd7ae8f</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/451">#451</a>
from actions/fix-storage-blob</li>
<li><a
href="d484700543"><code>d484700</code></a>
chore: restore minimatch.dep.yml license file</li>
<li><a
href="03a808050e"><code>03a8080</code></a>
chore: remove obsolete dependency license files</li>
<li><a
href="56fe6d904b"><code>56fe6d9</code></a>
chore: update <code>@actions/artifact</code> license file to 5.0.1</li>
<li><a
href="8e3ebc4ab4"><code>8e3ebc4</code></a>
chore: update package-lock.json with <code>@actions/artifact</code><a
href="https://github.com/5"><code>@5</code></a>.0.1</li>
<li><a
href="1e3c4b4d49"><code>1e3c4b4</code></a>
fix: update <code>@actions/artifact</code> to ^5.0.0 for Node.js 24
punycode fix</li>
<li><a
href="458627d354"><code>458627d</code></a>
chore: use local <code>@actions/artifact</code> package for Node.js 24
testing</li>
<li>Additional commits viewable in <a
href="018cc2cf5b...37930b1c2a">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 5.0.0 to 6.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>v6 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/upload-artifact@v6 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v5 had preliminary
support for Node.js 24, however this action was by default still running
on Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Upload Artifact Node 24 support by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/719">actions/upload-artifact#719</a></li>
<li>fix: update <code>@actions/artifact</code> for Node.js 24 punycode
deprecation by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/744">actions/upload-artifact#744</a></li>
<li>prepare release v6.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/745">actions/upload-artifact#745</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0">https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b7c566a772"><code>b7c566a</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/745">#745</a>
from actions/upload-artifact-v6-release</li>
<li><a
href="e516bc8500"><code>e516bc8</code></a>
docs: correct description of Node.js 24 support in README</li>
<li><a
href="ddc45ed9bc"><code>ddc45ed</code></a>
docs: update README to correct action name for Node.js 24 support</li>
<li><a
href="615b319bd2"><code>615b319</code></a>
chore: release v6.0.0 for Node.js 24 support</li>
<li><a
href="017748b48f"><code>017748b</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/744">#744</a>
from actions/fix-storage-blob</li>
<li><a
href="38d4c7997f"><code>38d4c79</code></a>
chore: rebuild dist</li>
<li><a
href="7d27270e0c"><code>7d27270</code></a>
chore: add missing license cache files for <code>@actions/core</code>,
<code>@actions/io</code>, and mi...</li>
<li><a
href="5f643d3c94"><code>5f643d3</code></a>
chore: update license files for <code>@actions/artifact</code><a
href="https://github.com/5"><code>@5</code></a>.0.1 dependencies</li>
<li><a
href="1df1684032"><code>1df1684</code></a>
chore: update package-lock.json with <code>@actions/artifact</code><a
href="https://github.com/5"><code>@5</code></a>.0.1</li>
<li><a
href="b5b1a91840"><code>b5b1a91</code></a>
fix: update <code>@actions/artifact</code> to ^5.0.0 for Node.js 24
punycode fix</li>
<li>Additional commits viewable in <a
href="330a01c490...b7c566a772">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
We rely on temporarily setting ZDOTDIR to our `zsh` resource directory
to implement automatic shell integration. Setting ZDOTDIR in a system
file like /etc/zshenv overrides our ZDOTDIR value, preventing our shell
integration from being loaded.
The only way to prevent /etc/zshenv from being run is via the --no-rcs
flag. (The --no-globalrcs only applies to system-level files _after_
/etc/zshenv is loaded.) Unfortunately, there doesn't appear to be a way
to run a "bootstrap" script (to reimplement the Zsh startup sequence
manually, similar to how our bash integration works) and then enter an
interactive shell session.
https://zsh.sourceforge.io/Doc/Release/Files.html
Given all of the above, document this as an unsupported configuration
for automatic shell integration and point affected users at our manual
shell integration option.
See: #8000
- Removes unnecessary marker constant from build.zig that existed
solely to signal build root status
- Uses filesystem check (@src().file access) instead of compile-time
declaration lookup to detect when ghostty is a dependency
- Same behavior with less indirection: file resolves from build root
only when ghostty is the main project
We rely on temporarily setting ZDOTDIR to our `zsh` resource directory
to implement automatic shell integration. Setting ZDOTDIR in a system
file like /etc/zshenv overrides our ZDOTDIR value, preventing our shell
integration from being loaded.
The only way to prevent /etc/zshenv from being run is via the --no-rcs
flag. (The --no-globalrcs only applies to system-level files _after_
/etc/zshenv is loaded.) Unfortunately, there doesn't appear to be a way
to run a "bootstrap" script (to reimplement the Zsh startup sequence
manually, similar to how our bash integration works) and then enter an
interactive shell session.
https://zsh.sourceforge.io/Doc/Release/Files.html
Given all of the above, document this as an unsupported configuration
for automatic shell integration and point affected users at our manual
shell integration option.
Fixes#9905
This fixes a major compatibility issue with the CSI S sequence: When our
top margin is at the top (row 0) without left/right margins, we should
be creating scrollback. Previously, we were only deleting.
I'm going to push this to 1.3 just given the feature of regressing any
other VT behaviors from this.
Also note the TODO, we implement the scrollback behavior by treating it
logically like an `index` (but scrolling above the bottom region). This
is expensive for large N but in most cases scroll up is small. Still, we
should optimize this one day.
**AI disclosure:** I used AI to investigate the xterm behavior, but got
it to return line numbers for me to verify the logic myself. I also used
it to identify missing test cases and fill those out initially, but I
modified them myself after. The actual core logic was all hand-written.
Fixes#9905
This fixes a major compatibility issues with the CSI S sequence:
When our top margin is at the top (row 0) without left/right
margins, we should be creating scrollback. Previously, we were
only deleting.
Fixes#9868 (shift+backspace part only)
You have to use `fish_key_reader -V` to verify this since it uses
different modes than Kitty's key reader. This matches the encoding to
Kitty.
Due to security issues, `sudo` implementations may not preserve
environment variables unless appended with `--preserve-env=list`.
Signed-off-by: definfo <hjsdbb1@gmail.com>
Tried my hand at #8432
Currently lacking tests and some sort of visual indicator that the
surface is locked.
Also, not entirely sure if I needed to touch `application.zig`.
Found what needed changing with help from Copilot (and added Docs w/
Copilot), but wrote most of the code myself.
(Claude-generated content below)
## Fix bash shell integration use-after-free bug
### Problem
After updating to the latest `main` branch, Ghostty fails to launch with
bash shell integration enabled on Linux (Ubuntu 25.10). The terminal
window briefly appears and then closes with an error like:
```
/bin/sh: 1: ically: not found
```
The error message varies but typically contains fragments of words (like
"ically" from "automatically").
Related issue was possibly [reported in the comments of PR
#9881](https://github.com/ghostty-org/ghostty/pull/9881) shortly after
it was merged.
### Root Cause Analysis
The bug is a **use-after-free** in the bash shell integration setup.
In `setupBash()`, the `ShellCommandBuilder` is initialized with a
`stackFallback` allocator:
```zig
var stack_fallback = std.heap.stackFallback(4096, alloc);
var cmd = internal_os.shell.ShellCommandBuilder.init(stack_fallback.get());
```
When `cmd.toOwnedSlice()` is called, it returns a slice allocated from
the `stackFallback` allocator. Since the command string (e.g., "bash
--posix") easily fits within 4096 bytes, this memory is allocated on the
**stack**.
When `setupBash()` returns, the stack frame is deallocated, but the
returned `.shell` command still points to this now-invalid stack memory.
When the command is later used in `execCommand()` to build the `/bin/sh
-c "..."` invocation, it reads garbage data.
The garbage data often contains "ically" because it picks up fragments
from nearby memory—likely from the string literal "automatically" in the
comment on line 280:
```zig
// being manually sourced or automatically injected (from here).
```
This is a [known footgun with Zig's stackFallback
allocator](https://github.com/ziglang/zig/issues/16344) - memory
allocated from the stack portion becomes invalid once the stack frame is
exited.
### Bisect / Blame
The bug was introduced **~5 hours ago** (earlier today):
| Field | Value |
|-------|-------|
| **Commit** | `04fecd7c07fccad423ab1c33324a1997e142b6e2` |
| **PR** | [#9881](https://github.com/ghostty-org/ghostty/pull/9881)
(os/shell: introduce ShellCommandBuilder) |
| **Date** | Fri Dec 12 08:59:44 2025 -0500 |
| **Merged as** | `dd06d8a13` |
The previous implementation in commit `c0deaaba4` used
`std.mem.joinZ(alloc, " ", args.items)` which correctly allocated the
result using the arena allocator. The refactor to use
`ShellCommandBuilder` inadvertently changed the allocation strategy to
use stack memory for the returned value.
Other uses of `stackFallback` in the codebase are safe because they
either:
- Copy data to another allocator before the function returns (e.g.,
`env.put()` in `setupXdgDataDirs`)
- Use the data only within the function scope (e.g.,
`stream_handler.zig`)
### The Fix
Copy the command string to the arena allocator before returning:
```zig
const cmd_str = try cmd.toOwnedSlice();
return .{ .shell = try alloc.dupeZ(u8, cmd_str) };
```
This ensures the memory remains valid for the lifetime needed by the
caller, matching the behavior of other `stackFallback` usage patterns in
the codebase.
### Testing
- Verified the fix on Ubuntu 25.10 with Ghostty built from source
- Before fix: Terminal crashes immediately with "ically: not found"
error
- After fix: Terminal launches successfully with shell integration
working
```
info(io_exec): shell integration automatically injected shell=.bash
info(io_exec): started subcommand path=/bin/sh pid=...
info(surface): surface closed addr=... # Normal close, no "abnormal process exit"
```
### Platform
Primarily affects Linux where `.shell` commands are wrapped in `/bin/sh
-c "..."`. macOS uses a different execution path via `login(1)`.
The ShellCommandBuilder uses a stackFallback allocator, which means
toOwnedSlice() may return memory allocated on the stack. When setupBash()
returns, this stack memory becomes invalid, causing a use-after-free.
This manifested as garbage data in the shell command string, often
appearing as errors like "/bin/sh: 1: ically: not found" (where "ically"
was part of nearby memory, likely from the comment "automatically").
The fix copies the command string to the arena allocator before returning,
ensuring the memory remains valid for the lifetime of the command.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This builder is an efficient way to construct space-separated shell
command strings.
We use it in setupBash to avoid using an intermediate array of arguments
to construct our bash command line.
I think at this point all moderators and helpers can agree with me in
that LLM-generated responses are a blight upon this Earth.
Also probably worth putting in a clause against AI-generated assets
This builder is an efficient way to construct space-separated shell
command strings.
We use it in setupBash to avoid using an intermediate array of arguments
to construct our bash command line.
I think at this point all moderators and helpers can agree with me in
that LLM-generated responses are a blight upon this Earth.
Also probably worth putting in a clause against AI-generated assets
(cf. the Commit Goods situation)
This adds the ability to change a _tab_ title. The previous
functionality was tied to a specific _surface_. A tab title will stick
to the current tab regardless of active splits and so on.
This follows the nomenclature that macOS terminal app does which is
"title vs terminal title" (although we explicitly use "tab" in various
places, I may remove that in the future).
**This is macOS only. GTK is tracked here: #9880**. I did macOS only
because thats the machine I'm on. It'll be trivial to add this to GTK,
too.
## Demo
https://github.com/user-attachments/assets/d9446785-d919-4212-8553-db50c56c8c2f
(The option is also in the main menu, the context menu, and the command
palette)
## AI Disclosure
This PR was done fully with Amp, I didn't write a single line of code at
the time of writing this PR description. I reviewed everything though
and fully understand it all. Its a mimic more or less of the prompt
surface title work (although we did unify some stuff like the apprt
action).
Prior to #7044, on macOS, our shell-integrated command line would be
executed under `exec -l`, which causes bash to be started as a login
shell. This matches the macOS platform norms.
The change to direct command execution meant that we'd skip that path,
and bash would start as a normal interactive (non-login) shell on macOS.
We fixed this in #7253 by adding `--login` to the `bash` direct command
on macOS.
This avoided some of the overhead of starting an extra process just to
get a login shell, but it unfortunately doesn't quite match the bash
environment we get when shell integration isn't enabled (namely, `$0`
doesn't get the login-shell-identifying `-` prefix).
Instead, this change implements the approach proposed in #7254, which
switches the bash shell integration path to use a `.shell` command,
giving us the same execution environment as the non-shell-integrated
command.
Prior to #7044, on macOS, our shell-integrated command line would be
executed under exec -l, which causes bash to be started as a login
shell. This matches the macOS platform norms.
The change to direct command execution meant that we'd skip that path,
and bash would start as a normal interactive (non-login) shell on macOS.
We fixed this in #7253 by adding `--login` to the `bash` direct command
on macOS.
This avoided some of the overhead of starting an extra process just to
get a login shell, but it unfortunately doesn't quite match the bash
environment we get when shell integration isn't enabled (namely, $0
doesn't get the login-shell-identifying "-" prefix).
Instead, this change implements the approach proposed in #7254, which
switches the bash shell integration path to use a .shell command, giving
us the same execution environment as the non-shell-integrated command.
Fixed: When reflowing content with many graphemes, the code incorrectly
increased hyperlink_bytes capacity
instead of grapheme_bytes, causing GraphemeMapOutOfMemory errors.
Added: An unit test for this specific issue.
This PR was written primarily by Opus.
Closes: #9863
When reflowing content with many graphemes, the code incorrectly increased hyperlink_bytes capacity
instead of grapheme_bytes, causing GraphemeMapOutOfMemory errors.
Description:
Context menu works on tabs with titlebar-style=tabs on MacOS Tahoe 26
Closes#9817
Demo:
https://github.com/user-attachments/assets/60eaae6e-a3ff-41eb-8c86-ba700490d6e2
Note:
- Tried first a passthrough-views approach, but AppKit’s internal
toolbar subviews continued intercepting right-clicks.
- Runtime subclassing proposed by Claude also worked but was rejected as
too fragile.
- Final solution routes secondary-click events at the window level using
sendEvent(_:), forwarding them to the tab bar only when the click is
visually within its bounds.
AI Disclosure:
AI (Claude Code and Codex) assisted with early explorations, but final
implementation was developed manually after evaluating and discarding
the unsafe subclassing approach proposed by Claude.
With this PR, the macos scrollbar always uses the overlay style. If the
OS preferred style is `.legacy`, we flash the scroller when the mouse is
moved over it, such that users can still click and drag without relying
on scroll wheels or gestures.
Implements #9610.
There are a few lines of code that could technically be removed after
this change as they're only needed to make surfaces work correctly with
the legacy scrollbar, but I decided to leave them in since they do no
harm (see code comments). This ensures correct behavior if, for whatever
reason, some corner case brings back the legacy scrollbar, or if someone
decides to experiment with scrollbar styles in the future.
### Background
After #9344, the Ghostty theme won't change after switching systems',
and reverting #9344 will bring back the issue it fixed.
The reason these two issues are related is because the scheme change is
based on changes of `effectiveAppearance`, which is also affected by
setting the window's `appearance` or changing
`NSAppearance.currentDrawing()`.
### Changes
Instead of observing `effectiveAppearance`, we now explicitly update the
color scheme of surfaces, so that we can control when it happens to
avoid callback loops and redundant updates.
### Regression Tests
- [x] #8282
- [x] Reloading with `window-theme = light` should update Ghostty with
the default dark theme with a dark window theme (break before
[#83104ff](83104ff27a))
- [x] `window-theme = light \n macos-titlebar-style = native` should
update Ghostty with the default dark theme with a light window theme
- [x] Reloading from the default config to `theme=light:3024
Day,dark:3024 Night \n window-theme = light`, should update Ghostty with
the theme `3024 Day` with a light window theme (break on
[#d39cc6d](d39cc6d478))
- [x] Using `theme=light:3024 Day,dark:3024 Night`; Switching the
system's appearance should change Ghostty's appearance (break on
[#d39cc6d](d39cc6d478))
- [x] Reloading from `theme=light:3024 Day,dark:3024 Night` with a light
window theme to the default config, should update Ghostty with the
default dark theme with a dark window theme
- [x] Reloading from the default config to `theme=light:3024
Day,dark:3024 Night \n window-theme=dark`, should update Ghostty with
the theme `3024 Night` with a dark window theme
- [x] Reloading from `theme=light:3024 Day,dark:3024 Night \n
window-theme=dark` to `theme=light:3024 Day,dark:3024 Night` with light
system appearance, should update Ghostty from dark to light
- [x] Reload with quick terminal open
When ghostty is used as a Zig dependency, detect this by comparing the
build root with ghostty's source directory. Skip git detection entirely
and use the version from build.zig.zon.
This fixes build failures when downstream projects have git tags that
don't match ghostty's version format. Previously, ghostty would read the
downstream project's git tags and panic at Config.zig:246 with:
\`\`\`
tagged releases must be in vX.Y.Z format matching build.zig
\`\`\`
**Reproduction:**
1. Create a project that uses ghostty as a Zig dependency
2. Tag the project with a version like \`v0.2.0\`
3. Run \`zig build\` → panic
**Fix:**
Use \`@src().file\` to get ghostty's source directory and compare it
with \`b.build_root\`. When they differ, ghostty is a dependency and we
skip git detection.
Thread:
https://ampcode.com/threads/T-197e6c33-b8f8-4b23-8fc8-7f6b6edd9f35
Detect if ghostty is being built as a dependency by comparing the build
root with ghostty's source directory. When used as a dependency, skip
git detection entirely and use the version from build.zig.zon.
This fixes build failures when downstream projects have git tags that
don't match ghostty's version format. Previously, ghostty would read
the downstream project's git tags and panic at Config.zig:246 with
"tagged releases must be in vX.Y.Z format matching build.zig".
Related to #1935
This adds a new structure `terminal.tmux.Viewer` which continues
building on all the prior tmux control mode work to add a full
bidirectional reconciliation loop to discover and sync terminal states
from tmux to Ghostty and vice versa. **This is the core, cross-platform
business logic that will power the GUIs, later.**
Our prior work were protocol building blocks, and this PR is an actual
functional piece of work. You can now start Ghostty, run `tmux -CC
attach`, and we _will_ be creating full blown terminals internal that
capture the content and mirror the state exactly (barring inevitable
bugs in something this complex). But, we don't yet show them visually.
:) And we don't yet send inputs to it (it's a viewer only, for now).
**This sucked.** The control mode protocol is difficult, to put it
mildly, for a variety of reasons. Correctness of this is going to be
hard. Therefore, I focused really hard on this design to make it **fully
unit test friendly.** We're able to simulate full tmux sessions and runt
through our state machine and assert various states. I think this will
be critical to correctness as we eventually collect real world data.
> [!WARNING]
>
> This does actually have user-impacting changes! When you run `tmux -CC
attach` we will now run our full control mode client. This could result
in bugs or crashes or other problems. This only activates if you have a
real tmux session, though, so it should be avoidable by most users.
Since we don't actually take our state and send it to the GUI or
anything, this should be pretty safe.
**AI disclosure:** I used AI for a lot of the protocol reverse
engineering and documentation to figure out how it all works. I designed
the architecture myself and implemented most of it manually.
Hello! I did read `CONTRIBUTING.md`. I understand that the preference is
to receive pull requests for existing open issues and acknowledge that
I'm skipping a step wrt the preferred workflow since there aren't any
open issues yet for #8419. Living dangerously and hoping for the best
here. I hope you'll see that I'm acting in good faith 🤞.
In my attempts to debug why `quick-terminal-size` config wasn't working
reliably for me, I discovered that the
[`ghostty_config_quick_terminal_size_s`](08c9661683/include/ghostty.h (L460-L480))
struct used to initialize `QuickTerminalSize` in Swift-land, more often
than not, didn't match what I had in my config file (e.g.
`quick-terminal-size = 75%, 50%`)
<details>
08c9661683/include/ghostty.h (L460-L480)08c9661683/macos/Sources/Ghostty/Ghostty.Config.swift (L507-L510)
</details>
Almost all mismatches seemed to be downstream of `tag` (for `primary`
and/or `secondary` `Size`) being an unexpected value for the
`ghostty_quick_terminal_size_tag_e` enum type, which led runtime
execution to fall into the `default` branch in this `switch` control
flow.
08c9661683/macos/Sources/Features/QuickTerminal/QuickTerminalSize.swift (L27-L38)
Looking at `src/config/CApi.zig`, `src/config/c_get.zig`,
`src/config/Config.zig`, and [Zig documentation for `extern
enum`](https://ziglang.org/documentation/master/#extern-enum), led me to
conclude that the crux of the issue is lack of guaranteed C ABI
compatibility for `Tag`
08c9661683/src/config/Config.zig (L7986-L7990)08c9661683/include/ghostty.h (L461-L465)
Further research revealed that based on C language spec alone, one
cannot assume a fixed width for `enum` and that the behaviour is
compiler dependant. But given how the Zig documentation suggests using
`enum(c_int)` for C-ABI-compatible enums and extern enums + comments
elsewhere in `Config.zig` suggesting that `enum(c_int)` was chosen for
extern compatibility,
08c9661683/src/config/Config.zig (L4877)
I think that this fix (changing `enum(u8)` to `enum(c_int)`) is likely
the most straightforward and appropriate one.
Despite the existence of #6937 it seems that there are still quite a few
people who aren't sure of the norms surrounding how to use Ghostty's
discussion spaces, so I chose to spell it all out here.
Regarding AI policy I also added a section heavily discouraging AI
responses within PRs and contributing to platforms one can't test on. I
don't understand what is wrong with vibecoders on macOS who just think
that the GTK side will somehow Just Work, despite us being pretty much
the only major piece of software using zig-gobject that I know of.
I am truly not sure why the tests never caught this, but I just fell for
the oldest trick in the book
Fixes a crash on GTK when a file is dragged then dropped into the
terminal - should not impact 1.2 as this is a casualty of the Zig 0.15
port
This fixes an issue I have on both macOS and Linux (ARM and x86_64)
where stack traces are broken for inlined functions. They don't point to
the proper location in the source code, making debugging difficult.
Release builds use the same previous function.
cc @qwerasd205
This fixes an issue I have on both macOS and Linux (ARM and x86_64)
where stack traces are broken for inlined functions. They don't point to
the proper location in the source code, making debugging difficult.
Release builds use the same previous function.
### Background
After #9344, the Ghostty theme won't change after switching systems', and reverting #9344 will bring back the issue it fixed.
The reason these two issues are related is because the scheme change is based on changes of `effectiveAppearance`, which is also affected by setting the window's `appearance` or changing `NSAppearance.currentDrawing()`.
### Changes
Instead of observing `effectiveAppearance`, we now explicitly update the color scheme of surfaces, so that we can control when it happens to avoid callback loops and redundant updates.
### Regression Tests
- [x] #8282
- [x] Reloading with `window-theme = light` should update Ghostty with the default dark theme with a dark window theme (break before [#83104ff](83104ff27a))
- [x] `window-theme = light \n macos-titlebar-style = native` should update Ghostty with the default dark theme with a light window theme
- [x] Reloading from the default config to `theme=light:3024 Day,dark:3024 Night \n window-theme = light`, should update Ghostty with the theme `3024 Day` with a light window theme (break on [#d39cc6d](d39cc6d478))
- [x] Using `theme=light:3024 Day,dark:3024 Night`; Switching the system's appearance should change Ghostty's appearance (break on [#d39cc6d](d39cc6d478))
- [x] Reloading from `theme=light:3024 Day,dark:3024 Night` with a light window theme to the default config, should update Ghostty with the default dark theme with a dark window theme
- [x] Reloading from the default config to `theme=light:3024 Day,dark:3024 Night \n window-theme=dark`, should update Ghostty with the theme `3024 Night` with a dark window theme
- [x] Reloading from `theme=light:3024 Day,dark:3024 Night \n window-theme=dark` to `theme=light:3024 Day,dark:3024 Night` with light system appearance, should update Ghostty from dark to light
- [x] Reload with quick terminal open
# Conflicts:
# macos/Sources/Features/Terminal/BaseTerminalController.swift
This should save on CI quite a bit. This will cancel our GHA runs when
you push to the same ref, except for `main`, where I want to make sure
every commit is tested.
This should save on CI quite a bit. This will cancel our GHA runs when
you push to the same ref, except for `main`, where I want to make sure
every commit is tested.
This was found by LLM hunting! We were not holding the lock properly
during these operations. There aren't any known cases where we can
directly attribute these races to issues but we did find at least one
consistent crash for a user when `linkAtPos` wasn't properly locked (in
another PR).
This continues those fixes.
https://ampcode.com/threads/T-6fc49f25-7f2f-4039-adb4-f86aaeced6d5
This was found by LLM hunting! We were not holding the lock properly
during these operations. There aren't any known cases where we can
directly attribute these races to issues but we did find at least one
consistent crash for a user when `linkAtPos` wasn't properly locked (in
another PR).
This continues those fixes.
From #9812
I'm not sure if this is the root cause of the crash in #9812 but the
LLM-discovered issue that we are not holding a lock here appears to be a
real issue. I manually traced the code paths and thought about this and
looked where we call `mouseRefreshLinks` in other places and this
appears to be a real bug.
From #9812
I'm not sure if this is the root cause of the crash in #9812 but the
LLM-discovered issue that we are not holding a lock here appears to be a
real issue. I manually traced the code paths and thought about this and
looked where we call `mouseRefreshLinks` in other places and this
appears to be a real bug.
Continuing just build foundation for #1935. There are no user-facing
changes here.
- This adds more command parsing for control mode output. I _believe_ we
now parse enough to at least render everything initially for most tmux
sessions. At least, from the VT parsing side (we haven't done any GUI
work whatsoever to react to this).
- This also adds a new layout string parser, e.g.
`159x48,0,0{79x48,0,0,79x48,80,0}` into a useful struct. We'll need this
to convert our visible layouts into actual GUI components, eventually.
- This adds a new output format parser for commands such as
`list-windows`. Control mode sends output framed in `%begin/%end`
blocks, but the output is in an arbitrary format depending on the input
command and unrelated to control mode itself. The output format parser
will let us parse this output.
I think this is a good place to stop and PR. I think next up I might try
hooking up a state machine to `src/termio/stream_handler.zig` to start
actually working with this. Before that, it may behoove me to change
`stream_readonly` to support non-readonly operations via callback so I
can do DCS and maybe unit test this whole thing too.... we'll see where
the wind blows.
**AI disclosure:** AI wrote many tests and did a lot of implementation
here. I reviewed everything manually though and understand it all
completely.
If the BEL character is received too frequently, the GUI thread can be
starved and Ghostty will lock up and eventually crash. This PR limits
BEL handling to 1 per 100ms.
Fixes#9800.
If the BEL character is received too frequently, the GUI thread can be
starved and Ghostty will lock up and eventually crash. This PR limits
BEL handling to 1 per 100ms.
Fixes#9800.
## Problem
When ghostty is used as a dependency (e.g., in another Zig project), the
build can panic on Linux with:
\`\`\`
GhosttyDist.zig:173:34: 0x1768046 in exists (build.zig)
if (std.fs.accessAbsolute(b.pathFromRoot(self.dist), .{})) {
\`\`\`
This happens because \`b.pathFromRoot()\` can return a relative path
(e.g., when the build root is \`.\`), but \`std.fs.accessAbsolute()\`
asserts the path must be absolute.
## Solution
Replace \`std.fs.accessAbsolute(b.pathFromRoot(...))\` with
\`b.build_root.handle.access(...)\`, which uses the build root's
directory handle directly and works correctly with relative paths.
Context:
https://ampcode.com/threads/T-7511e7e4-4b4a-4f11-9c3c-817aaa519b84
The main thing to emphasize is that end users should never source
.zshenv directly; it's only meant to be used as part of our shell
injection environment.
At the moment, there's no way to guard against accidentally use, but we
can consider making e.g. GHOSTTY_SHELL_FEATURES always defined in this
environment to that it can be used to differentiate the cases.
In practice, it's unlikely that people actually source this .zshenv
script directly, so hopefully this additional documentation clarifies
things well enough.
The ghostty-integration script can be manually sourced, and it uses the
Zsh 5.1+ features, so that's a better place to guard against older Zsh
versions.
This also keeps the .zshenv script focused on just bootstrapping our
automatic shell integration.
I also changed the version check to a slightly more idiomatic pattern.
The ghostty-integration script can be manually sourced, and it uses the
Zsh 5.1+ features, so that's a better place to guard against older Zsh
versions.
This also keeps the .zshenv script focused on just bootstrapping our
automatic shell integration.
I also changed the version check to a slightly more idiomatic pattern.
Replace std.fs.accessAbsolute(b.pathFromRoot(...)) with
b.build_root.handle.access(...) since pathFromRoot can return
relative paths, but accessAbsolute asserts the path is absolute.
The main thing to emphasize is that end users should never source
.zshenv directly; it's only meant to be used as part of our shell
injection environment.
At the moment, there's no way to guard against accidentally use, but we
can consider making e.g. GHOSTTY_SHELL_FEATURES always defined in this
environment to that it can be used to differentiate the cases.
In practice, it's unlikely that people actually source this .zshenv
script directly, so hopefully this additional documentation clarifies
things well enough.
I think it makes sense that if there was already text from the previous
search that it should match based on that, this doesnt remove that it
highlights everything so you can press backspace to delete and start
from a empty search
Fixes#189
This adds the UI for search to GTK. There is still polish to be done in
follow-ups but this makes search work well with GTK to start!
**AI disclosure:** Believe it or not, almost this entire PR was
AI-written. Amp did an excellent job looking at our existing codebase,
comparing it to the macOS codebase, writing blueprint files, etc. I
reviewed everything written, modified some basics, and verified it
manually and under Valgrind.
## Demo
<img width="1654" height="1234" alt="CleanShot 2025-11-29 at 20 52
11@2x"
src="https://github.com/user-attachments/assets/0eb38367-398f-4165-9838-7a35465857bc"
/>
## Future Improvements
- When dragging the overlay, we should change the cursor and show drop
targets
- There's probably some small stylistic tweaks we can make to this
- I'm not sure the CSS is right for both light and dark modes so we may
need to tweak that
This fixes memory corruption where future matches on a fully dirty row
would write highlights out of bounds. It was easy to reproduce in debug
by searching for `$` in `ghostty +boo`
When columns shrink during resize-without-reflow, page.size.cols is
updated but page.capacity.cols retains the old larger value. When
adjustCapacity later runs (e.g., to expand style/grapheme storage),
it was creating a new page using page.capacity which has the stale
column count, causing size.cols to revert to the old value.
This caused a crash in render.zig where an assertion checks that
page.size.cols matches PageList.cols.
Fix by explicitly copying page.size.cols to the new page after
creation, matching how size.rows is already handled.
Amp-Thread-ID: https://ampcode.com/threads/T-976bc49a-7bfd-40bd-bbbb-38f66fc925ff
Co-authored-by: Amp <amp@ampcode.com>
This fixes memory corruption where future matches on a fully dirty row
would write highlights out of bounds. It was easy to reproduce in debug
by searching for `$` in `ghostty +boo`
not an accepted issue
https://github.com/ghostty-org/ghostty/discussions/9745 but it seemed
simple enough to add, pretty much copies how the previewer sorts based
on color
This allows --color argument to work when using --plain to sort the list
returned
Fixes#2660
Rather than calculate our window frame size based on various chrome
calculations, we now utilize SwiftUI layouts and view intrinsic content
sizes with `setContentSize` to setup our content size ignoring all our
other widgets.
I'm sure there's some edge cases I'm missing here but this should be a
whole lot more reliable on the whole.
Fixes#2660
Rather than calculate our window frame size based on various chrome
calculations, we now utilize SwiftUI layouts and view intrinsic content
sizes with `setContentSize` to setup our content size ignoring all our
other widgets.
I'm sure there's some edge cases I'm missing here but this should be a
whole lot more reliable on the whole.
This fixes regression of #5690, which kind of comes from #9576. 05b42919d5 (before #9576) has weird behaviours too, restored windows are not properly focused. With this pr, we only order `selectedWindow` front so we won't mess up with its selection state and the order of the tab group.
This means that the pin we're using to track our position in the
PageList was part of a node that got reused/recycled at some point. We
can't make any meaningful guarantees about the state of the PageList.
This only happens with scrollback pruning so we can treat it as a
complete search.
This means that the pin we're using to track our position in the
PageList was part of a node that got reused/recycled at some point. We
can't make any meaningful guarantees about the state of the PageList.
This only happens with scrollback pruning so we can treat it as a
complete search.
Swift conveniently converts strings to UTF-8 encoded cstrings when
passing them to external functions, however our libghostty functions
also take a length and we were using String.count for that, which
returns the number of _characters_ not the byte length, which caused
searches with multi-byte characters to get truncated.
I went ahead and changed _all_ invocations that pass a string length to
use the utf-8 byte length even if the string is comptime-known and all
ASCII, just so that it's proper and if someone copies one of the calls
in the future for user-inputted data they don't reproduce this bug.
ref:
https://developer.apple.com/documentation/swift/string/counthttps://developer.apple.com/documentation/swift/stringprotocol/lengthofbytes(using:)
Swift conveniently converts strings to UTF-8 encoded cstrings when
passing them to external functions, however our libghostty functions
also take a length and we were using String.count for that, which
returns the number of _characters_ not the byte length, which caused
searches with multi-byte characters to get truncated.
I went ahead and changed _all_ invocations that pass a string length to
use the utf-8 byte length even if the string is comptime-known and all
ASCII, just so that it's proper and if someone copies one of the calls
in the future for user-inputted data they don't reproduce this bug.
ref:
https://developer.apple.com/documentation/swift/string/counthttps://developer.apple.com/documentation/swift/stringprotocol/lengthofbytes(using:)
Move the search result counter inside the search text field using an
overlay, preventing layout shift when results appear.
**Before:** The counter appeared as a separate element in the HStack,
causing the text field to shift when results loaded.
**After:** The counter is overlaid inside the text field on the right
side with reserved padding, eliminating layout shift.
---
**AI Disclosure: The was entirely authored with Claude Code,
specifically with Claude Opus 4.5.**
Move the search result counter (e.g. "1/30") inside the search text
field using an overlay, preventing layout shift when results appear.
This PR was authored with Claude Code.
#189 for macOS
This adds a search GUI for macOS, including macOS-standard menu bar
items, and keybindings that match other native macOS applications such
as Terminal app, Safari, and others. This introduces a new keybinding
action `start_search` to start a blank search for GUIs.
This PR also resolves a number of minor issues found in the search
subsystem and renderer related to search from prior PRs. This should
result in overall improved search stability. **Please note there are
still known issues (bottom of this PR).**
> [!WARNING]
>
> **A note on stability:** I know a lot of people are eager to have this
feature, and I'm excited
> for this feature to soon be available in tip releases. But note that
new features like this are
> always filled with performance issues, bugs, crashes, etc. That's the
point of tip releases:
> to find and address these before wider availability. We will do our
best to respond rapidly to
> major issues found in tip, but don't expect perfect functionality
immediately!
## Demo
https://github.com/user-attachments/assets/3b81752e-d7e5-4875-9864-92497333b23e
> [!NOTE]
>
> You can drag the search window to any of the four corners if its
getting in the way of reading the terminal.
## Known Issues
TODO prior to this PR merging:
- [x] Single-byte search terms cause a crash since our sliding window
can't handle it. This PR temporarily requires search terms with length
2+ before starting a search to avoid it. 😄
- [x] The way `ScreenSearch` prunes history is fundamental unsafe. With
a rapidly growing screen that could reach history limits and an active
search at the same time, Ghostty is almost guaranteed to crash
currently. The workaround is to not search actively scrolling screens
(new data) for now.
- ScreenSearch has to restart on resize. We don't have any good way to
handle reflow since our search results are unpinned. We can look into
this later but this fixes correctness.
- PageList now tracks serial number by node that monotonically increases
on any alloc or reuse. Our screen search uses this to prune invalid
history results.
When pasting text in GTK, the current version properly prioritizes
text/plain;charset=utf-8 when the content is offered by another
application, but when pasting from ghostty to itself the mime type
selection algorithm prefers the offer order and matches `text/plain`,
which then converts non-ASCII UTF-8 into a bunch of escaped hex
characters (e.g. 日本語 becomes \E6\97\A5\E6\9C\AC\E8\AA\9E)
This is being discussed on the GTK side[1], but until everyone gets an
updated GTK it cannot hurt to offer the UTF-8 variant first (and one of
the GTK dev claims it actually is a bug not to do it, but the wayland
spec is not clear about it, so other clients could behave similarly)
Link: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/9189 [1]
Fixes#9682
When pasting text in GTK, the current version properly prioritizes
text/plain;charset=utf-8 when the content is offered by another
application, but when pasting from ghostty to itself the mime type
selection algorithm prefers the offer order and matches `text/plain`,
which then converts non-ASCII UTF-8 into a bunch of escaped hex
characters (e.g. 日本語 becomes \E6\97\A5\E6\9C\AC\E8\AA\9E)
This is being discussed on the GTK side[1], but until everyone gets an
updated GTK it cannot hurt to offer the UTF-8 variant first (and one of
the GTK dev claims it actually is a bug not to do it, but the wayland
spec is not clear about it, so other clients could behave similarly)
Link: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/9189 [1]
Fixes#9682
Continuing #189
This adds the `navigate_search:previous` and `next` key bindings which
allow search matches to be navigated. The currently selected search
match is highlighted using a new `search-selected-foreground/background`
configuration with a reasonable default.
As search results are navigated, the viewport moves to keep them
visible.
## Demo
https://github.com/user-attachments/assets/facc9f3e-e327-4c65-b5f7-0279480ac357
This causes linker issues for some libghostty users. I don't know why we
never saw these issues with Ghostty release builds, but generally
speaking I think its fine to do this for 3rd party code unless we've
witnessed an issue. And these deps have been stable for a long, long
time.
It's clear from the surrounding code that the RGB values returned from
`cell_text_vertex` are expected to be linearized. This was not the case
for the cursor cell, resulting in the cursor text being too bright when
using `cursor-text = cell-background`.
Fixes #8353/#9138.
xref #8960, which would also fixe this issue, but I don't know where
that's heading
This causes linker issues for some libghostty users. I don't know why we
never saw these issues with Ghostty release builds, but generally
speaking I think its fine to do this for 3rd party code unless we've
witnessed an issue. And these deps have been stable for a long, long
time.
The march towards #189 continues.
This hooks up the search thread to the main surface and all the state
necessary for the renderer to show search results in the viewport! This
also adds a `search` binding which takes a query to start/stop a search.
**This still doesn't add any search GUI,** which will come later, the
internals must happen first.
A non-blank binding will start or change the search term. An empty
binding will stop search:
```
keybind = cmd+f=search:Hello
keybind = shift+cmd+f=search:
```
> [!NOTE]
>
> Obviously, search will eventually have a GUI. The point of this PR is
primarily to connect all the various internal systems more than
anything. GUI will come soon.
## Demo
https://github.com/user-attachments/assets/06af5a3b-280e-4804-b506-419b92a95f99
## Major Changes
The only major changes required as part of this is the introduction of
what I'm calling the terminal "highlight" system. This is a generic
system for highlighting portions of the terminal contents. These will
ultimately underpin what we currently call "selections" (selecting text
with your mouse/keyboard) but that is far too large a change to make in
one PR.
Therefore, this PR introduces highlights and the only consumer is the
entire search subsystem.
## Limitations
Still plenty of limitations we need to keep marching towards resolving:
- ~~Search matches are styled the same as selections. They should be
different.~~
- There is no way to iterate search matches, yet.
- There is no GUI for inputting a search query or viewing total matches,
yet.
- ~~I think searches are case-sensitive currently and they should
probably not be.~~ Done, for ASCII.
But hey, it's something!
These now properly match the FreeType API- compared directly in the unit
tests against the values provided by the FreeType header itself.
This was ridiculously wrong before, like... wow.
FcLangSetHasLang returns FcLangResult enum values:
- FcLangEqual (0): Exact match
- FcLangDifferentTerritory (1): Same language, different territory
- FcLangDifferentLang (2): Different language
The previous comparison to FcTrue (1) caused:
- Exact matches (0) to incorrectly return false
- Partial matches (1) to incorrectly return true
This fix changes the comparison to FcLangEqual (0) so hasLang()
correctly returns true only for exact language matches.
Fixes emoji font detection which relies on checking for 'und-zsye'
language tag support.
## Summary
Adds a new environment variable `GHOSTTY_QUICK_TERMINAL=1` that is set
when a terminal surface is launched as a quick terminal. This allows
shell configurations to conditionally adjust behavior based on whether
the terminal is a quick terminal.
Closes#3985
<img width="1430" height="228" alt="CleanShot 2025-11-23 at 12 39 27
png"
src="https://github.com/user-attachments/assets/1085c19b-9d58-4603-a03d-662bfde47095"
/>
## Motivation
Quick terminals are designed as ephemeral, singleton windows for quick
commands. Many users (especially tmux users) have shell configurations
that automatically attach to or start tmux sessions on terminal launch.
This automatic behavior conflicts with the quick terminal use case.
Example from the issue:
```zsh
# Users want to skip this in quick terminals
if [[ -z "$TMUX" ]]; then
tmux attach || tmux new
fi
With this PR, users can now detect quick terminals and adjust their shell behavior:
if [[ -z "$TMUX" ]] && [[ -z "$GHOSTTY_QUICK_TERMINAL" ]]; then
tmux attach || tmux new
fi
```
## Implementation
- macOS: Added GHOSTTY_QUICK_TERMINAL=1 to SurfaceConfiguration
environment variables in QuickTerminalController.swift:345-351
- Linux/GTK: Added environment variable check in
src/apprt/gtk/class/surface.zig:1469-1472 within defaultTermioEnv()
The environment variable is automatically inherited by split surfaces.
## Note on Scripting API
I'm aware that @rhodes-b mentioned this functionality was planned for
the scripting API (issue #3985). However, I believe this simpler
environment variable approach provides immediate value for tmux users
and shell configuration use cases, while the scripting API can later
provide more comprehensive surface introspection capabilities.
## AI Assistance Disclosure
This PR was written with Claude Code assistance. The Linux/GTK
implementation was fully AI-generated. I implemented the mac version
myself after Claude Code helped me understand the codebase architecture
and locate the appropriate files.
This PR builds on https://github.com/ghostty-org/ghostty/pull/9678 ~so
the diff from there is included here (it's not possible to stack PRs
unless it's a PR against my own fork)--review that one first!~
This PR fixes an issue with the VS15/VS16 handling in terminal `print`,
where before we didn't check for valid sequences if the base code point
is an emoji. This meant that a VS15 after an emoji that isn't part of a
valid sequence would narrow the cell, despite that the emoji doesn't
have a text presentation.
This PR also uses a single `is_emoji_vs_base` instead of the
`is_emoji_vs_emoji` and `is_emoji_vs_text`. See the [uucode
comment](215ff09730/src/config.zig (L239-L254))
for why I'm recommending this.
AI was used in some of the uucode changes in
https://github.com/ghostty-org/ghostty/pull/9678 (Amp--primarily for
tests), but everything was carefully vetted and much of it done by hand.
This PR was made without AI.
This updates uucode to the latest version, with the following changes:
b309dfb4e2...31655fba3c
For this PR, the new `uucode` version has two changes that contribute to
the diff:
* The `grapheme_break` now includes `emoji_modifier` and
`emoji_modifier_base`, so we don't need to grab those from
`is_emoji_modifier` and `is_emoji_modifier_base`.
* The `wcwidth` calculation has been split into `wcwidth_standalone`
(width of a code point when it's the only code point in a grapheme) and
`wcwidth_zero_in_grapheme` (whether the code point is _not_ contributing
to the width of a multi-code-point grapheme). To keep the current
Ghostty behavior for this PR, this sets `width` to 0 if
`wcwidth_zero_in_grapheme`, but with the exception of
`is_emoji_modifier` (see comment).
* While this PR isn't affected by it, take a look at
[wcwidth.zig](https://github.com/jacobsandlund/uucode/blob/main/src/x/config_x/wcwidth.zig)
for the considerations that went into determining the width of a code
point, along with many comments.
* See
[x/grapheme.zig](https://github.com/jacobsandlund/uucode/blob/main/src/x/grapheme.zig)
for the calculation of the width of a grapheme based on the width of the
code points with exceptions, again with many comments.
* See
[resources/wcwidth](https://github.com/jacobsandlund/uucode/tree/main/resources/wcwidth)
for a comparison of other unicode libraries calculation of "wcwidth".
PRs will follow this in a moment to also take advantage of the new
`uucode` version for:
* Better grapheme segmentation
* Correct VS15/VS16 handling
* More correct cell width calculation (especially certain scripts such
as Devanagari)
This makes `cursorStyle` utilize `RenderState` to determine the
appropriate cursor style. This moves the cursor style logic outside the
critical area, although it was cheap to begin with.
This always removes `viewport_is_bottom` which had no practical use.
This makes `cursorStyle` utilize `RenderState` to determine the
appropriate cursor style. This moves the cursor style logic outside the
critical area, although it was cheap to begin with.
This always removes `viewport_is_bottom` which had no practical use.
This adds a new API called `RenderState` that produces the proper state
required for a renderer to draw a viewport and updates our renderer to
use it instead of the prior screen clone method.
The newsworthy change is here is that we've shortened the critical area
where the renderer holds the terminal lock and blocks IO by anywhere
from **2x to 5x faster**, and in about half the frames we're now in the
critical area for **zero microseconds** because `RenderState` has much
better dirty/damage tracking.
**For libghostty Zig users**, this API is available to the Zig module
and can be used to create your own renderers (to any artifact, it
doesn't have to be graphical! It is even useful for text like a tmux
clone or something).
## Differences vs Old Method
The renderer previously called `Screen.clone`. This produces a copy of
all the screen data (even stuff the renderer may not need) and attempts
to create a standalone, fully functional `Screen` allocation. I didn't
microbenchmark this to understand exactly where the slowdown was, but I
think it was based primarily in two places:
- Screens have a minimum allocation of two Ghostty "pages" which are
quite large. I think this allocation was the primary expensive part.
Without refactoring our entire Screen/PageList work, this was
unavoidable.
- The clone was unconditional and would produce a fully new Screen on
every frame.
The new structure `RenderState` is stateful and each frame calls
`update` on the prior value and it modifies itself in place. This
addresses the above two points in a few ways:
- Allocation is minimized to only what we need, no full pages.
- Since it is stateful (updating in-place), we only change dirty data
and don't need to copy everything. **Note the benchmarks below only test
_full updates_, so you aren't even seeing the benefits of this!**
- Also since it is stateful, we cache expensive calculations (such as
for selections) so future screen updates can reuse those cached values
rather than recompute them.
- We retain memory from prior updates even when the screen is dirty, so
even in dirty states, it's unlikely we allocate.
More details in the "Design" section.
## Benchmarks
Here are some microbenchmarks on the previous render critical area
versus the new one.
For each of the benchmarks below, ignore the time units (milliseconds)
and instead **focus on the relative speedup.** The benchmarks are all
doing a full render frame setup around 1000 times, because the actual
cost of a frame update is in dozens or hundreds of microseconds.
> [!NOTE]
>
> I'm still working on some more benchmarks before merging. I'll update
this space.
### Full screen, single style
<img width="1542" height="480" alt="CleanShot 2025-11-21 at 07 35 13@2x"
src="https://github.com/user-attachments/assets/c28c9f33-d1aa-4723-8a8e-3c6d70fe3667"
/>
### Full screen, plaintext
<img width="1642" height="612" alt="CleanShot 2025-11-21 at 07 36 06@2x"
src="https://github.com/user-attachments/assets/b51f57cf-7c48-46c8-a347-8ecc0bdd3d47"
/>
### Full screen, different style per cell (pathological case)
<img width="1704" height="456" alt="CleanShot 2025-11-21 at 07 37 55@2x"
src="https://github.com/user-attachments/assets/71a98250-d8d1-47ab-ae69-5e6b3b60bf2d"
/>
### Critical Area: Typing at Shell Prompt
| This PR | Main Branch |
---------|--------
| <img width="1064" height="764" alt="CleanShot 2025-11-21 at 14 44
31@2x"
src="https://github.com/user-attachments/assets/8a0ab3a1-3d68-41f0-9469-bc08a4569286"
/> | <img width="1040" height="684" alt="CleanShot 2025-11-21 at 14 47
10@2x"
src="https://github.com/user-attachments/assets/04ffa607-8841-436b-b6e9-eeeb6ee9482d"
/> |
### Critical Area: Neovim Scrolling
| This PR | Main Branch |
---------|--------
| <img width="1054" height="748" alt="CleanShot 2025-11-21 at 14 45
06@2x"
src="https://github.com/user-attachments/assets/ccafaee8-720f-41be-820d-fd705835607a"
/> | <img width="1068" height="796" alt="CleanShot 2025-11-21 at 14 47
48@2x"
src="https://github.com/user-attachments/assets/68087496-d371-4c7c-8b4c-b967dbaeaa7c"
/> |
### Critical Area: `btop -u 100`
This is closer to a pathological case, about as close as you get with a
real tool in the wild. `btop` uses hundreds of unique styles and updates
many cells across many rows very frequently (every 100ms in this case).
You can see that some of our frame times in this case are similar but
there are _so many more near-idle frames_ thanks to our dirty tracking.
| This PR | Main Branch |
---------|--------
| <img width="1088" height="900" alt="CleanShot 2025-11-21 at 14 45
44@2x"
src="https://github.com/user-attachments/assets/ea63f0eb-f06e-4d00-95a3-c55a3755cc67"
/> | <img width="1078" height="906" alt="CleanShot 2025-11-21 at 14 48
18@2x"
src="https://github.com/user-attachments/assets/cef360de-2b12-440f-8c4c-6a69b5ce4058"
/> |
### "DOOM Fire"
Fullscreen on my macOS when from 770 FPS to ~808 FPS consistently, a
solid 5% increase repeatedly.
<img width="3520" height="2392" alt="CleanShot 2025-11-21 at 07 45
29@2x"
src="https://github.com/user-attachments/assets/033effca-0abb-4ff8-a21b-83214d118d12"
/>
### IO
While this was rendering focused, the smaller critical area does help IO
performance a bit.
We've already tracked down the remaining issues to the IO thread going
to sleep and overhead with context switching. We're investigating
switching to a spin lock for the IO thread only in another track of
work.
> [!NOTE]
>
> **This is comparing with `main`, which already has a 20-30%
performance improvement over v1.2.3.**
<img width="982" height="698" alt="image"
src="https://github.com/user-attachments/assets/52a86f6c-6f09-45fe-9ac7-ca62c7ac6ee4"
/>
## Design
The design of the API is a _stateful_ `RenderState` struct that you call
`update` on each frame with the target terminal, and it only updates
what is needed. RenderState keeps track of rows, cells, hyperlinks,
selections, etc.
```zig
// Start empty
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
// Each frame update it with a terminal.
// THIS IS THE ONLY PART THAT ACCESS `t`
try state.update(alloc, &t);
// Access render data (can be outside any locking for `t`)
...
```
The ergonomics of the `RenderState` structure a wee bit clunky because
we make use of struct-of-arrays (SoA, Zig's MultiArrayList) to better
optimize cache locality for renderer data vs. update data (what we need
to update the render state is different from what we need to draw).
Once you get used to the API though, it's pretty beautiful. I mean, look
at this:
```zig
for (
0..,
row_data.items(.raw),
row_data.items(.cells),
) |y, row, cells| {
const cells_slice = cells.slice();
for (
0..,
cells_slice.items(.raw),
cells_slice.items(.grapheme),
) |x, cell, graphemes| {
```
## Improvements
This PR makes various improvements across the board:
- It bears repeating in case it was missed previously that the critical
area time of a render has gone down 2x to 5x when there is work and is
now free when there is no work (the previous implementation always did
work).
- Font shaping is much more efficient now and only requires access to a
render state.
- Selection handling is now cached and works with dirty tracking.
Previously, if you had an active selection, we'd search the entire
screen multiple times (like... once per row). Yikes.
- Hyperlink handling is _much_ more efficient. Instead of iterating
through the entire screen contents _per configured link_ we now cache
the screen contents as a string and search one whole string multiple
times. Obvious, but we didn't do this before.
- The `contrainedWidth` and `rowNeverExtendBg` helper methods are now
both much more efficient and live within the renderer package rather
than being awkwardly in the terminal package.
## Future Notes
- Our `terminal.Selection` API is very bad. It conceptually makes sense
and I understand why I designed it this way (easy) but it makes it hard
to render or manipulate performantly.
**AI Disclosure:** AI was used only to assist with writing some tests
and converting some tests. The primary logic is all organic, meatbag
produced.
This change should give more consistent results between high and low DPI
displays, and generally approximate the authorial intent of the metrics
a little better.
Also changed the cell height adjustment to prioritize the top or bottom
when adjusting by an odd number depending on whether the face is higher
or lower in the cell than it "should" be. This should make it easier for
users who have an issue with a glyph protruding from the cell to adjust
the height and resolve it.
The code that re-creates the font descriptor from scratch using the same
attributes also rubs off the magic dust that makes CoreText not throw a
fit at us for using a "hidden" system font (name prefixed with a dot) by
name when we use the descriptor. This means that a small subset of chars
that only have glyphs in these fallback system fonts like ".CJK Symbols
Fallback HK Regular" and ".DecoType Nastaleeq Urdu UI" would not be able
to be rendered, since when we requested the font with the non-magical
descriptor CoreText would complain in the console and give us Times New
Roman instead.
Using `CTFontDescriptorCreateCopyWithAttributes` to clear the charset
attribute instead of recreating from scratch makes the copy come out
magical, and CoreText lets us instantiate the font from it, yippee!
### ℹ️ For anyone who came to this PR from a search engine rabbit hole
trying to figure this out for your own code:
It seems like if you want to use one of these fonts, you have to avoid
creating a new descriptor from scratch with the name. To create a
descriptor with one of these names that CoreText won't get upset about,
you have to get it in one of these ways (there may be others but these
are ones that definitely work at time of writing):
- Get it from `CTFontCreateForString`(`WithLanguage`) with a character
only provided by that font.
- Get it from `CTFontCreateUIFontForLanguage` or any of the mechanisms
for generally receiving a system font that you don't necessarily have
full control over the exact choice of font with.
- Get it from a collection created with
`CTFontCollectionCreateMatchingFontDescriptors`
- Get it from `CTFontCreateWithGraphicsFont` ... and it seems that for
now, `CGFontCreateWithFontName` lets you use these "hidden" names
without complaining :)
I wouldn't trust any of these methods to necessarily *continue* working
in to the future, since Apple seems to be trying to dissuade
*intentional* use of any particular named hidden system font.
Realistically, the `CreateForString` option is the most likely to stick
around.
If all else fails, check FireFox or Chrome's source code to see how
they're solving it in current year.
And to help it get indexed for those looking for it...
<details>
<summary>CoreText's angry message</summary>
```
CoreText note: Client requested name ".CJKSymbolsFallbackHK-Regular", it will get TimesNewRomanPSMT rather than the intended font. All system UI font access should be through proper APIs such as CTFontCreateUIFontForLanguage() or +[NSFont systemFontOfSize:].
CoreText note: Set a breakpoint on CTFontLogSystemFontNameRequest to debug.
```
</details>
Draw proper anti-aliased dots now instead of rectangles, thanks to z2d
this is very easy to do, and the results are very nice, no more weird
gaps in dotted underlines if your cell is the wrong number of pixels
across.
Use z2d to draw the undercurl instead of the manual raster code we had
before- the code was cool but unnecessarily complicated. Plus z2d lets
us have rounded caps on the undercurl which is neat.
Also make sure we won't draw off the canvas with our underlines-- the
canvas has padding but it's not infinite.
This change should give more consistent results between high and low DPI
displays, and generally approximate the authorial intent of the metrics
a little better.
Also changed the cell height adjustment to prioritize the top or bottom
when adjusting by an odd number depending on whether the face is higher
or lower in the cell than it "should" be. This should make it easier for
users who have an issue with a glyph protruding from the cell to adjust
the height and resolve it.
Add descriptions to fish shell completions
Claude Code used to understand the codebase and reason through the edge
cases (several iterations)
- Using first sentence from the Config to add description for each
config, even if sentence spans few lines
- Several options can share the same description, code doesn't duplicate
description, see screenshot in the posted issue thread below
Fixes [#9531](https://github.com/ghostty-org/ghostty/issues/9531)
The code that re-creates the font descriptor from scratch using the same
attributes also rubs off the magic dust that makes CoreText not throw a
fit at us for using a "hidden" system font (name prefixed with a dot) by
name when we use the descriptor. This means that a small subset of chars
that only have glyphs in these fallback system fonts like ".CJK Symbols
Fallback HK Regular" and ".DecoType Nastaleeq Urdu UI" would not be able
to be rendered, since when we requested the font with the non-magical
descriptor CoreText would complain in the console and give us Times New
Roman instead.
Using `CTFontDescriptorCreateCopyWithAttributes` to clear the charset
attribute instead of recreating from scratch makes the copy come out
magical, and CoreText lets us instantiate the font from it, yippee!
A whole bunch of optimizations in hot paths in the IO processing areas
of our code (well, one of them covers everything). I validated that each
commit either improved one or more of our vtebench results, or improved
the time it takes to process 2 years worth (2.4GB) of data from
asciinema.
## vtebench
<img width="1278" height="903" alt="image"
src="https://github.com/user-attachments/assets/bad46777-4606-4870-b7d7-8df0c4bb3b39"
/>
(I decided to patch vtebench to report in nanoseconds instead of
milliseconds since clearly it was not designed for a machine as fast as
mine. Nanoseconds gives much more useful results when the numbers are
this low.)
Do note the *slight* regression in the "unicode" test, this is probably
because I added a branch hint in `Terminal.print` in order to optimize
for printing narrow characters, since they make up the vast majority of
characters typically printed in the terminal, but the vtebench "unicode"
test is pretty much all wide characters.
This shouldn't have a negative effect on users of CJK languages since
it's a *very* slight reduction in speed and they will still be printing
many narrow characters, especially in TUIs; spaces, box drawing
characters, symbols, punctuation, etc.
## asciinema processing
I wrote a program that uses libghostty to push 2 years worth (2.4GB) of
data from publicly uploaded asciinema recordings in to the terminal as
fast as possible- since it's just libghostty, there's no renderer
overhead happening, it's just the core terminal emulation, effectively
everything that io-reader thread does if it didn't have wait for the
renderer ever.
On main, this took roughly 26.1–26.7 seconds to process, on this branch
it takes just 18.4–18.6 seconds, that's a ~30% improvement in raw IO
processing speed when processing real world data!
## Summary of changes
In order of commits:
- Fixed a bug that I hit when trying to have Ghostty process all that
asciinema data, in certain bad cases it was possible to accidentally
insert the `0` hyperlink ID in to a page, which would then cause a
lockup in ReleaseFast mode when trying to clone that page since the
string alloc would try to iterate `1..0` to allocate 0 chunks.
- I noticed in profiling Ghostty that `std.debug.assert` was showing up
in the profile, which it should not have been since its doc comment
promises that it will be optimized out in ReleaseFast- but evidently
something is wrong with Zig, or that comment's promise is based on an
expectation from LLVM that it fails to meet - but either way, by
replacing all uses of `assert` with a version that is explicitly marked
`inline`, that function call overhead in tight loops and hotpaths is
avoided. This change alone accounts for like a third of the IO
processing time improvement, though it had minimal impact on vtebench
scores.
- I optimized the SGR parser somewhat by adding branch hints and
removing the `.reset_underline` action, replacing it with `.{ .underline
= .none }`.
- Gated a somewhat expensive assert in RefCountedSet behind a runtime
safety check.
- Improved the performance of `Style.eql` and `Style.hash` since these
are hot functions, called extremely frequently since adding styles to
the style set is a very common operation. Achieved this by making `eql`
less generic - explicitly comparing each part of the style rather than
looping over fields - and ordering checks from most likely to differ to
least likely to differ so that differences can be found as soon as
possible; and changed the hash from xxhash to simply folding the packed
struct down to 64 bits and then using `std.hash.int`. Also manually
inlined the code from `std.meta.activeTag` in `Packed.fromStyle`, since
profiling showed it in the callstack and it's a single cast so it really
should not have the function call overhead.
- Explicitly marked some trivial functions as inline, the optimizer
would already have been doing this (probably) but doing it explicitly
gives the optimizer more time to spend on other things. Added cold
branch hints to "should be impossible" and error-returning paths that
should be very rare, and unlikely branch hints to a lot of "invalid"
paths- to optimize for receiving valid data.
- Removed a branch in the parser csi param action, just unconditionally
multiply by 10 before adding digit value, even if it's the first digit.
This codepath is rarely hit since we have a fast path for this in the
stream code, but the stream code already has this optimization so I just
copied it over.
- `CharsetState.charsets` used to be an `EnumArray`, but the
layout/access logic for that was less-than-ideal, and the access
functions were not inlining-- and these are very hot since we access
this for every single print, so I wrote a bespoke struct to hold that
info instead, gained a couple percent of IO perf with that.
- Added branch hints based on the data I derived from the asciinema
dump, which gave big boost to vtebench results, especially for the
cursor movement and dense cells tests (which makes sense, since cursor
movement and setting attributes both got `likely` hints :p) -- data at
https://github.com/qwerasd205/asciinema-stats
- This is probably the most invasive change in this PR: I removed the
dirty bitset from `Page` and replaced it with a dirty flag on each row,
for the majority of operations this is faster to write, since the row
being dirtied is probably already loaded and probably will be written to
for other changes as well. This gave a couple percent IO processing
improvement. The only exception is scrolling-type operations, which are
extremely efficient by just moving rows around with a single memmov, so
looping through the rows to mark each dirty slows them down, and indeed
after this change the scrolling benchmarks in vtebench regressed,
*however*...
- Added a "full page dirty" flag on `Page`, which is set when an
operation is performed that dirties most or all the rows in the page,
which is used for scrolling-type operations. This *does* make the dirty
tracking slightly less precise for these operations, but with the
caching and stuff we do in the renderer, I don't think `rebuildCells` is
a bottleneck, so rebuilding a few extra rows shouldn't hurt. After this
change, all the scrolling benchmarks in vtebench improved drastically.
- Tiny micro-improvements to RefCountedSet; streamlined the control flow
in `lookup`, added an unlikely branch hint in `insert` for the branch
that resurrects dead items since dead items aren't that common.
- Improve SGR parser performance again by using `@call(.always_inline`
to explicitly inline calls to `StaticBitSet.isSet` (for the separator
list), since I noticed they weren't being inlined, causing function call
overhead in a hotpath.
- I noticed that `clearGrapheme` and `clearHyperlink` would check every
cell in the row after they were done in order to update the
`grapheme`/`hyperlink` flag on the row if there were none left, which
isn't great since `clearCells` called these functions for multiple cells
in the same row back-to-back, which leads to a ton of excess work. I
separated the flag updating parts of these functions out and called them
only if necessary (if the cells being cleared were the full row then the
flag could unconditionally be set to false) and only after all the cells
were cleared. This gave a nice improvement to IO processing since
clearCells is evidently a very hot function.
- Removed inline annotations on `Page.clearGrapheme` and
`Page.clearHyperlink` in favor of inlining directly at the one callsite
that benefited from inlining, this improved IO processing speed.
- Inlined trivial function `Charset.table`.
- Inlined `size.getOffset` and `size.intFromBase` as they are both
trivial pointer math that often benefits from surrounding context.
---
If you'd like me to separate out the trivial improvements (branch hints,
inline annotations, 1-line changes) from the functionality-changing ones
(pretty much just the changes to dirty tracking), just let me know!
These were actually hurting performance lol, except in the places where
I added the `.always_inline` calls- for some reason if these functions
aren't inlined there it really messes up the top region scrolling
benchmark in vtebench and I'm not entirely certain why...
This PR partially addresses #4504 with a one-liner, all feedback is very
welcome.
### AI disclaimer
I used Claude Code to help navigate the various layers of the rendering
stack, to instrument a ton of intermediate now-deleted log statements,
and ultimately to identify and fix this bug. I directed it
conversationally, gave it experiments to run, audited all of its work,
threw most of it out, and finally landed on this extremely small and
simple change that fixes the issue _for me_ but I think for a lot of
other cases as well. The fix ended up being small, so hopefully it's
easy to review and discuss.
Details:
# Fix horizontal glyph spacing on non-Retina displays
On external monitors (1.0x scale, 72 DPI), text had excessive horizontal
spacing between glyphs. The issue was less noticeable on Retina displays
(2.0x scale, 144 DPI) due to higher pixel density, but the proportional
spacing error was identical.
## Before
Background is ghostty 1.2.3 from homebrew, foregreound is iTerm2:
<img width="862" height="938" alt="Screenshot 2025-10-31 at 3 04 47 PM"
src="https://github.com/user-attachments/assets/feff5279-05cc-4008-b2f5-8ea3b4d6d14b"
/>
## After
Background is ghostty tip + this change, foreground is iTerm2:
<img width="659" height="774" alt="Screenshot 2025-10-31 at 3 00 42 PM"
src="https://github.com/user-attachments/assets/702dc7f8-bb46-43ec-8156-f69d003e8a37"
/>
(my iTerm2 has some custom thickening / brightening you can see; that's
not a part of this change)
## Root Cause
The metrics calculation in Metrics.zig used `@ceil()` to round the cell
width from CoreText's glyph measurements, which created a mismatch
between cell width (which determines glyph positions) and the actual
glyph advances.
At 72 DPI with a 12pt font:
- CoreText returns glyph advance: 7.224609375 pixels
- Cell width was ceiled to: 8 pixels
- Gap per character: 0.78 pixels (~10.8% error)
At 144 DPI with the same font:
- CoreText returns glyph advance: 14.44921875 pixels (exactly 2x)
- Cell width was ceiled to: 15 pixels
- Gap per character: 0.55 pixels (~3.8% error)
The error at high DPI is much better than at low DPI, since the absolute
error is always no more than 1px.
## Fix
Changed `@ceil(face_width)` to `@round(face_width)`. This makes cell
width match the glyph advances better, reducing the error to at most
0.5px:
- 72 DPI: round(7.22) = 7
- 144 DPI: round(14.45) = 14
Height continues using `@ceil()` since vertical space can be slightly
larger without visual issues, but... should it? I'm not sure; this is a
good topic for discussion.
This improves the `clearCells` function since it only has to update once
after clearing all of the individual cells, or not at all if the whole
row was cleared since then it knows for sure that it cleared them all.
This also makes it so that the row style flag is properly tracked when
cells are cleared but not the whole row.
I am so sick and tired of people complaining that the build instructions
on the website are wrong when they clearly haven't realized the difference
between Git-based and tarball-based builds, so here's the extra work to
make sure people actually realize that
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0
to 5.0.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v5...v5.0.1">https://github.com/actions/checkout/compare/v5...v5.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="93cb6efe18"><code>93cb6ef</code></a>
Cleanup actions/checkout@v6 auth style (<a
href="https://redirect.github.com/actions/checkout/issues/2301">#2301</a>)</li>
<li>See full diff in <a
href="08c6903cd8...93cb6efe18">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.8.3 to 31.8.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.8.4</h2>
<h2>What's Changed</h2>
<ul>
<li>nix: 2.32.3 -> 2.32.4 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/261">cachix/install-nix-action#261</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31.8.3...v31.8.4">https://github.com/cachix/install-nix-action/compare/v31.8.3...v31.8.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="0b0e072294"><code>0b0e072</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/261">#261</a>
from cachix/create-pull-request/patch</li>
<li><a
href="16d2e3294d"><code>16d2e32</code></a>
nix: 2.32.3 -> 2.32.4</li>
<li>See full diff in <a
href="7ec16f2c06...0b0e072294">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Currently, the scroller's appearance is the same as the window's.
So, with the light system appearance and the following config:
```
window-theme = system
macos-titlebar-style = native
```
It's hard to see where the scroller is. This pr changes the
scroller’s(ScrollView) appearance to match the surface's background
colour, so it's always easier to find.
> Changing `verticalScroller?.appearance` doesn't seem to work
<img width="601" height="630" alt="image"
src="https://github.com/user-attachments/assets/9dc18439-9dcb-479a-802a-de439b7dc9d8"
/>
This adds a benchmark and some test coverage for a `screen-clone`
benchmark. This benchmarks the screen cloning which is a hot spot for
lock contention for the renderer + IO threads. I wasn't able to
meaningfully speed this up, but still want to commit this benchmark.
### Problem
Custom icon configuration (`macos-icon = custom-style` with
`macos-icon-screen-color`) stopped working, reverting to the default
icon.
### Root cause
The `ColorList.clone()` method only cloned the `colors` array but not
the `colors_c` array. The Swift code reads from `colors_c` via the C API
(`ghostty_config_get`), so when configs were cloned, the C-accessible
color list was empty.
### Why it broke
This bug was introduced in the original implementation in 29929a473 (Dec
2024), but remained dormant until commit f60bdb0fa (Sep 19, 2025), which
moved the icon-setting `switch` statement into `syncAppearance()`. Since
`syncAppearance()` is called with cloned configs from
`ghosttyConfigDidChange()` (which receives cloned configs from
`Ghostty.App.swift:1639`), the icon code now ran with cloned configs
that had empty `colors_c` arrays.
### Fix
Clone both arrays in `ColorList.clone()`:
```zig
.colors = try self.colors.clone(alloc),
.colors_c = try self.colors_c.clone(alloc), // Added
```
### Testing
- Added ColorList.test.clone test case that verifies both colors and
colors_c arrays are properly cloned
- Verified test fails without the fix (expected 3 colors_c items, found
0)
- Verified test passes with the fix
- Confirmed custom icon now persists correctly with both initial config
load and subsequent config change notifications
### Discussion
I opened a discussion to report this
([9616](https://github.com/ghostty-org/ghostty/discussions/9616)) that
this PR will resolve.
> [!NOTE]
> **LLM Usage Disclosure**
> This bug was investigated and debugged with assistance from Claude
Code. The root cause analysis and fix were developed through interactive
debugging.
This could cause a 0-length hyperlink to be present in the screen,
which, in ReleaseFast, causes a lockup as the string alloc tries to
iterate `1..0` to allocate 0 chunks.
I encountered these bugs while trying to benchmark Ghostty for
performance work.
- Tmux control mode parsing would start accessing deallocated memory
after entering the "broken" state, and worse yet, would cause a
double-free once it was supposed to be deinited.
- Despite our best efforts, CoreText can still produce non-monotonic
(non-ltr) runs. Our renderer code relies on monotonic ltr ordering so in
the rare case where this happens we just sort the buffer before
returning it.
- C1 (8-bit) controls can be executed in certain parser states, so we
need to handle them in the stream's `execute` function. Luckily this was
pretty straightforward since all C1 controls are equivalent to `ESC`
followed by `C1 - 0x40`.
- `Terminal.Screen`'s `cursorScrollDown` function could cause memory
corruption because of `eraseRow` moving the cursor's tracked pin to a
different page. In fixing this, I actually reduced the complexity of
that codepath.
- **Bonus!** Added a nice helper function to `Offset.Slice` so that you
can just do `offset_slice.slice()` instead of
`offset_slice.offset.ptr(base)[0..offset_slice.len]`. Much more
readable.
### `vtebench` before/after
<img width="984" height="691" alt="image"
src="https://github.com/user-attachments/assets/ef20dcc5-d611-4763-9107-355d715a6c0b"
/>
Doesn't seem like any of these changes caused a performance regression.
It was previously possible for `eraseRow` to move the cursor pin to a
different page, and then the call to `cursorChangePin` would try to free
the cursor style from that page even though that's not the page it
belongs to, which creates memory corruption in release modes and
integrity violations or assertions in debug mode.
As a bonus, this should actually be faster this way than the old code,
since it avoids needless work that `cursorChangePin` otherwise does.
These can be unambiguously invoked in certain parser states, and as such
we need to handle them. In real world use they are extremely rare, hence
the branch hint. Without this, we get illegal behavior by trying to cast
the value to the 7-bit C0 enum.
Progress towards #189
## Search Thread
This completes an initial implementation of the search thread. This is a
separate thread that manages a terminal search operation, triggering
event callbacks for various scenarios. The performance goal of the
search thread is to minimize time spent within the critical area of the
terminal lock while making forward progress on search outside of that.
The search thread sends two messages back to the caller right now:
- Total matches: sent continuously as new matches are found, just a
number
- Viewport matches: a list of `Selection` structures spanning the
current viewport of matches within the visible viewport. Sent whenever
the viewport changes (location or content).
I think that's enough to build a rudimentary search UI.
For this initial implementation, the search also relies on a "refresh
timer" which trigger every 24ms (40 FPS) to grab the lock and look for
any reconciliation that needs to happen: viewport moved, active screen
changed, active area changed, etc. This is a total guess and is
arbitrary currently. The value should be tuned to balance responsiveness
and IO throughput (lock-holding). I actually suspect this may be too
frequent right now.
A TODO is noted for the future to pause the refresh timer when the
terminal being search isn't focused. We'll have to do that before
shipping search because the way its built right now we will definitely
consume unnecessary CPU while unfocused. But only while search is
active.
## `ViewportSearch`
I also determined the need for what is called the `ViewportSearch`
layer. This is similar to `ActiveSearch` in that it throws away and
re-searches an area, but is tuned towards efficiently detecting viewport
changes. I found its more efficient to continuously research the visible
viewport than to hunt for those matches within the cached ScreenSearch
results, which can be very large.
## Future
Next up we need to hook up the search thread to some keybindings to
start and stop the search so we can then trigger those from our apprts
(GUIs).
I highly suspect this is going to expose some major performance issues
(overly active message sending being the likely culprit) that we can fix
up in the search thread thereafter. Up until this point we can only run
this stuff in isolation in unit tests which is good for testing
correctness but difficult for testing resource usage.
There are some written TODOs in the Thread.zig file in this PR. I may
address some of them before merging, since I think a couple are pretty
obvious performance gotchas.
This PR changes our primary/alt screen tracking from being by-value
fields that are copied during switch to heap-allocated pointers that are
stable. This is motivated now by #189 since our search thread needs a
stable screen pointer, but this will also help us in the future with our
future N-screens proposal I have.
Also, as a nice bonus, alt screen memory is now initialized on demand
when you first enter alt-screen, so this saves a few MB of memory for a
new terminal if you never use alt screen!
This is something I've wanted to do for a veryyyyyy long time, but the
annoyance of the task really held me back. I finally pushed through and
did this with the help of some AI agents for the rote tasks (renaming
stuff).
Chugging along towards #189
This adds significantly more internal work for searching. A long time
ago, I added #2885 which had a hint of what I was thinking of. This
simultaneously builds on this and changes direction.
The change of direction is that instead of making PageList fully
concurrency safe and having a search thread access it concurrently, I'm
now making an architectural shift where our search thread will grab the
big lock (blocking all IO/rendering), but with the bet that we can make
our critical areas small enough and time them well enough that the
performance hit while actively searching will be minimal. **Results yet
to be seen, but the path to implement this is much, much simpler.**
## Rearchitecting Search
To that end, this PR builds on #2885 by making `src/terminal/search` and
entire package (rather than a single file).
```mermaid
graph TB
subgraph Layer5 ["<b>Layer 5: Thread Orchestration</b>"]
Thread["<b>Thread</b><br/>━━━━━━━━━━━━━━━━━━━━━<br/>• MPSC queue management<br/>• libxev event loop<br/>• Message handling<br/>• Surface mailbox communication<br/>• Forward progress coordination"]
end
subgraph Layer4 ["<b>Layer 4: Screen Coordination</b>"]
ScreenSearch["<b>ScreenSearch</b><br/>━━━━━━━━━━━━━━━━━━━━━<br/>• State machine (tick + feed)<br/>• Result caching<br/>• Per-screen (alt/primary)<br/>• Composes Active + History search<br/>• Interrupt handling"]
end
subgraph Layer3 ["<b>Layer 3: Domain-Specific Search</b>"]
ActiveSearch["<b>ActiveSearch</b><br/>━━━━━━━━━━━━━━━━━━━━━<br/>• Active area only<br/>• Invalidate & re-search<br/>• Small, volatile data"]
PageListSearch["<b>PageListSearch</b><br/>━━━━━━━━━━━━━━━━━━━━━<br/>• History search (reverse order)<br/>• Separated tick/feed ops<br/>• Immutable PageList assumption<br/>• Garbage pin detection"]
end
subgraph Layer2 ["<b>Layer 1: Primitive Operations</b>"]
SlidingWindow["<b>SlidingWindow</b><br/>━━━━━━━━━━━━━━━━━━━━━<br/>• Manual linked list node management<br/>• Circular buffer maintenance<br/>• Zero-allocation search<br/>• Match yielding<br/>• Page boundary handling"]
end
Thread --> ScreenSearch
ScreenSearch --> ActiveSearch
ScreenSearch --> PageListSearch
ActiveSearch --> SlidingWindow
PageListSearch --> SlidingWindow
classDef layer5 fill:#0a0a0a,stroke:#ff0066,stroke-width:3px,color:#ffffff
classDef layer4 fill:#0f0f0f,stroke:#ff6600,stroke-width:3px,color:#ffffff
classDef layer3 fill:#141414,stroke:#ffaa00,stroke-width:3px,color:#ffffff
classDef layer2 fill:#1a1a1a,stroke:#00ff00,stroke-width:3px,color:#ffffff
class Thread layer5
class ScreenSearch layer4
class ActiveSearch,PageListSearch layer3
class SlidingWindow layer2
style Layer5 fill:#050505,stroke:#ff0066,stroke-width:2px,color:#ffffff
style Layer4 fill:#080808,stroke:#ff6600,stroke-width:2px,color:#ffffff
style Layer3 fill:#0c0c0c,stroke:#ffaa00,stroke-width:2px,color:#ffffff
style Layer2 fill:#101010,stroke:#00ff00,stroke-width:2px,color:#ffffff
```
Within the package, we have composable layers that let us test each
point:
- `SlidingWindow`: The lowest layer, the caller manually adds linked
list page nodes and it maintains a sliding window we search over,
yielding results without allocation (besides the circular buffers to
maintain the sliding window).
- `PageListSearch`: Searches a PageList structure in reverse order
(assumption: more recent matches are more valuable than older), but
separates out the `tick` (search, but no PageList access) and `feed`
(PageList access, prep data for search but don't search) operations.
This lets us `feed` in a critical area and `tick` outside. **This
assumes an immutable PageList, so this is for history.**
- `ActiveSearch`: Searches only the active area of a PageList. The
expectation is that the active area changes much more regularly, but it
is also very small (relative to scrollback). Throws away and re-searches
the active area as necessary.
- `ScreenSearch`: Composes the previous three components to coordinate
searching an active terminal screen. You'd have one of these per screen
(alt vs primary). This also caches results unlike the other components,
with the expectation that the caller will revisit the results as screens
change (so if you switch from neovim back to your shell and vice versa
with a search active, it won't start over).
- `Thread`: A dedicated search thread that will receive messages via
MPSC queues while managing the forward progress of a `ScreenSearch` and
sending matches back to the surface mailbox for apprt rendering. **The
thread component is not functional, just boilerplate, in this PR.**
ScreenSearch is a state machine that moves in an iterative `tick` +
`feed` fashion. This will let us "interrupt" the search with updates on
the search thread (read our mailbox via libxev loops for example) and
will let us minimize critical areas with locks (only `feed`).
Each component is significantly unit tested, especially around page
boundary cases. Given the complexity, there is no way this is perfect,
but the architecture is such that we can easily add regression tests as
we find issues.
## Other Changes, Notes
The only change to actually used code is that tracked pins in a
`PageList` can now be flagged as "garbage." A garbage tracked pin is one
that had to be moved in a non-sensical way because the previous location
it tracked has been deleted. This is used by the searcher to detect that
our history was pruned.
**If my assumption about the big lock is wrong** and this ends up being
godawful for performance, then it should still be okay because more
granular locking and reference counting such as that down by @dave-fl in
#8850 can be pushed into these components and reused. So this work is
still valuable on its own.
## Future
This PR is still just a bunch of internals, split out into its own PR so
I don't make one huge 10K diff PR. There are a number of future tasks:
- Flesh out `ScreenSearch` and hook it up to `Thread`
- Pull search thread management into `Surface` (or possibly the render
thread or shared render state since active area changes can be
synchronized with renderer frame rebuilds. Not sure yet.)
- Send updates back to the surface thread so that apprts can update UI.
- Apprt actions, input bindings, etc. to hook this all up (the easy
part, really).
The next step is to continue to flesh out the `ScreenSearch` as required
and hook it up to `Thread`.
**AI disclosure:** AI reviewed the code and assisted with some tests,
but didn't write any of the logic or design. This is beyond its ability
(or my ability to spec it out clearly enough for AI to succeed).
Fixes#9579
Protect against panics caused by integer overflows by using functions
that allow integer overflows to be caught instead of causing a panic.
Also protect against DOS from images that might not cause an overflow
but do consume an absurd amount of memory by limiting images to a
maximum size of 4GiB.
Fixes#9579
Protect against panics caused by integer overflows by using functions
that allow integer overflows to be caught instead of causing a panic.
Also protect against DOS from images that might not cause an
overflow but do consume an absurd amount of memory by limiting
images to a maximum size of 4GiB (which is the maximum size of
`image-storage-limit`).
Fixes#9532
Supersedes #9554
Turns out the explicit `UTF8_STRING` atom was not needed after all and
GTK adds it automatically when running under X11; just having the
explicit UTF-8 charset type is enough.
This corrects situations where it may not be necessary to include
(Wayland), in addition to removing a duplicate atom under X11.
Importantly, this also corrects issues under Wayland in some scenarios,
such as using Electron-based apps (e.g., VSCode/Codium under Ubuntu
24.04 LTS).
Turns out this was not needed after all and GTK adds it automatically
when running under X11; just having the explicit UTF-8 charset type is
enough.
This corrects situations where it may not be necessary to include
(Wayland), in addition to removing a duplicate atom under X11.
Importantly, this also corrects issues under Wayland in some scenarios,
such as using Electron-based apps (e.g., VSCode/Codium under Ubuntu
24.04 LTS).
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.8.2 to 31.8.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.8.3</h2>
<h2>What's Changed</h2>
<ul>
<li>nix: 2.32.2 -> 2.32.3 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/260">cachix/install-nix-action#260</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31.8.2...v31.8.3">https://github.com/cachix/install-nix-action/compare/v31.8.2...v31.8.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7ec16f2c06"><code>7ec16f2</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/260">#260</a>
from cachix/create-pull-request/patch</li>
<li><a
href="5afc2ac89d"><code>5afc2ac</code></a>
nix: 2.32.2 -> 2.32.3</li>
<li>See full diff in <a
href="456688f15b...7ec16f2c06">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
There is a sparkle-related 'issue' with the previous implementation.
When you download/install in the `updateAvailable` state, if you don't
install it, then check the updates again. Sparkle loses its downloaded
stage in the delegate (it's normal when I use the sparkle source code).
This time, when you click install in the `updateAvailable` state, it
just uses the previous downloaded package and starts to install, without
calling `showReady(toInstallAndRelaunch:)`.
I think removing `readyToInstall` in our customised ui makes sense,
since the current package is pretty small and only takes a few seconds
to download for a normal network. And most of users intend to install
this update.
Also, there is no way back once you reach "Install and Restart" stage in
`SPUStandardUserDriver`, unless you force quit ofc.
~~This pr also contains some further cleanup for #9170, since
`InstallingView` is no longer visible for users.~~
#### `auto-download`
When `auto-download = check`, Sparkle will call
`UpdateDriver/showUpdateFound(with:state:reply:)`.
When `auto-download = download`, previously no update ui was presented
to the user, neither Sparkle's nor Ghostty’s.
> I tried tick&untick auto download&install in Sparkle's alert, which
doesn't seem to make any difference.
With this pr, `auto-download = check` will behave the same,
**`auto-download = download` will now prompt the user with a capsule.**
https://github.com/user-attachments/assets/f994dd29-d348-4fea-8777-df3720d6e7af
> [!NOTE]
> I used AI to proofread my comments
There is a sparkle-related 'issue' with the previous implementation. When you download/install in the `updateAvailable` state, if you don't install it, then check the updates again. Sparkle loses its downloaded stage in the delegate (it's normal when I use the sparkle source code). This time, when you click install in the `updateAvailable` state, it just uses the previous downloaded package and starts to install, without calling `showReady(toInstallAndRelaunch:)`.
I think removing `readyToInstall` in our customed ui, will reduce one step to install an update for most of the users out there, which makes sense, since the current package is pretty small, only takes a few seconds to download for a normal network, and they intended to install this update.
Resolves Issue #8357
### Implementation
Following the existing `onResize` callback pattern in
`TerminalSplitTreeView`, I added an `onEqualize` callback to
`SplitView`. When a divider is double-tapped, the callback retrieves a
surface from that `TerminalView`'s `SplitTree` and calls `splitEqualize`
to equalize the entire tree.
### Context
There is an existing PR #8364 that implements this feature but uses
`focusedSurface`, which doesn't work for unfocused windows. Since that
PR has been inactive for a few months after requested changes, I've
implemented this alternative approach.
Credit to @liby for that initial implementation!
### AI Usage
I chatted with Claude Code in Plan mode to understand the relationship
between surfaces and the split tree/split views, but I wrote all the
code myself.
### Screenshot
https://github.com/user-attachments/assets/0efd70ef-c90e-4b50-b853-b05e2ca2be67
Listen to changes to the OS preferred scroller style and use it to
trigger updates the core surface size such that the scrollbar margin
kept in sync. Otherwise, the margin update would be deferred until the
next window or surface resize/split event.
In the video I'm turning my external bluetooth mouse off and on again,
triggering these updates.
https://github.com/user-attachments/assets/15c0ee40-2c1a-419a-8b07-8270e9f7a12f
Here's a much simpler approach to fixing #9248 that doesn't involve
changes to the core or libghostty (the apprt one) at all. No safe area
API needed. All that's needed is for the content view to not clip its
subviews, such that the surface view can draw behind the scrollbar even
though it's a subview of the content view.
I haven't looked at the details on the renderer side, but since this
works it must be that it always renders background all the way to the
edges of the framebuffer, regardless of what the terminal screen size
(including window padding) happens to be. (This was already evident in
that we were able to avoid a transparent slot when the scrollbar was
hidden.) So I guess we kind of already have a safe area API through this
ability to set a screen size that's different from the framebuffer size?
_EDIT:_ I guess what's missing is the ability to shift the origin of the
screen within the framebuffer.
Fixes#9248, supersedes #9291.
Currently, close confirmation is always attached to the first tab in the
window group.
**This pr will attach the alert to the window that actually needs that
confirmation, so upon close, the window with the running process will be
focused.**
https://github.com/user-attachments/assets/8fddd4ce-a3f9-4e18-a645-80c85b5ba995
### Reproduce Steps on Tip (1.2.3 seems fine)
1. Open a new window.
2. Open a new tab.
3. **Wait for 2-3 seconds.**
4. Undo.
5. 💥
**RC is unclear for me, but dispatching it seems to fix it.**
This makes `ucs-detect` available in our Nix environment so that we can
run tests on our Unicode support. In the future, I'd like to modify our
CI to run this too.
This also adds a `./test/ucs-detect.sh` script that runs `ucs-detect`
with consistent options that match the upstream test styles.
Fwiw, I did try to write a simple libghostty-based Zig binary to run
this automatically for us in CI but while libghostty-vt is very good, we
don't yet have the proper APIs setup for actually setting up a Pty and
sub processing commands and hooking them up to a VT stream. So, I punt
that to the future.
This makes `ucs-detect` available in our Nix environment so that we can
run tests on our Unicode support. In the future, I'd like to modify our
CI to run this too.
This also adds a `./test/ucs-detect.sh` script that runs `ucs-detect`
with consistent options that match the upstream test styles.
If a UTF-8 byte order mark starts a config file, it should be ignored.
This also refactors config file loading a bit to reduce redundant code
and to make it possible to test loading config from a file.
Fixes#9490
If a UTF-8 byte order mark starts a config file, it should be ignored.
This also refactors config file loading a bit to reduce redundant code
and to make it possible to test loading config from a file.
Fixes#9490
Partially fixes#8493.
After dictating some texts, the icon still appears above, but it will
return to its right position after resizing or `\n` (saying newline, not
hitting enter).
This behaviour is better than before, where the icon always appeared
above.
> Quicklook doesn't seem to call this on Tahoe, but it still works well
anyway.
### Reference:
9e905357bb/src/nsterm.m (L7426)https://github.com/user-attachments/assets/6bf818c3-a0bb-412f-ae06-673f67cdeae4
This fixes the VS16 issues found in this test:
https://ucs-detect.readthedocs.io/sw_results/ghostty.html#ghostty
This is also a more robust way to handle VS15/16 in general.
This commit also changes our propeties to be a packed struct which
reduces its size from 4 bytes to 1 and likewise drops our unicode table
size 4x.
This updates uucode. As part of this, the wcwidth implementation was
updated (in uucode) to make emoji modifiers width ZERO. But if they're
standalone, we want them as width 2.
So this also contains a change to force them as width 2 for our width
calculation. This only matters for standalone emoji modifiers, because
when they form a valid grapheme we don't use this width calculation.
This updates uucode. As part of this, the wcwidth implementation was
updated (in uucode) to make emoji modifiers width ZERO. But if they're
standalone, we want them as width 2.
So this also contains a change to force them as width 2 for our width
calculation. This only matters for standalone emoji modifiers, because
when they form a valid grapheme we don't use this width calculation.
Partially fixes#8493.
After dictating some texts, the icon still appears above, but it will return to its right position after resizing or `\n` (saying newline, not hitting enter).
This behaviour is better than before, where the icon always appeared above.
### Reference:
9e905357bb/src/nsterm.m (L7426)
This adds the `UTF8_STRING` atom and explicit UTF-8 character set MIME
type (`text/plain;charset=utf-8`) to text content when being sent to the
clipboard under the new multipart support.
This fixes clipboard support under X11 particularly, which generally
looks for the `UTF8_STRING` atom when looking for text content. This can
be verified with `xclip -out -verbose`, or trying to do things like
paste in Firefox.
I've noted that there's a number of other older atoms that exist, but
I've refrained from adding them for now. Kitty only seems to set
`UTF8_STRING` and I've had a hard time finding consensus on what exactly
is the correct set otherwise.
This adds the UTF8_STRING atom and explicit UTF-8 character set MIME
type (text/plain;charset=utf-8) to text content when being sent to the
clipboard under the new multipart support.
This fixes clipboard support under X11 particularly, which generally
looks for the UTF8_STRING atom when looking for text content. This can be
verified with xclip -out -verbose, or trying to do things like paste in
Firefox.
I've noted that there's a number of other older atoms that exist, but
I've refrained from adding them for now. Kitty only seems to set
UTF8_STRING and I've had a hard time finding consensus on what exactly
is the correct set otherwise.
This supports the new `setClipboard` parameter that may provide data in
multiple formats, allowing us to copy rich text to/from the clipboard as
well as other types in the future.
This all fixes a memory leak on all clipboard ops that snuck through.
This supports the new `setClipboard` parameter that may provide data in
multiple formats, allowing us to copy rich text to/from the clipboard as
well as other types in the future.
Fixes#9426
Since we can't set the meta charset tag since we emit partial HTML, we
use codepoint entities like `{` for non-ASCII characters to ensure
proper rendering.
Fixes#9426
Since we can't set the meta charset tag since we emit partial HTML, we
use codepoint entities like `{` for non-ASCII characters to
ensure proper rendering.
Fixes#9420
The problem was ultimately that the padding calculations assumed that
the total vertical padding is always less than one cell height (just
doing `fmod(contentHeight, cellHeight)` instead of the more careful
`contentHeight - scrollbar.len * cellHeight`). This is not at all true,
and as a result, the calculated document height was often a cell height
short of what it should be.
For similar reasons, we shouldn't rely on `fmod`-based padding update
calculations during relayouting. This PR takes the proper approach of
saving and reusing the current scrollbar state to calculate the correct
document height on every layout. As a bonus, this removes the flickering
scrollbar during resize that I complained about in #9296.
Fixes#9397
This makes `copy_to_clipboard` take an optional parameter with the
format to copy. **The default has changed to `mixed`,** which will set
multiple content types on the clipboard allowing the OS or target
application to choose what they prefer. In this case, we set both
`text/plain` and `text/html`.
This only includes the macOS implementation. The GTK side still needs to
be done, but is likely trivial to do.
https://github.com/user-attachments/assets/b1b2f5cd-d59a-496e-bb77-86a60571ed7f
This moves the bg, fg, cursor color state into the `Terminal` struct,
including all default and override handling. I've rewritten our color
palette handling so that overrides, defaults, resets, etc are all
handled by the terminal package. I've added much more unit test
coverage.
This has various benefits:
* libghostty now handles OSC and Kitty color operations
* formatters can be aware of all of these colors (not implemented in
this PR)
* renderer has way less inter-thread messages
* renderer uses less memory
* termio stream handler uses less memory and becomes simpler
* override logic, changing defaults can all be unit tested
* we have unit tests for kitty color operations end-to-end now (cc
@jcollie heyo!)
There's a ton of risk on this PR too. There's a lot of really tiny
conditionals EVERYWHERE and I'm sure I got at least one wrong. We'll let
this bake in tip to find those, I'm sure they're minor and will show up
as just the wrong color somewhere.
**AI disclosure:** Amp wrote many of the tests. I did all the
implementation.
With its being `focusable`(default), the first responder became the text
editor instead of the paste button.
This fixes the issue where one can't confirm with the keyboard.
This doesn't affect its selection.
std.fs.makeDirAbsolute() only creates the last directory. We instead
need Dir.makePath() to make the entire path, including intermediate
directories.
This fixes the problem where a missing $XDG_STATE_HOME directory (e.g.
~/.local/state/) would prevent our ssh cache file from being created.
Fixes#9393
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.8.1 to 31.8.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.8.2</h2>
<h2>What's Changed</h2>
<ul>
<li>nix: 2.32.1 -> 2.32.2 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/259">cachix/install-nix-action#259</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31.8.1...v31.8.2">https://github.com/cachix/install-nix-action/compare/v31.8.1...v31.8.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="456688f15b"><code>456688f</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/259">#259</a>
from cachix/create-pull-request/patch</li>
<li><a
href="0cacfe0f2a"><code>0cacfe0</code></a>
nix: 2.32.1 -> 2.32.2</li>
<li>See full diff in <a
href="fd24c48048...456688f15b">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This adds HTML formatting capabilities to the formatter package. HTML is
emitted as inline styles. For palette indexes, direct RGB is emitted if
we have access to a palette; otherwise, we fall back to CSS variables.
This isn't exposed to end users yet, but will enable copy as html
features. This is available in libghostty.
Fixes#9395
**AI disclosure:** I used AI (Amp) to help me write tests, but the
implementation was done manually. I reviewed everything.
There have been frequent reports of key encoding issues in vim and tmux
with version 1.2.3 on macOS: #9340, #9361, #9401,
https://discord.com/channels/1005603569187160125/1432413679806320772.
I think I found the culprit: the option modifier is always passed as alt
to the core, regardless of `macos-option-as-alt`. Since #9289, this
means that a key event where option was used (as option) for translation
is encoded as if it also has the alt modifier.
For example, consider the many European keyboard layouts where option+8
sends `[`. If `macos-option-as-alt = true`, Ghostty correctly intercepts
the option and encodes option+8 as alt+8 instead (that is,
`^[[27;3;56~`). But if `macos-option-as-alt = false`, Ghostty first
allows option to be used for translation, obtaining `[`, and then
encodes the key event as alt+[ (that is, `^[[27;3;91~`), rather than
just `[`.
Tweaking the test case from #9289, here's a quick way to see this: set
`macos-option-as-alt = left`, run
```
printf '\033[>4;2m'
cat
```
choose a European keyboard layout (e.g., Norwegian), and hit both
left-option+8 and right-option+8. The former inserts `^[[27;3;56~` in
all well-behaved terminals. The latter inserts `[` in other terminals,
but `^[[27;3;91~` in Ghostty.
Basically, while modify other keys 2 does require encoding consumed
modifiers, the option key is not one of the supported modifiers, and
should not be included (as alt or anything else) when
`macos-option-as-alt = false`.
This PR removes alts that were actually options when using modify other
keys 2.
This removes all existing functionality that I know of that encodes a
terminal, screen, pagelist, or page as plaintext and unifies all logic
onto the formatter system.
This replaces the logic of Screen.selectionString with calls to
ScreenFormatter.
This means that all our various selection-based features like copying to
clipboards now uses the new formatter. The formatter code is now
user-facing.
This forced us to pass all selectionString tests which revealed some
edge cases that were not handled correctly before in the formatter! The
formatter now handles:
- Plain text now emits `\n` instead of `\r\n`. VT emits `\r\n`
- Rectangular selections
- Various wide character edge cases
- Selection is now inclusive on the end, not exclusive
This adds the option `pin_map` or `point_map` (for pages) to formatter,
allowing callers to get a byte-by-byte mapping for where on the screen
each encoding maps to. This is used by search internals and hyperlinks.
I haven't hooked that all up yet. This diff was big enough I wanted this
as one.
Tests significantly cover the new feature.
Next up, we'll rip out `selectionString` and replace it with formatters!
This adds a new formatter that can be used with standard Zig `{f}`
formatting that emits any portion of the terminal screen as VT
sequences. In addition to simply styling, this can emit the entire
terminal/screen state such as cursor positions, active style, terminal
modes, etc.
To do this, I've extracted all formatting to a dedicated `formatter`
package within `terminal`. This handles all formatting types (currently
plaintext and VT formatting, but can imagine things like HTML in the
future). Presently, we have "formatting" split out across a variety of
places in Terminal, Screen, PageList, and Page. I didn't remove this
code yet but I intend to unify it all on formatter in the future.
This also doesn't expose this functionality in any user-facing way yet.
This PR just adds it to the ghostty-vt Zig module and unit tests it.
Ghostty app changes will come later.
**This also improves the readonly stream** to handle OSC color
operations for _setting_ but it doesn't emit any responses of course,
since its readonly.
Fixes a bug described in #9291, where resizing an empty window causes
the scrollbar to appear even though the window remains larger than the
total content, because the relayouting fails to account for the change
in padding around the cell grid.
To reproduce the issue:
1. Enable legacy scrollbars (System Settings -> Appearance -> Show
scroll bars -> Always)
2. Open Ghostty
3. Vertically resize the window to make it smaller. The scrollbar will
pop up, and as you drag the window edge, it will cycle between a maximum
offset and zero depending on how far the resize is from an integer
multiple of the cell height.
With this PR you'll still see the scrollbar flicker while resizing, but
when you stop it will always disappear. Haven't figured out how to get
rid of the flickering yet. I tried to condition various updates on the
window not being in a live resize, but so far no luck.
contains() checks the cache for an existing entry. It's a read-only
operation, so we can drop the write bit and fixupPermissions() call.
This is also consistent with the list() operation.
fixupPermissions() is unnecessary in this code path. It provided minimal
additional security because all of our creation and update operations
enforce 0o600 (owner-only) permissions, so anyone tampering with this
file has already gotten around that. The contents of this (ssh host
cache) file are also not sensitive enough to warrant any additional
hardening on reads.
Bumps
[actions/download-artifact](https://github.com/actions/download-artifact)
from 5.0.0 to 6.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/download-artifact/releases">actions/download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<p><strong>BREAKING CHANGE:</strong> this update supports Node
<code>v24.x</code>. This is not a breaking change per-se but we're
treating it as such.</p>
<ul>
<li>Update README for download-artifact v5 changes by <a
href="https://github.com/yacaovsnc"><code>@yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/417">actions/download-artifact#417</a></li>
<li>Update README with artifact extraction details by <a
href="https://github.com/yacaovsnc"><code>@yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/424">actions/download-artifact#424</a></li>
<li>Readme: spell out the first use of GHES by <a
href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/431">actions/download-artifact#431</a></li>
<li>Bump <code>@actions/artifact</code> to <code>v4.0.0</code></li>
<li>Prepare <code>v6.0.0</code> by <a
href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/438">actions/download-artifact#438</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/danwkennedy"><code>@danwkennedy</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/431">actions/download-artifact#431</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v5...v6.0.0">https://github.com/actions/download-artifact/compare/v5...v6.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="018cc2cf5b"><code>018cc2c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/438">#438</a>
from actions/danwkennedy/prepare-6.0.0</li>
<li><a
href="815651c680"><code>815651c</code></a>
Revert "Remove <code>github.dep.yml</code>"</li>
<li><a
href="bb3a066a8b"><code>bb3a066</code></a>
Remove <code>github.dep.yml</code></li>
<li><a
href="fa1ce46bbd"><code>fa1ce46</code></a>
Prepare <code>v6.0.0</code></li>
<li><a
href="4a24838f3d"><code>4a24838</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/431">#431</a>
from danwkennedy/patch-1</li>
<li><a
href="5e3251c4ff"><code>5e3251c</code></a>
Readme: spell out the first use of GHES</li>
<li><a
href="abefc31eaf"><code>abefc31</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/424">#424</a>
from actions/yacaovsnc/update_readme</li>
<li><a
href="ac43a6070a"><code>ac43a60</code></a>
Update README with artifact extraction details</li>
<li><a
href="de96f4613b"><code>de96f46</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/417">#417</a>
from actions/yacaovsnc/update_readme</li>
<li><a
href="7993cb44e9"><code>7993cb4</code></a>
Remove migration guide for artifact download changes</li>
<li>Additional commits viewable in <a
href="634f93cb29...018cc2cf5b">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This exposes the SGR parser to the C and Wasm APIs. An example is shown
in c-vt-sgr.
Compressed example:
```c
#include <assert.h>
#include <stdio.h>
#include <ghostty/vt.h>
int main() {
// Create parser
GhosttySgrParser parser;
assert(ghostty_sgr_new(NULL, &parser) == GHOSTTY_SUCCESS);
// Parse: ESC[1;31m (bold + red foreground)
uint16_t params[] = {1, 31};
assert(ghostty_sgr_set_params(parser, params, NULL, 2) == GHOSTTY_SUCCESS);
printf("Parsing: ESC[1;31m\n\n");
// Iterate through attributes
GhosttySgrAttribute attr;
while (ghostty_sgr_next(parser, &attr)) {
switch (attr.tag) {
case GHOSTTY_SGR_ATTR_BOLD:
printf("✓ Bold enabled\n");
break;
case GHOSTTY_SGR_ATTR_FG_8:
printf("✓ Foreground color: %d (red)\n", attr.value.fg_8);
break;
default:
break;
}
}
ghostty_sgr_free(parser);
return 0;
}
```
**AI disclosure:** Amp wrote most of the C headers, but I verified it
all. https://ampcode.com/threads/T-d9f145cb-e6ef-48a8-ad63-e5fc85c0d43e
After `ghostty_app_update_config`, `ghostty_action_config_change_s` was
fired with the correct config. This happens synchronously, which will
update `App.config` in `App.configChange(_:target:v:)`.
Previously, after updating, `App.config` was set with the stale one,
which caused #8282.
Fixes a double-free bug when closing the inspector.
Not sure if there's a good way to add a test to check that allocation
and deallocation are sound here? To instantiate an Inspector you need a
Surface, which in turn requires an entire App/apprt. Doesn't look like
the repo has any tests with that kind of scope at the moment?
This adds a new stream handler implementation that updates terminal
state in reaction to VT sequences, but doesn't perform any of the
actions that would require responses (e.g. queries).
This is exposed in two ways: first, as a standalone `ReadonlyStream` and
`ReadonlyHandler` type that contains all the implementation. Second, as
a convenience func on `Terminal` as `vtStream` and `vtHandler` which
return their respective types preconfigured to update the calling
terminal state.
This dramatically simplifies libghostty-vt usage from Zig (and will
eventually be exposed to C, too) since a Terminal on its own is ready to
go as a full VT parser and state machine without needing to build any
custom types!
There's a second big bonus here which is that our `stream_readonly.zig`
tests are true end-to-end tests for raw bytes to terminal state. This
will let us test a wider variety of situations more broadly. To start,
there are only a handful of tests implemented here.
**AI disclosure:** Amp wrote basically this whole thing, but I reviewed
it. https://ampcode.com/threads/T-3490efd2-1137-4112-96f6-4bf8a0141ff5
### Background
> Reported from
https://discord.com/channels/1005603569187160125/1320882404717625374/1431258448439279709
Per current implementation, when `macos-titlebar-style` is `tabs` or
`transparent`, the titlebar's background is hidden to enable a blur
effect, but this could result in an incorrect appearance when the
window's appearance is different from the one based on the terminal's
background color. For instance, with the following configs:
```
window-theme = "light"
// theme will be default to Ghostty, which is dark
```
or
```
window-theme = "system"
// theme will be default to Ghostty, which is dark
// and system theme is set to light
```
### Changes
Update window theme based on the terminal's background color when using
`tabs` or `transparent`
This removes our `@hasDecl` usage from `terminal.Stream` and instead
uses a tagged union approach similar to what we already do for apprt
actions. The reasons to do this:
1. It is less magic. You don't get new functionality by magically
implementing a function.
2. It is safer. You can't typo a function name and Zig's exhaustive enum
handling will force you to handle all cases (even if most cases are
no-ops). This also helps you as at the implementor know when new
functionality pops up.
3. It is easier to integrate into C (for libghostty-vt). We can expose a
single tagged union type with a single callback rather than whatever the
previous mess was. This PR doesn't do this yet.
In addition, this PR adds in some helpers necessary to make it easier to
make C ABI compatible tagged unions. This lays the groundwork for our
libghostty-vt work but isn't exposed directly there yet. This PR has no
functional changes. Everything should behave identically as before.
I'm PRing this now because its already a huge diff, and I want to get
this in before I make more meaningful changes such as exposing some of
this to libghostty or adding a simpler Stream handler that maps to
terminal state for the Zig module and so on.
## Benchmarks
There's no meaningful impact on VT processing, I'd say all changes seen
below are noise:
<img width="2038" height="1392" alt="CleanShot 2025-10-25 at 07 10
04@2x"
src="https://github.com/user-attachments/assets/af6fa611-5b35-44d0-91ae-26955b1f980a"
/>
## One more `@hasDecl`
There is one more `hasDecl` remaining for `handleManually`. This is a
special case that's only used by our inspector. I think there is a
better way to do this but I didn't want to bloat this PR with anything
more! This doesn't impact our primary consumers of stream.
## AI Disclosure
I used AI considerably in handling the rote tasks in refactoring this. I
did the design myself manually but then prompted AI to help complete it
step by step. I did review each manually and understand it but I want to
take a careful review again...
Remove the semi-magic upper bound on the total cache key length. The
hostname and username validation routines will perform their own length
checks.
Also consolidate this function's tests. We previously had a few
redundant test cases.
Fixes#9322
SwiftUI `Text` has huge performance issues. On my maxed out MBP it hangs
for any text more than 100KB (it took ~8s to display it!). `TextEditor`
with a constant value works much better and handles scrolling for us,
too!
Fixes#9132
We were processing our window size defaults separate from our window
position and the result was that you'd get some incorrect behavior.
Unify the logic more to fix the positioning.
Note there is room to improve this further, I think that all initial
positioning could go into the controller completely. But I wanted to
minimize the diff for a backport.
This adds a set of Wasm convenience functions to ease memory management.
These are all prefixed with `ghostty_wasm` and are documented as part of
the standard Doxygen docs.
I also added a very simple single-page HTML example that demonstrates
how to use the Wasm module for key encoding.
This also adds a bunch of safety checks to the C API to verify that
valid values are actually passed to the function. This is an easy to hit
bug.
**AI disclosure:** The example is AI-written with Amp. I read through
all the code and understand it but I can't claim there isn't a better
way, I'm far from a JS expert. It is simple and works currently though.
Happy to see improvements if anyone wants to contribute.
This makes `libghostty-vt` build for freestanding wasm targets (aka a
browser) and produce a `ghostty-vt.wasm` file. This exports the same C
API that libghostty-vt does.
This commit specifically makes the changes necessary for the build to
build properly and for us to run the build in CI. We don't yet actually
try using it...
Fixes a crash when NSLocale returns nil for languageCode or countryCode
properties. This can happen when the app launches without locale
environment variables set.
The crash occurs at `src/os/locale.zig:87-88` when trying to call
`getProperty()` on a nil object. The fix adds a null check and falls
back to `en_US.UTF-8` instead of dereferencing null.
## Testing
Tested by running with locale variables unset:
```bash
unset LC_ALL && ./zig-out/Ghostty.app/Contents/MacOS/ghostty
```
Before: segmentation fault
After: launches successfully with fallback locale
Fixes#8900
Our xterm modify other keys state 2 encoding was stripped consumed mods
from the keyboard event. This doesn't match xterm or other popular
terminal emulators (but most importantly: xterm). Use the full set of
mods and add a test to verify this.
Reproduction:
```
printf '\033[>4;2m'
cat
```
Then press `ctrl+shift+h` and compare across terminals.
Closes#8430
A few questions:
* Should I set a default keybind for `toggle-mouse-reporting`? The issue
mentioned one, it's currently unset.
* Am I handling the `toggle-mouse-reporting` action properly in
`performAction` (gtk) / `action` (macos)?
Copilot was used to understand the codebase, but code was authored
manually.
This cleans up some of our termio exec code by unifying process launch
state into a single union type. This makes it easier to distinguish
between the current two mutually exclusive modes of launching a process:
fork/exec and flatpak dbus commands.
It also ensures everyplace we touch related to process launching is
forced to address every case (exhaustive switch handling). I did find
one resource cleanup bug based on this cleanup, which is also fixed
here. This just improves memory slightly so it's not a big deal.
If we add future ways to launch processes, we can add a new union case.
For example, I originally had a `posix_spawn` option while I was
experimenting with that before abandoning it (see #9274).
The host validation code previously expected IPv6 addresses to be
enclosed in [brackets], but that's not how ssh(1) expects them.
This change removes that requirement and reimplements the host
validation routine to check for valid hostnames and IP addresses (IPv4
and IPv6) using standard routines rather than custom logic.
Fixes#9251
Bumps
[flatpak/flatpak-github-actions](https://github.com/flatpak/flatpak-github-actions)
from 6.5 to 6.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/flatpak/flatpak-github-actions/releases">flatpak/flatpak-github-actions's
releases</a>.</em></p>
<blockquote>
<h2>v6.6</h2>
<ul>
<li>Specify full URL policy when mirroring screenshots</li>
<li>Fix restore cache keys to actually include arch</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="92ae9851ad"><code>92ae985</code></a>
Fix restore cache keys to actually include arch</li>
<li><a
href="b8a638469e"><code>b8a6384</code></a>
Specify full URL policy when mirroring screenshots</li>
<li><a
href="6684584b07"><code>6684584</code></a>
Switch funding to flatpak instead</li>
<li><a
href="e5aa88fb51"><code>e5aa88f</code></a>
readme: Update flathub images list</li>
<li><a
href="b93832bada"><code>b93832b</code></a>
Sync readme from flathub's fork</li>
<li>See full diff in <a
href="10a3c29f01...92ae9851ad">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
The host validation code previously expected IPv6 addresses to be
enclosed in [brackets], but that's not how ssh(1) expects them.
This change removes that requirement and reimplements the host
validation routine to check for valid hostnames and IP addresses (IPv4
and IPv6) using standard routines rather than custom logic.
std.net.isValidHostname is currently too generous. It considers strings
like ".example.com", "exa..mple.com", and "-example.com" to be valid
hostnames, which is incorrect according to RFC 1123 (the currently
accepted standard).
Until the standard library function is improved, we can use this local
implementation that does follow the RFC 1123 standard.
I asked Claude to perform an audit of the code based on its understand
of the RFC. It suggested some additional test cases and considers the
overall implementation to be robust (its words) and standards compliant.
Ref: https://www.rfc-editor.org/rfc/rfc1123
std.net.isValidHostname is currently too generous. It considers strings
like ".example.com", "exa..mple.com", and "-example.com" to be valid
hostnames, which is incorrect according to RFC 1123 (the currently
accepted standard).
Until the standard library function is improved, we can use this local
implementation that does follow the RFC 1123 standard.
I asked Claude to perform an audit of the code based on its understand
of the RFC. It suggested some additional test cases and considers the
overall implementation to be robust (its words) and standards compliant.
Ref: https://www.rfc-editor.org/rfc/rfc1123
`fish_add_path` by default updates the `fish_user_paths` universal
variable which makes the modification persist across shell sessions.
The integration also tries to update the `fish_user_paths` when the
desired path already appears in the `PATH` environment variable but not
in `fish_user_paths`. Because `fish_user_paths` will always be inserted
before the inherited `PATH` env. This makes the added path
unintentionally has a higher priority.
This patch fixes the above issues by adding `--global` and `--path`
options to `fish_user_paths` which limits the modification scope and
ensures that the path won't be added if it already exists in `PATH`.
Ref: https://fishshell.com/docs/current/cmds/fish_add_path.html
`fish_add_path` by default updates the `fish_user_paths` universal
variable which makes the modification persist across shell sessions.
The integration also tries to update the `fish_user_paths` when the
desired path already appears in the `PATH` environment variable but
not in `fish_user_paths`. Because `fish_user_paths` will always be
inserted before the inherited `PATH` env. This makes the added path
unintentionally has a higher priority.
This patch makes the above issues by adding `--global` and `--path`
options to `fish_user_paths` which limits the modification scope and
ensures that the path won't be added if it already exists in `PATH`.
This fixes the source of a deadlock that some tip users have hit. If our
surface mailbox is full and there is a dirty scrollbar state, then
drawFrame would block forever trying to queue to the surface mailbox.
We now fail instantly if the queue is full and keep the scrollbar state
dirty. We can try again on the next frame, it's not a critical thing to
get updated.
This fixes the source of a deadlock that some tip users have hit. If our
surface mailbox is full and there is a dirty scrollbar state, then
drawFrame would block forever trying to queue to the surface mailbox.
We now fail instantly if the queue is full and keep the scrollbar state
dirty. We can try again on the next frame, it's not a critical thing to
get updated.
NSScreen instances can be garbage collected at any time, even for
screens that remain connected, making NSMapTable with weak keys
unreliable for tracking per-screen state.
This changes the quick terminal to use CGDisplay UUIDs as stable
identifiers, keyed in a strong dictionary. Each entry stores the window
frame along with screen dimensions, scale factor, and last-seen
timestamp.
**This should make quick terminal size restore more stable than 1.2.2.**
Rules for pruning:
- Entries are invalidated when screens shrink or change scale
- Entries persist and update when screens grow (allowing cached state to
work with larger resolutions)
- Stale entries for disconnected screens expire after 14 days.
- Maximum of 10 screen entries to prevent unbounded growth
NSScreen instances can be garbage collected at any time, even for
screens that remain connected, making NSMapTable with weak keys
unreliable for tracking per-screen state.
This changes the quick terminal to use CGDisplay UUIDs as stable
identifiers, keyed in a strong dictionary. Each entry stores the
window frame along with screen dimensions, scale factor, and last-seen
timestamp.
Rules for pruning:
- Entries are invalidated when screens shrink or change scale
- Entries persist and update when screens grow (allowing cached state
to work with larger resolutions)
- Stale entries for disconnected screens expire after 14 days.
- Maximum of 10 screen entries to prevent unbounded growth
When the preferred scrollbar style is "legacy", the scrollbar takes up
space that offsets the actual terminal. To prevent reflow, we detect
this before the scrollbar becomes visible and shrink our terminal width
to prepare for it.
This doesn't account for the style changing at runtime, yet.
This changes the default FreeType load target to `FT_LOAD_TARGET_LIGHT`,
giving the hinter a lighter touch in line with the default behavior in
most other GTK apps, and adds a load flag such that the old hinting
behavior can be restored via config.
As discussed in
https://github.com/ghostty-org/ghostty/issues/8674#issuecomment-3417082534.
However, this doesn't close that issue, as it still doesn't respect
custom Fontconfig settings. It's just a stopgap solution bringing
Ghostty's defaults more in line with the typical results of not
customizing Fontconfig.
Fixes#2731 (again)
This regressed in 1.2 due to the renderer rework missing porting this. I
believe this issue is still valid even with the rework since the font
grid changes the atlas and if there are still cached cells that
reference the old atlas coordinates it will produce garbage.
Fixes#2731 (again)
This regressed in 1.2 due to the renderer rework missing porting this. I
believe this issue is still valid even with the rework since the font
grid changes the atlas and if there are still cached cells that
reference the old atlas coordinates it will produce garbage.
Fixes#111
This builds on the prior work and adds scrollbars to the GTK
application. After this PR, both the macOS and GTK applications support
scrollbars and #111 can be closed.
## TODO
- [x] Verify that with text coming out if we manually scroll to the
bottom we bind to the bottom
- [x] Valgrind checks
Fixes#9233. The issue was that the content view was effectively
truncated by `SurfaceScrollView`s safe area insets covering the titlebar
(I suppose the scroll view doesn't inherit the `ignoresSafeArea`
modifier on `TerminalSplitTreeView` because it's not actually a node in
that tree, it just contains a subview that is). I think it's OK to zero
these insets unconditionally, as I don't think the frame size passed
down from `SurfaceWrapper` will ever overlap with a visible titlebar.
This PR also fixes a somewhat related issue I discovered along the way.
In many cases, the mouse wouldn't work on the first row of text, whether
trying to select text or interact with TUI elements like neovim
bufferlines/tablines. When looking at the inspector while moving the
mouse around, you'd see the mouse position jump discontinuously from a
y-coordinate of 10-20 pixels to -3, i.e., no longer within the surface.
The problem turned out to be that the height of the document view only
included the text lines, not the window padding, while the content view
is sized to the surface including padding. If you imagine the
metaphorical sliding of the viewport up and down the document, and you
require the text to snap to a fixed grid on the viewport, it's clear
that the document must have the same top and bottom padding as the
viewport, otherwise the math breaks at the ends. Sure enough, adding
this padding fixed the problem.
To properly reproduce these issues and see the effect of the fixes, it's
helpful to change the system settings to always show scroll bars
(Appearance -> Show scroll bars -> Always). That's what connecting an
external mouse was all about. You should also remove the debug banner or
use a release build, otherwise the banner takes the place of the hidden
titlebar and conceals the issue.
Completes #111 for macOS
This builds on #9225 and adds the native, macOS GUI scrollbars for
terminals.
This doesn't do GTK, but all the groundwork is there to make this easy
on GTK, depending on how scrollviews work there. I have to look into
that still. But in theory all the information and controls we provide
out of core are generic to _any_ scrollbar drawing.
## Demo
https://github.com/user-attachments/assets/85683bf9-1117-4f32-aaec-d926edd68c39
## Details
- The scrollbars respect your macOS system settings on style, color,
visibility.
- A new configuration `scrollbar` controls whether scrollbars are
visible (defaults to `system` allowing the system to choose).
- There is a new keybind action `scroll_to_row:N` that lets you jump to
an absolute row number N. This is implemented efficiently. This is how
grabbing the knob and scrolling works.
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.8.0 to 31.8.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.8.1</h2>
<h2>What's Changed</h2>
<ul>
<li>nix: 2.32.0 -> 2.32.1 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/258">cachix/install-nix-action#258</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31...v31.8.1">https://github.com/cachix/install-nix-action/compare/v31...v31.8.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fd24c48048"><code>fd24c48</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/258">#258</a>
from cachix/create-pull-request/patch</li>
<li><a
href="a55fd2d847"><code>a55fd2d</code></a>
nix: 2.32.0 -> 2.32.1</li>
<li>See full diff in <a
href="7ab6e7fd29...fd24c48048">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps
[namespacelabs/nscloud-setup-buildx-action](https://github.com/namespacelabs/nscloud-setup-buildx-action)
from 0.0.19 to 0.0.20.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/namespacelabs/nscloud-setup-buildx-action/releases">namespacelabs/nscloud-setup-buildx-action's
releases</a>.</em></p>
<blockquote>
<h2>v0.0.20</h2>
<h2>What's Changed</h2>
<ul>
<li>Add wait-for-builder input to eagerly start build clusters and wait
for them by <a
href="https://github.com/annervisser"><code>@annervisser</code></a> in
<a
href="https://redirect.github.com/namespacelabs/nscloud-setup-buildx-action/pull/11">namespacelabs/nscloud-setup-buildx-action#11</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/annervisser"><code>@annervisser</code></a>
made their first contribution in <a
href="https://redirect.github.com/namespacelabs/nscloud-setup-buildx-action/pull/11">namespacelabs/nscloud-setup-buildx-action#11</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/namespacelabs/nscloud-setup-buildx-action/compare/v0...v0.0.20">https://github.com/namespacelabs/nscloud-setup-buildx-action/compare/v0...v0.0.20</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="91c2e65377"><code>91c2e65</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-setup-buildx-action/issues/11">#11</a>
from namespacelabs/add-wait-for-builder-input-to-eager...</li>
<li><a
href="459dd43ae1"><code>459dd43</code></a>
Add wait-for-builder input to eagerly start build clusters and wait for
them</li>
<li>See full diff in <a
href="7020d7d8e6...91c2e65377">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Related to #111
This adds the necessary logic and data for the `PageList` data structure
to keep track of **total length** of the screen, **offset** into the
viewport, and **length** of the viewport. These three values are
necessary to _render_ a scrollbar. This PR updates the renderer to grab
this information but stops short of actually drawing a scrollbar (which
we'll do with native UI), in the interest of having a PR that doesn't
contain too many changes.
**This doesn't yet draw a scrollbar, these are just the internal changes
necessary to support it.**
## Background
The `PageList` structure is very core to how we represent terminal
state. It maintains a doubly linked list of "pages" (not literally
virtual memory pages, but close). Each page stores cell information,
styles, hyperlinks, etc fully self-contained in a contiguous sets of VM
pages using offset addresses rather than full pointers. **Pages are not
guaranteed to be equal sizes.** (This is where scrollbars get difficult)
Because it is a linked list structure of non-equal sized nodes, it isn't
amenable to typical scrollbar behavior. A scrollbar needs to know: full
size, offset, and length in order to draw the scrollbar properly.
Getting these values naively is `O(N)` within the data structure that is
on the hottest IO performance path in all of Ghostty.
## Implementation
### PageList
We now maintain two cached values for **total length** and **viewport
offset**.
The total length is relatively straightforward, we just have to be
careful to update it in every operation that could add or remove rows.
I've done this and ensured that every place we update it is covered with
unit test coverage.
The viewport offset is nasty, but I came up with what I believe is a
good solution. The viewport when arbitrarily scrolled is defined as a
direct pointer to the linked list node plus a row offset into that node.
The only way to calculate offset from the top is `O(N)`.
But we have a couple shortcuts:
1. If the viewport is at the bottom (most common) or top, calculating
the offset is `O(1)`: bottom is `total_rows - active_rows`, both readily
available. And top is `0` by definition.
2. Operations on the PageList typically add or remove rows. We don't do
arbitrary linked list surgery. If we instrument those areas with delta
updates to our cache, we can avoid the `O(N)` cost for most operations,
including scrolling a scrollbar. The only expensive operation is a full,
arbitrary jump (new node pointer).
Point 1 was quick to implement, so I focused all the complexity on point
2. Whenever we have an operation that adds or removes rows (for example
pruning the scroll back, adding more, erase rows within the active area,
etc.) then I do the math to calculate the delta change required for the
offset if we've already calculated it, and apply that directly.
### Renderer
The other issue was how to notify the apprts of scrollbar state. Sending
messages on any terminal change within the IO thread is a non-option
because (1) sending messages is slow (2) the terminal changes a lot and
(3) any slowness in the IO thread slows down overall terminal
throughput.
The solution was to **trigger scrollbar notifications with the renderer
vsync**. We read the scrollbar information when we render a frame,
compare it to renderer previous state, and if the scrollbar changed,
send a message to the apprt _after the frame is GPU-renderer_.
The renderer spends _most_ of its time sleeping compared to the IO
thread, and has more opportunities for optimizing its awake time.
Additionally, there's no reason to update the scrollbar information if
the renderer hasn't rendered the new frames because the user can't even
see the stuff the scrollbar wants to scroll to. We're talking about
millisecond scale stuff here at worst but it adds up.
## Performance
No noticeable performance impact for the additional metrics:
<img width="1012" height="738" alt="image"
src="https://github.com/user-attachments/assets/4ed0a3e8-6d76-40c1-b249-e34041c2f6fd"
/>
## AI Usage
I used Amp to help audit the codebase and write tests. I wrote all the
main implementation code manually. I came up with the main design
myself. Relevant threads:
-
https://ampcode.com/threads/T-95fff686-75bb-4553-a2fb-e41fe4cd4b77#message-0-block-0
-
https://ampcode.com/threads/T-48e9a288-b280-4eec-83b7-ca73d029b4ef#message-91-block-0
## Future
This is just the internal changes necessary to _draw_ a scrollbar. There
will be other changes we'll need to add to handle grabbing and actually
jumping the scrollbar. I have a good idea of how to implement those
performantly as well.
Fixes#9191
This changes our color change operations from writing to the renderer
mailbox directly to using our `rendererMailboxWriter` function which
handles the scenario where the mailbox is full by yielding the lock,
waking up the renderer, and retrying later.
This is a known deadlock scenario we've worked around since the private
beta days, but unfortunately this slipped through and I didn't catch it
in review.
What happens here is it's possible with certain escape sequences for the
IO thread to saturate other mailboxes with messages while holding the
terminal state lock. This can happen to any thread. This ultimately
leads to starvation and all threads deadlock.
Our IO thread is the only thread that produces this kind of massive
stream of events while holding the lock, so we have helpers in it to
attempt to queue (cheap, fast) and if that fails then to yield the lock,
wakeup the target thread, requeue, and grab the lock again (expensive,
slow).
This hasn't caused any known bugs but leads to selection memory
corruption and assertion failures in runtime safe modes. When the
terminal screen changes (primary to secondary) and we have an active
dragging mode going either by moving the mouse or our selection tick
timer, we should halt.
We still keep the mouse state active which lets selection continue once
the screen switches back.
- update nixpkgs now that Zig 0.15.2 is available in nixpkgs
- drop hack that worked around compile failures on systems with more
than 32 cores
- enforce patch version of Zig
This reimplements the MAC address-aware URI parsing logic used by the
OSC 7 handler and adds an additional .raw_path option that returns the
full, unencoded path string (including query and fragment values), which
is needed for compliant kitty-shell-cwd:// handling.
Notably, this implementation takes an options-based approach that allows
these additional behaviors to be enabled at runtime. It also leverages
two std.Uri.parse guarantees:
1. Return slices point into the original text string.
2. .raw components don't require unescaping (.percent_encoded does).
The implementation is in a new 'os.uri' module because its now generic
enough to not be hostname-oriented.
We use os.uri.parseUri and its parsing options to reimplement our OSC 7
file-style URI handling. This has two advantages:
First, it fixes kitty-shell-cwd scheme handling. This scheme expects the
full, unencoded path string, whereas the file scheme expects normal URI
percent encoding. This was preventing paths containing "special" URI
characters (like "path?") from working correctly in our bash, zsh, and
elvish shell integrations, which report working directories using the
kitty-shell-cwd scheme. (fish uses file URIs, which work as expected.)
Second, we can greatly simplify our hostname and path string handling
because we can now rely on the "raw" std.Uri component form to always
provide the correct representation.
Lastly, this lets us remove the previous URI-related code from the
os.hostname module, restoring its focus to hostname-related functions.
See: #5289
This mainly allows users who have a pending update but didn't install it
for some time to re-check to see if there is something newer in the mean
time.
This might fix#9191, but since I don't have a reproduction I can't be
sure. In any case, this is a bad bug that should be fixed.
The issue is that we weren't checking our scroll timer completion state.
This meant that if `start_scroll_timer` was called multiple times within
a single loop tick, we'd enqueue our completion multiple times, leading
to various undefined behaviors.
If we don't enqueue anything else in the interim, this is safe by
chance. But if we enqueue something else, then we'd hit a queue
assertion failure and honestly I'm not totally sure what would happen.
I wasn't able to trigger the "bad" case, but I was able to trigger the
benign case very easily. Our other timers such as the renderer cursor
timer already have this protection.
Let's fix this and continue looking...
Bumps
[namespacelabs/nscloud-setup-buildx-action](https://github.com/namespacelabs/nscloud-setup-buildx-action)
from 0.0.18 to 0.0.19.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7020d7d8e6"><code>7020d7d</code></a>
support experimental flags in setup-buildx action. (<a
href="https://redirect.github.com/namespacelabs/nscloud-setup-buildx-action/issues/10">#10</a>)</li>
<li><a
href="361d7cbc2b"><code>361d7cb</code></a>
Fix error parsing.</li>
<li><a
href="4271c3f47a"><code>4271c3f</code></a>
Robust status checking.</li>
<li><a
href="77ccfe1b7f"><code>77ccfe1</code></a>
Print exec outputs.</li>
<li><a
href="5b194a9c8d"><code>5b194a9</code></a>
Update README</li>
<li><a
href="4db0d391b6"><code>4db0d39</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-setup-buildx-action/issues/9">#9</a>
from namespacelabs/n-g-patch-1</li>
<li><a
href="feb137b9d0"><code>feb137b</code></a>
Update README.md</li>
<li><a
href="f57c281382"><code>f57c281</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-setup-buildx-action/issues/8">#8</a>
from namespacelabs/pavol/docker_buildx_ls</li>
<li><a
href="4177f4c963"><code>4177f4c</code></a>
e2e: run docker buildx ls</li>
<li>See full diff in <a
href="01628ae51e...7020d7d8e6">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps
[softprops/action-gh-release](https://github.com/softprops/action-gh-release)
from 2.4.0 to 2.4.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/releases">softprops/action-gh-release's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.1</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<h3>Other Changes 🔄</h3>
<ul>
<li>fix(util): support brace expansion globs containing commas in
parseInputFiles by <a
href="https://github.com/Copilot"><code>@Copilot</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/672">softprops/action-gh-release#672</a></li>
<li>fix: gracefully fallback to body when body_path cannot be read by <a
href="https://github.com/Copilot"><code>@Copilot</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/671">softprops/action-gh-release#671</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/softprops/action-gh-release/compare/v2...v2.4.1">https://github.com/softprops/action-gh-release/compare/v2...v2.4.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md">softprops/action-gh-release's
changelog</a>.</em></p>
<blockquote>
<h2>2.4.1</h2>
<h2>What's Changed</h2>
<h3>Other Changes 🔄</h3>
<ul>
<li>fix(util): support brace expansion globs containing commas in
parseInputFiles by <a
href="https://github.com/Copilot"><code>@Copilot</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/672">softprops/action-gh-release#672</a></li>
<li>fix: gracefully fallback to body when body_path cannot be read by <a
href="https://github.com/Copilot"><code>@Copilot</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/671">softprops/action-gh-release#671</a></li>
</ul>
<h2>2.4.0</h2>
<h2>What's Changed</h2>
<h3>Exciting New Features 🎉</h3>
<ul>
<li>feat(action): respect working_directory for files globs by <a
href="https://github.com/stephenway"><code>@stephenway</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/667">softprops/action-gh-release#667</a></li>
</ul>
<h2>2.3.4</h2>
<h2>What's Changed</h2>
<h3>Bug fixes 🐛</h3>
<ul>
<li>fix(action): handle 422 already_exists race condition by <a
href="https://github.com/stephenway"><code>@stephenway</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/665">softprops/action-gh-release#665</a></li>
</ul>
<h3>Other Changes 🔄</h3>
<ul>
<li>dependency updates</li>
</ul>
<h2>2.3.3</h2>
<h2>What's Changed</h2>
<h3>Exciting New Features 🎉</h3>
<ul>
<li>feat: add input option <code>overwrite_files</code> by <a
href="https://github.com/asfernandes"><code>@asfernandes</code></a> in
<a
href="https://redirect.github.com/softprops/action-gh-release/pull/343">softprops/action-gh-release#343</a></li>
</ul>
<h3>Other Changes 🔄</h3>
<ul>
<li>dependency updates</li>
</ul>
<h2>2.3.2</h2>
<ul>
<li>fix: revert fs <code>readableWebStream</code> change</li>
</ul>
<h2>2.3.1</h2>
<h3>Bug fixes 🐛</h3>
<ul>
<li>fix: fix file closing issue by <a
href="https://github.com/WailGree"><code>@WailGree</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/629">softprops/action-gh-release#629</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="6da8fa9354"><code>6da8fa9</code></a>
release 2.4.1</li>
<li><a
href="f38efdea4c"><code>f38efde</code></a>
fix: gracefully fallback to body when body_path cannot be read (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/671">#671</a>)</li>
<li><a
href="cec1a1113b"><code>cec1a11</code></a>
fix(util): support brace expansion globs containing commas in
parseInputFiles...</li>
<li>See full diff in <a
href="aec2ec56f9...6da8fa9354">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.7.0 to 31.8.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.8.0</h2>
<h2>What's Changed</h2>
<ul>
<li>nix: 2.31.2 -> 2.32.0 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/257">cachix/install-nix-action#257</a>
Release notes: <a
href="https://discourse.nixos.org/t/nix-2-32-0-released/70528">https://discourse.nixos.org/t/nix-2-32-0-released/70528</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31.7.0...v31.8.0">https://github.com/cachix/install-nix-action/compare/v31.7.0...v31.8.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7ab6e7fd29"><code>7ab6e7f</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/257">#257</a>
from cachix/create-pull-request/patch</li>
<li><a
href="a851831538"><code>a851831</code></a>
nix: 2.31.2 -> 2.32.0</li>
<li><a
href="0b2de19be5"><code>0b2de19</code></a>
docs: update the ci badge</li>
<li><a
href="b8a94d3614"><code>b8a94d3</code></a>
ci: pass correct args to the act test</li>
<li><a
href="0ef05056da"><code>0ef0505</code></a>
ci: adjust oldest supported version for macos-15</li>
<li><a
href="0b43574e96"><code>0b43574</code></a>
ci: add macos-15-intel runner</li>
<li>See full diff in <a
href="9280e7aca8...7ab6e7fd29">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Update libvaxis. The latest commit of libvaxis includes `uucode` as the
unicode
library. `uucode` has a much cleaner API and is actively developed by a
Ghostty
maintainer (@jacobsandlund). This also has the advantage of removing the
last
transitive dependency Ghostty has that is hosted on codeberg, which
separates us
from the frequent outages.
Disclosures: I used AI to debug the import issue in `boo.zig`
---------
Co-authored-by: Mitchell Hashimoto <m@mitchellh.com>
Most of the changes seem to be Tahoe UI related and now that we have a
custom UI I don't think there is anything important here but we should
update nonetheless.
This includes multiple changes to clean up the "installing" state:
- Ghostty will not confirm quit, since the user has already confirmed
they want to restart to install the update.
- If termination fails for any reason, the popover has a button to retry
restarting.
- The copy and badge symbol have been updated to better match the
reality of the "installing" state.
<img width="1756" height="890" alt="CleanShot 2025-10-12 at 15 04 08@2x"
src="https://github.com/user-attachments/assets/1b769518-e15f-4758-be3b-c45163fa2603"
/>
AI written:
https://ampcode.com/threads/T-623d1030-419f-413f-a285-e79c86a4246b fully
understood.
Resolves#8689
For various reason, ghostty wants to have a unique file extension for
the config files. The name was settled on `config.ghostty`. This will
help with tooling. See #8438 (original discussion) for more details.
This PR introduces the preferred default of `.ghostty` while still
supporting the previous `config` file. If both files exist, a warning
log is sent.
The docs / website will need to be updated to reflect this change.
> [!NOTE]
> Only tested on macOS 26.0.
---------
Co-authored-by: Mitchell Hashimoto <m@mitchellh.com>
### Background
Been running Ghostty locally for a while now, and I use the Finder
service a lot. It often confuses me which one is the official one, until
I actually open it.
### Changes
- Use blueprint to distinguish from release app, if no custom icon
specified
- Change BundleDisplayName to Ghostty[Debug]
- Enable Info.plist preprocessing for reading
`$(INFOPLIST_KEY_CFBundleDisplayName)` for providing different services
with different configurations
> (Preprocessing was once reverted
before](https://github.com/ghostty-org/ghostty/commit/6508fec), so I'm
not sure whether this follows the 'rules' here, but for now, there are
no links in the plist file, so I think it’s
[safe](https://developer.apple.com/library/archive/technotes/tn2175/_index.html#:~:text=can%20pass%20the-,%2Dtraditional,-flag%20to%20the)
to enable it
Previously, the fish shell integration interfered with fish's builtin vi
mode cursor switching configurations such as `$fish_cursor_default` and
`$fish_cursor_insert`.
```console
$ ghostty --config-default-files=false -e fish --no-config --init-command 'source "$GHOSTTY_RESOURCES_DIR"/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish; fish_vi_key_bindings'
```
The above command starts fish in vi mode with Ghostty shell
integrations. Manually loading the integration is necessary due to
`--no-config` blocking auto injection.
1. At the prompt, fish is in insert mode, and the cursor is a blinking
beam. However, press escape and then "i" to exit then re-enter insert
mode, and the cursor will be a solid beam due to the
`$fish_cursor_unknown` setting. Without the shell integration, insert
mode always uses a solid beam cursor.
2. A similar problem shows if we start fish with `fish_vi_key_bindings
default`. The cursor ends up as a blinking beam in normal mode only due
to the shell integration interfering. This glitch can also be reset away
by entering then exiting insert mode.
3. Also, `$fish_cursor_external` has no effect when used with shell
integration. After `fish_vi_key_bindings`, set it to `line`, run cat(1),
and shell integration will give you a blinking block, not the asked for
line/beam.
I verified that this patch makes the shell integration stop interfering
in three scenarios above, and it still changes the cursor when not using
fish's vi mode.
Note that `$fish_cursor_*` variables can be set when fish isn't in vi
mode, so they're not great signals for the shell integration hooks.
As pointed out in #9156, an unintended consequence of all the work to
get icon sizing right is that `adjust-icon-height` now only applies to
the small icons you get when the next cell is not whitespace. Large
icons are unaffected.
With this PR, `adjust-icon-height` affects the maximum height of every
symbol specifying the `.icon` constraint height, regardless of
constraint width. This includes most Nerd Font icons, but excludes emoji
and other unicode symbols, and also excludes terminal graphics-oriented
Nerd Font symbols such as Powerline symbols.
In the following screenshots, **Baseline** is without
`adjust-icon-height`, while **Before** and **After** are with
`adjust-icon-height = -25%`.
**Baseline**
<img width="711" height="95" alt="Screenshot 2025-10-11 at 23 28 20"
src="https://github.com/user-attachments/assets/7499db4d-75a4-4dbd-b107-8cb5849e31a3"
/>
**Before** (only small icons affected)
<img width="711" height="95" alt="Screenshot 2025-10-11 at 23 20 12"
src="https://github.com/user-attachments/assets/9afd9fbf-ef25-44cc-9d8e-c39a69875163"
/>
**After** (both small and large icons affected, but not emoji)
<img width="711" height="95" alt="Screenshot 2025-10-11 at 23 21 05"
src="https://github.com/user-attachments/assets/90999f59-3b43-4684-9c8e-2c3c1edd6d18"
/>
### This PR depends on #9162#1691 doesn't seem to be an issue anymore, but changing appearance while
Ghosty is active will result in similar nastiness.
https://github.com/user-attachments/assets/fcd7761e-a521-4382-8d7a-9d93dc0806bc
### Changes
- [Sequoia/Ventura] Fix flickering new tab icon, and it also didn't
respond to window's key status change correctly
- [Sequoia/Ventura] Fix after changing appearance, tab bar may disappear
or have inconsistent background colour
- Fix initial tint of reset zoom button on Sequoia/Ventura with
`macos-titlebar-style=tabs` and all `native/transparent` titlebars
- Fix title alignment with custom font with `native/transparent`
titlebar
Of course #9142 would require a minor follow-up!
* Scale groups can cut across patch sets, but not across fonts. We had
some scale group mixing between Font Awesome and the weather symbols,
which is removed by this PR.[^cp_table_full]
* There's one case where a scale group includes a glyph that's not part
of any patch sets, just for padding out the group bounding box.
Previously, an unrelated glyph from a different font would be pulled in.
Now we use an appropriate stand-in. (See code comment for details.)
* I noticed overlaps weren't being split between each side of the
bounding box, they were added to both sides, resulting in twice as much
padding as specified.
Screenshots showing the extra vertical padding for progress bar elements
due to the second bullet point:
**Before**
<img width="191" height="42" alt="Screenshot 2025-10-11 at 15 33 54"
src="https://github.com/user-attachments/assets/cf288cce-86d3-46fd-ae86-18e5c274b0e4"
/>
**After**
<img width="191" height="42" alt="Screenshot 2025-10-11 at 15 33 20"
src="https://github.com/user-attachments/assets/7ac799c7-bf50-4e65-a74a-f8a2c42d2441"
/>
[^cp_table_full]: Forming and using the merged `cp_table_full` table
should have been a red flag. Such a table doesn't make sense, it would
be a one-to-many map. You need the names of the original fonts to
disambiguate.
Fixes#9076
**Before**
<img width="128" height="57" alt="Screenshot 2025-10-11 at 00 07 09"
src="https://github.com/user-attachments/assets/a6b416d5-dae1-4cea-a836-00640ceaf39b"
/>
**After**
<img width="128" height="57" alt="Screenshot 2025-10-11 at 00 07 31"
src="https://github.com/user-attachments/assets/7d2df7b1-4767-4e2d-84d2-8301da5c6602"
/>
These screenshots show the chevrons mentioned in
https://github.com/ghostty-org/ghostty/discussions/7820#discussioncomment-14617170,
which should be scaled as a group but were not until this PR.
The added code downloads each individual symbol font file from the Nerd
Fonts github repo (making sure to get the version corresponding to the
vendored `font-patcher.py`) and iterates over all of them to build the
correct and complete codepoint mapping table. The table is saved to
`nerd_font_codepoint_tables.py`, which `nerd_font_codegen.py` will reuse
if possible instead of downloading the font files again.
I'm not going to utter any famous last words or anything, but... after
this, I don't think the number of remaining issues with icon
scaling/alignment is _large._
Clicking on desktop notifications sent by Ghostty _should_ cause the
window that sent the notification to come to the top. However, because
the notification that was sent targeted the wrong surface (apprt surface
vs core surface) and the window did not call `present()` on itself the
window would never be brought to the surface, the correct tab would not
be selected, etc.
Fixes#9145
Log messages will include the problematic CSS, simplifying debugging.
Especially helpful since some of our CSS is generated at runtime so it
could be difficult to examine the CSS "source".
```
info(gtk_ghostty_application): loading gtk-custom-css path=/home/ghostty/dev/ghostty/x.css
warning(gtk_ghostty_application): css parsing failed at <data>:2:3-14: gtk-css-parser-error-quark 4 No property named "border-poop"
* {
border-poop: 0;
warning(gtk_ghostty_application): css parsing failed at <data>:1:3-3:1: gtk-css-parser-warning-quark 1 Unterminated block at end of document
* {
border-poop: 0;
```
vs:
```
info(gtk_ghostty_application): loading gtk-custom-css path=/home/ghostty/dev/ghostty/x.css
warning(glib): WARNING: Gtk: Theme parser error: <data>:2:3-14: No property named "border-poop"
warning(glib): WARNING: Gtk: Theme parser warning: <data>:1:3-3:1: Unterminated block at end of document
```
I want to see #7932 get merged, so applied the latest proposed patch.
Will close if the original PR gets some traction, as I do _not_ know Zig
nor this project.
Co-authored-by: rhodes-b <59537185+rhodes-b@users.noreply.github.com>
If an update is available, you can now trigger the full download,
install, and restart from a single command palette action. This allows
for a fully keyboard-driven update process.
While an update is being installed, an option to cancel or skip the
current update is also shown as an option, so that can also be
keyboard-driven.
This currently can't be bound to a keyboard action, but that may be
added in the future if there's demand for it.
**AI Disclosure:** Amp was used considerably. I reviewed all the code
and understand it.
## Demo
https://github.com/user-attachments/assets/df6307f8-9967-40d4-9a62-04feddf00ac2
Sorry, I'm living dangerously here and haven't started a discussion.
New ghostty user. When working interactively with SQL clients you're
often writing semi-colons at the end of statements, e.g. `select * from
table;`
It's super annoying when you double-click to select the word `table` it
actually selects `table;` Anecdotally, this behaviour disagrees with
other terminals I've tried (tho not exhaustive).
Disclosure: Claude wrote this code but, ironically, I "assisted it" by
pointing to the file and function after uncovering issue #30 and
relevant PR.
Fixes#2638Fixes#9056
This changes our update check and notification system to be fully
unobtrusive if a terminal window is open. If a terminal window is open,
update system notifications now appear in the titlebar or the bottom
right corner of the window (if the titlebar is unavailable for any
reason). Importantly, this means no more window popups!
If a terminal window is not open, then explicit update checks will open
the existing, standard, dedicated window that currently exists. This
only triggers for manual update checks while the window is not open,
though. Or at least, that is the intention, I'm not sure if I got all
the logic there 100% correct.
**AI disclosure:** I used Amp considerably on the path to this fix.
Sorry, I wanted to use Codex due to the source, but I wanted to get this
fix out quickly and used a tool I was more familiar with. I manually
modified most of the code and understand it all.
## Demo
Update flows are complex and can do many things, so I built a simulator
to test the various states. This section will show videos of this.
### Happy Path (Full Update Check and Install)
https://github.com/user-attachments/assets/0d9c3396-cad1-4f13-b247-0fcc7382b47e
### Happy Path (No titlebar)
https://github.com/user-attachments/assets/839ffc20-b2d2-459b-9558-29f0f233d7a2
### No Update Available
https://github.com/user-attachments/assets/44650a98-c39b-4119-a8d0-64c21e06b79f
### Error
https://github.com/user-attachments/assets/ab27fe8c-4dd9-48f2-ad4f-23928d6a6829
### First Launch Permission Check
Note: This would show up automatically, not manually triggered.
https://github.com/user-attachments/assets/0add869e-eea8-4600-b119-4a236e77c4bf
## TODO
- [x] Fix progress percentage causing width wiggling
- [x] Fix padding from edges to be aligned on top/bottom and right
Previous PR: #8535 (merged but problem persists)
Issues: #5934
The Ghostty window will always start with the title "Ghostty" at
startup, and then immediately change to the correct window title. This
is a problem when using compositors like Hyprland and Niri if you want
to create rules for floating windows and similar, as the window title
isn't detected at startup.
This fixes the bad behaviour both for title configured in the config
file, and for processes started with the --title argument.
In this fix I've updated the `tags.zig` `closureComputedTitle()`
function to get the title from the passed in config, and use that as a
fallback before the default `Ghostty` fallback.
Previous behaviour as logged by `niri msg event-stream`:
> Window opened or changed: Window { id: 19, title: Some("Ghostty"),
app_id: Some("com.mitchellh.ghostty-debug"), pid: Some(802495),
workspace_id: Some(1), is_focused: true, is_floating: false, is_urgent:
false, layout: WindowLayout { pos_in_scrolling_layout: Some((3, 1)),
tile_size: (2266.0, 1365.0), window_size: (2266, 1365),
tile_pos_in_workspace_view: None, window_offset_in_tile: (0.0, 0.0) } }
Window layouts changed: [(6, WindowLayout { pos_in_scrolling_layout:
Some((4, 1)), tile_size: (2266.0, 1365.0), window_size: (2266, 1365),
tile_pos_in_workspace_view: None, window_offset_in_tile: (0.0, 0.0) })]
Window opened or changed: Window { id: 19, title:
Some("pr-test-title-fix"), app_id: Some("com.mitchellh.ghostty-debug"),
pid: Some(802495), workspace_id: Some(1), is_focused: true, is_floating:
false, is_urgent: false, layout: WindowLayout { pos_in_scrolling_layout:
Some((3, 1)), tile_size: (2266.0, 1365.0), window_size: (2266, 1365),
tile_pos_in_workspace_view: None, window_offset_in_tile: (0.0, 0.0) } }
New behaviour:
> Window opened or changed: Window { id: 20, title:
Some("pr-test-title-fix"), app_id: Some("com.mitchellh.ghostty-debug"),
pid: Some(804534), workspace_id: Some(1), is_focused: true, is_floating:
false, is_urgent: false, layout: WindowLayout { pos_in_scrolling_layout:
Some((3, 1)), tile_size: (2266.0, 1365.0), window_size: (2266, 1365),
tile_pos_in_workspace_view: None, window_offset_in_tile: (0.0, 0.0) } }
Window layouts changed: [(6, WindowLayout { pos_in_scrolling_layout:
Some((4, 1)), tile_size: (2266.0, 1365.0), window_size: (2266, 1365),
tile_pos_in_workspace_view: None, window_offset_in_tile: (0.0, 0.0) })]
This fixes the problem as shown in the output. I have only tested this
on Linux (Arch with Niri).
Release notes at:
https://github.com/vancluever/z2d/blob/v0.9.0/CHANGELOG.md
This release brings our Zig 0.15.x branch into main, now that Ghostty is
on it too.
Additionally, this adds major speedups to the default path (filling with
a solid color using the default operator).
Release notes at:
https://github.com/vancluever/z2d/blob/v0.9.0/CHANGELOG.md
This release brings our Zig 0.15.x branch into main, now that Ghostty is
on it too.
Additionally, this adds major speedups to the default path (filling with a
solid color using the default operator).
Adds the Metal Toolchain as a required Xcode component for building
Ghostty. Also updates the notes about Xcode 26 now that it and Tahoe are
out of Beta.
Bumps
[softprops/action-gh-release](https://github.com/softprops/action-gh-release)
from 2.3.4 to 2.4.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/releases">softprops/action-gh-release's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.0</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<h3>Exciting New Features 🎉</h3>
<ul>
<li>feat(action): respect working_directory for files globs by <a
href="https://github.com/stephenway"><code>@stephenway</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/667">softprops/action-gh-release#667</a></li>
</ul>
<h3>Other Changes 🔄</h3>
<ul>
<li>chore(deps): bump the npm group with 2 updates by <a
href="https://github.com/dependabot"><code>@dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/668">softprops/action-gh-release#668</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/softprops/action-gh-release/compare/v2.3.4...v2.4.0">https://github.com/softprops/action-gh-release/compare/v2.3.4...v2.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md">softprops/action-gh-release's
changelog</a>.</em></p>
<blockquote>
<h2>2.4.0</h2>
<h2>What's Changed</h2>
<h3>Exciting New Features 🎉</h3>
<ul>
<li>feat(action): respect working_directory for files globs by <a
href="https://github.com/stephenway"><code>@stephenway</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/667">softprops/action-gh-release#667</a></li>
</ul>
<h2>2.3.4</h2>
<h2>What's Changed</h2>
<h3>Bug fixes 🐛</h3>
<ul>
<li>fix(action): handle 422 already_exists race condition by <a
href="https://github.com/stephenway"><code>@stephenway</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/665">softprops/action-gh-release#665</a></li>
</ul>
<h3>Other Changes 🔄</h3>
<ul>
<li>dependency updates</li>
</ul>
<h2>2.3.3</h2>
<h2>What's Changed</h2>
<h3>Exciting New Features 🎉</h3>
<ul>
<li>feat: add input option <code>overwrite_files</code> by <a
href="https://github.com/asfernandes"><code>@asfernandes</code></a> in
<a
href="https://redirect.github.com/softprops/action-gh-release/pull/343">softprops/action-gh-release#343</a></li>
</ul>
<h3>Other Changes 🔄</h3>
<ul>
<li>dependency updates</li>
</ul>
<h2>2.3.2</h2>
<ul>
<li>fix: revert fs <code>readableWebStream</code> change</li>
</ul>
<h2>2.3.1</h2>
<h3>Bug fixes 🐛</h3>
<ul>
<li>fix: fix file closing issue by <a
href="https://github.com/WailGree"><code>@WailGree</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/629">softprops/action-gh-release#629</a></li>
</ul>
<h2>2.3.0</h2>
<ul>
<li>Migrate from jest to vitest</li>
<li>Replace <code>mime</code> with <code>mime-types</code></li>
<li>Bump to use node 24</li>
<li>Dependency updates</li>
</ul>
<h2>2.2.2</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="aec2ec56f9"><code>aec2ec5</code></a>
release 2.4.0</li>
<li><a
href="4db716b167"><code>4db716b</code></a>
feat: respect working_directory for files globs; add input and tests (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/667">#667</a>)</li>
<li><a
href="14820f2cee"><code>14820f2</code></a>
chore(deps): bump the npm group with 2 updates (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/668">#668</a>)</li>
<li>See full diff in <a
href="62c96d0c4e...aec2ec56f9">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Adds the Metal Toolchain as a required Xcode component for building
Ghostty. Also updates the notes about Xcode 26 now that it and Tahoe are
out of Beta.
OSC 133;A can have:
- special_key
- click_events
OSC 133;C can have:
- cmdline
- cmdline_url
Notably, they are in use by `fish`. Not sure what other shells currently
use these options.
Note that the options are only parsed. Nothing further is done with them
at this point.
OSC 133;A can have:
- special_key
- click_events
OSC 133;C can have:
- cmdline
- cmdline_url
Notably, they are in use by `fish`. Not sure what other shells currently
use these options.
Note that the options are only parsed. Nothing further is done with them
at this point.
This fix was found by Claude Code, but I manually reviewed this change
and removed extraneous changes made by the AI tool.
Co-authored-by: moderation <michael@sooper.org>
This modernizes `KeyEncoder` to a new `std.Io.Writer`-based API.
Additionally, instead of a single struct, it is now an `encode` function
that takes a series of more focused options. This is more idiomatic Zig
while also making it easier to expose via libghostty-vt.
libghostty-vt Zig module also gains access to key encoding APIs. The C
APIs will follow another time.
Converting the KeyEncoder tests was done using AI:
https://ampcode.com/threads/T-9731bbdc-e0a9-41ad-9404-2b781a66ee39
Reviewed and understood.
This modernizes `KeyEncoder` to a new `std.Io.Writer`-based API.
Additionally, instead of a single struct, it is now an `encode` function
that takes a series of more focused options. This is more idiomatic Zig
while also making it easier to expose via libghostty-vt.
libghostty-vt also gains access to key encoding APIs.
This moves our paste logic to `src/input` in preparation for exposing
this as part of libghostty-vt. This yields an immediate benefit of unit
tests for paste encoding.
Additionally, we were able to remove one allocation on every unbracketed
paste path unless the input specifically contains a newline. Unlikely to
be noticable, but nice.
NOTE: This also includes one change in behavior: we no longer encode
`\r\n` and a single `\r`, but as a duplicate `\r\r`. This matches xterm
behavior and I don't think will result in any issues since duplicate
carriage returns should do nothing in well-behaved terminals.
This moves our paste logic to `src/input` in preparation for exposing
this as part of libghostty-vt. This yields an immediate benefit of
unit tests for paste encoding.
Additionally, we were able to remove one allocation on every unbracketed
paste path unless the input specifically contains a newline. Unlikely to
be noticable, but nice.
NOTE: This also includes one change in behavior: we no longer encode
`\r\n` and a single `\r`, but as a duplicate `\r\r`. This matches xterm
behavior and I don't think will result in any issues since duplicate
carriage returns should do nothing in well-behaved terminals.
Since #8999, `macos-custom-icon` works when its a fully expanded
absolute path like `/Users/username/dir/icon.icns`, but not when it's
abbreviated as `~/dir/icon.icns`. Users were understandably surprised
and confused by this. This PR adds tilde expansion using `NSString`s
built-in property for this.
Also removed a line from the config docs that seemed erroneous. Given
that the option has a functional default, it seems incorrect to say that
it's required.
As promised in #8990.
I opted for hardcoded metrics and bounding boxes rather than actually
loading fonts and glyphs, both to avoid backend dependence and limit the
focus to the constraint calculations themselves, and because I wanted to
test a case that isn't exhibited by any of the fonts available in the
repo.
This also fixes an error from #8990, probably due to a botched
cherry-pick or rebase.
This is my final set of fixes to the font patcher/icon scaling code. It
builds on #8563 and there's not much reason to pay attention here until
that one has been reviewed (the unique changes in this PR only touch the
two `nerd_font_*` files; the other 8 files in the diff are just #8563).
However, I wanted to make sure the full set of changes/fixes I propose
are out in the open, such that any substantial edits by maintainers
(like in #7953) can take into account the full context.
I think this and the related patches should be considered fixes, not
features, so I hope they can be considered for a 1.2.x release.
This PR fixes some bugs in the extraction of scale and alignment rules
from the `font_patcher` script. Roughly in order of importance:
* Nerd fonts apply an offset to some codepoint ranges when extracting
glyphs from their original font (e.g., Font Awesome) and placing them in
a Nerd Font. Rules are specified in terms of the former codepoints, but
must be applied to the latter. This offset was previously not taken into
account, so rules were applied to the wrong glyphs, and some glyphs that
should have rules didn't get any.
* Previously, the rules from every single patch set was included, but
the embedded Symbols Only font doesn't contain all of them. Most
importantly, there's a legacy patch set that only exists for historical
reasons and is never used anymore, which was overwriting some other
rules because of overlapping codepoint ranges. Also, the Symbols Only
font contains no box drawing characters, so those rules should not be
included. With this PR, irrelevant patch sets are filtered out.
* Some patch sets specify overlapping codepoint ranges, though in
reality the original fonts don't actually cover the full ranges and the
overlaps just imply that they're filling each other's gaps. During font
patching, the presence/absence of a glyph at each codepoint in the
original font takes care of the ambiguity. Since we don't have that
information, we need to hardcode which patch set "wins" for each case
(it's not always the latest set in the list). Luckily, there are only
two cases.
* Many glyphs belong to scale groups that should be scaled and aligned
as a unit. However, in `font_patcher`, the scale group is _not_ used for
_horizontal_ alignment, _unless_ the entire scale group has a single
advance width (remember, the original symbol fonts are not monospace).
This PR implements this rule by only setting `relative_width` and
`relative_x` if the group is monospace.
There are some additional tweaks to ensure that each codepoint actually
gets the rule it's supposed to when it belongs to multiple scale groups
or patch sets, and to avoid setting rules for codepoints that don't
exist in the embedded font.
Follow-up to #8563, which broke scaling without alignment. This change
recovers the behavior from before #8563, such that a scaled group is
clamped to the constraint width and height if necessary, and otherwise,
scaling does not shift the center of the group bounding box.
As a part of this change, horizontal alignment was rewritten to assume
the face is flush with the left edge of the cell. The cell-to-face
offset in the rendering code is then applied regardless of the value of
`align_horizontal`. This both simplifies the code and improves
consistency, as it ensures that the offset is the same for all
non-bitmap glyphs (rounded in FreeType, not rounded in CoreText). It's
the right thing to do following the align-to-face changes in #8563.
In CoreText, when thickening (font smoothing) is enabled or Ghostty is
synthesizing a bold face, the glyph bounding box is padded to make sure
the thicker glyph can fit. Currently, this happens before applying
constraints (scaling and alignment), which makes the size and position
of constrained glyphs dependent on font size, font thickening strength,
and display DPI.
With this PR, constraints are applied before any other adjustments, and
padding is applied directly to the rasterization canvas without
modifying any metrics.
For consistency, I also moved constraint application above emboldening
in the FreeType code, although under that API, the two operations are
orthogonal as far as I can tell.
Secondly, this PR moves glyph centering above bitmap quantization, as
centering is generally fractional and will therefore undo the quantizing
if done after.
Supersedes #8552.
In Freetype, measure rect after emboldening, so constraints apply to the
true glyph size like in CoreText.
In CoreText, don't let font smoothing affect the rect (only the canvas).
This adds a new script we can manually run that downloads all the files
that need to be uploaded to the mirror and updates our build.zig.zon.
The upload still happens manually [by me] but this simplifies the task
greatly.
This adds a new script we can manually run that downloads all the files
that need to be uploaded to the mirror and updates our build.zig.zon.
The upload still happens manually [by me] but this simplifies the task
greatly.
Replaces #8372
Before merge I'm going to squash this and give @pluiedev coauthor, since
I took a lot of her work. I just have to go through this myself to make
sure I learn all the changes in Zig 0.15, but as I got things, I copy
and pasted her work in. My work is probably less thorough and there are
places we can convert deprecated things, probably, but this results in
green CI.
## Benchmarks
It looks like there are some speed regressions in isolated places. I'm
not sure if this is noise or not, I'm going to keep running some tests.
If someone can check macOS that'd be helpful (my vtebench is down on
macOS atm), cc @qwerasd205 if interested.
On an x86_64 system:

Fixes#8991
Uses OSC 133 esc sequences to keep track of how long commands take to
execute. If the user chooses, commands that take longer than a user
specified limit will trigger a notification. The user can choose between
a bell notification or a desktop notification.
Signal handlers are connected to surface objects in two spots - when a
tab is added to a page and when the split tree changes. This resulted in
duplicate signal handlers being added for each surface. This was most
noticeable when copying the selection to the clipboard - you would see
two "Copied to clipboard" toasts. Ensure that there is only one signal
handler by removing any old ones before adding the new ones.
You can pretty simply reproduce a crash on `main` in `Debug` mode by
running `printf "مرحبًا \n"` with your primary font set to one that
supports Arabic such as Cascadia Code/Mono or Kawkab Mono, which will
cause CoreText to output the shaped glyphs non-monotonically which hits
the assert we have in the renderer.
In `ReleaseFast` this assert is skipped and because we already moved
ahead to the space glyph (which belongs at the end but is emitted first)
all of the glyphs up to that point are lost. I believe this is probably
the cause of #8280, I tested and this change seems to fix it at least.
Included in this PR is a little optimization: we were allocating buffers
to copy glyphs etc. from runs to every time, even though CoreText
provides `CTRunGet*Ptr` functions which get *pointers* to the internal
storage of these values- these aren't guaranteed to return a usable
pointer but in that case we can always fall back to allocating again.
Also avoided allocation while processing glyphs by ensuring capacity
beforehand immediately after creating the `CTLine`.
The performance impact of this PR is negligible on my machine and
actually seems to be positive, probably due to avoiding allocations if I
had to guess.
The solution we had before worked in most cases but there were some
which caused problems still. This is what HarfBuzz's CoreText shaper
backend does, it uses a CTTypesetter with the forced embedding level
attribute. This fixes the failure case I found that was causing non-
monotonic outputs which can have all sorts of unexpected results, and
causes a crash in Debug modes because we assert the monotonicity while
rendering.
Reduce potential allocation while processing glyphs by ensuring capacity
in the buffer ahead of time and also using CTRunGet*Ptr functions first
and only allocating for those if that didn't work (it should almost
always work in practice.)
We never used it because our minidump files on Linux didn't contain
meaningful information. With Zig's Writergate, let's drop this and
rewrite it later, we can always resurrect it from the git history.
Rejoice @pluiedev
We never used it because our minidump files on Linux didn't contain
meaningful information. With Zig's Writergate, let's drop this and
rewrite it later, we can always resurrect it from the git history.
Fixes#8991
Uses OSC 133 esc sequences to keep track of how long commands take to
execute. If the user chooses, commands that take longer than a user
specified limit will trigger a notification. The user can choose between
a bell notification or a desktop notification.
#8829 fixed the interaction of Powerline glyphs with other symbols, but
regressed in the handling of the Powerline glyphs themselves by letting
them get caught in an early exit that imposes a constraint width of 1.
This PR fixes the regression and adds corresponding tests. Tried to be
somewhat principled about why the special treatment is warranted, hence
the new helper function `isGraphicsElement`.
**Before**
<img width="270" height="44" alt="Screenshot 2025-10-02 at 00 16 54"
src="https://github.com/user-attachments/assets/9e975434-114c-44d5-a4ed-ac6a954b9d00"
/>
**After**
<img width="270" height="44" alt="Screenshot 2025-10-02 at 00 16 11"
src="https://github.com/user-attachments/assets/20545e74-c9f9-4a6b-9bf0-a7cf1d38c3a0"
/>
Zig 0.15 removed the ability to compress from the stdlib, which makes
porting our framegen tool to Zig 0.15+ more work than it's worth. We
already depend on and have the ability to build zlib, and Zig is a full
blown C compiler, so let's just use C.
The framegen C program doesn't free any memory, because it is meant to
exit quickly. It otherwise behaves pretty much the same as the old Zig
codebase.
The build scripts were modified to build the C program and run it, but
also to include the framedata in the generated source tarball so that
downstream packagers don't have to do this (although they'll have all
the deps anyways).
**AI disclosure:** The Zig to C conversion was done by Amp. I know C and
reviewed the code. The build system improvements are partially done by
Amp as well but I finished it all out. I've reviewed everything. Full
thread:
https://ampcode.com/threads/T-4e0a80af-3d2a-48d6-bf59-3e2f05eb7673
Zig 0.15 removed the ability to compress from the stdlib, which makes
porting our framegen tool to Zig 0.15+ more work than it's worth. We
already depend on and have the ability to build zlib, and Zig is a full
blown C compiler, so let's just use C.
The framegen C program doesn't free any memory, because it is meant to
exit quickly. It otherwise behaves pretty much the same as the old Zig
codebase.
The build scripts were modified to build the C program and run it, but
also to include the framedata in the generated source tarball so that
downstream packagers don't have to do this (although they'll have all
the deps anyways).
This fixes the lazyImport importing outside of the build root. The build
root for the build binary is always the root `build.zig` (which we
want), but our `src/build_config.zig` transitively imported SharedDeps
which led to issues with Zig 0.15.
This back ports to main early (outside our Zig 0.15 PR) because its
compatible and will simplify that one.
This fixes the lazyImport importing outside of the build root. The build
root for the build binary is always the root `build.zig` (which we
want), but our `src/build_config.zig` transitively imported SharedDeps
which led to issues.
Since we now use uucode, we don't need ziglyph anymore. Ziglyph was kept
around as a test-only dep so we can verify matching but this is
complicating our Zig 0.15 upgrade because ziglyph doesn't support Zig
0.15. Let's just drop it.
cc @jacobsandlund
Since we now use uucode, we don't need ziglyph anymore. Ziglyph was kept
around as a test-only dep so we can verify matching but this is
complicating our Zig 0.15 upgrade because ziglyph doesn't support Zig
0.15. Let's just drop it.
I used the new CPU counter mode in Instruments.app to track down
functions that had instruction delivery bottlenecks (indicating i-cache
misses) and picked a bunch of trivial functions to mark as inline (plus
a couple that are only used once or twice and which benefit from
inlining).
The size of `macos-arm64/libghostty-fat.a` built with `zig build
-Doptimize=ReleaseFast -Dxcframework-target=native` goes from
`145,538,856` bytes on `main` to `145,595,952` on this branch, a
negligible increase.
These changes resulted in some pretty sizable improvements in vtebench
results on my machine (Apple M3 Max):
<img width="983" height="696" alt="image"
src="https://github.com/user-attachments/assets/cac595ca-7616-48ed-983c-208c2ca2023f"
/>
With this, the only vtebench test we're slower than Alacritty in (on my
machine, at 130x51 window size) is `dense_cells` (which, IMO, is so
artificial that optimizing for it might actually negatively impact real
world performance).
I also did a pretty simple improvement to how we copy the screen in the
renderer, gave it its own page pool for less memory churn. Further
optimization in that area should be explored since in some scenarios it
seems like as much as 35% of the time on the `io-reader` thread is spent
waiting for the lock.
> [!NOTE]
> Before this is merged, someone really ought to test this on an x86
processor to see how the performance compares there, since this *is*
tuning for my processor specifically, and I know that M chips have
pretty big i-cache compared to some x86 processors which could impact
the performance characteristics of these changes.
A whole bunch of inline annotations, some of these were tracked down
with Instruments.app, others are guesses / just seemed right because
they were trivial wrapper functions.
Regardless, these changes are ultimately supported by improved vtebench
results on my machine (Apple M3 Max).
Changes it so that the renderer retains its own MemoryPool for PageList
pages so that new pages rarely need to be allocated when cloning the
screen. Also switches to using an arena allocator in `updateFrame` to
avoid having to deinit the cloned screen since instead we can just throw
out the memory.
- Provide SONAME versioned shared libraries with major version numbers
to separate ABI breaks
Adding a SemanticVersion to the shared library's module generates
symlings for `libghostty-vt.so.<major version>` and
`libghostty-vt.so.<major>.<minor>`
```
> zig build
> ls zig-out/lib/
libghostty-vt.so@ libghostty-vt.so.0@ libghostty-vt.so.0.1.0*
> readelf -d zig-out/lib/libghostty-vt.so
Dynamic section at offset 0x452858 contains 25 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]
0x000000000000000e (SONAME) Library soname: [libghostty-vt.so.0]
...
```
Major versions are important for allowing multiple versions of a library to co-exist and let the SONAME section in an ELF binary request a specific major version of a shared library.[^1]
I believe the rules for updating SONAME versions match the SemVer spec for ABI changes.
> 1. MAJOR version when you make incompatible API changes
> 2. MINOR version when you add functionality in a backward compatible manner
> 3. PATCH version when you make backward compatible bug fixes
[^2]
[^1]: https://www.man7.org/conf/lca2006/shared_libraries/slide5d.html
[^2]: https://semver.org/#summary
Closes#8791
Discussion: #8657
### Summary
This adds the FocusTerminalIntent App Intent and related function
(focusSurface), allows external tools (such as Shortcuts/Siri) to
programmatically move focus to a specific terminal window or tab.
### Verification
This functionality has been tested across following scenarios,
confirming correct focus behavior for:
- Split Window
- Tab Group
- Quick Terminal
### Note
It is not supported to move focus to a split that is hidden by a zoomed
split. The same applies to the CloseTerminalIntent.
### AI Disclosure
This pull request was made with assistance from Claude Code.
I reviewed all AI-generated code and wrote the final output manually.
Some bell features should be triggered on the receipt of every BEL
character, namely `audio` and `system`. However, Ghostty was setting a
boolean to `true` upon the receipt of the first BEL. Subsequent BEL
characters would be ignored until that boolean was reset to `false`,
usually by keyboard/mouse activity.
This PR fixes the problem by ensuring that the `audio` and `system`
features are triggered every time a BEL is received. Other features
continue to be triggered only when the `bell-ringing` boolean state
changes.
Fixes#8957
Some bell features should be triggered on the receipt of every BEL
character, namely `audio` and `system`. However, Ghostty was setting a
boolean to `true` upon the receipt of the first BEL. Subsequent BEL
characters would be ignored until that boolean was reset to `false`,
usually by keyboard/mouse activity.
This PR fixes the problem by ensuring that the `audio` and `system`
features are triggered every time a BEL is received. Other features
continue to be triggered only when the `bell-ringing` boolean state
changes.
Fixes#8957
Fixes https://github.com/ghostty-org/ghostty/discussions/8697 by making
`OK` the suggested default and activating it by default.
Previously `OK` was `destructive` which imo is not a good approach for
just setting a terminal title.
Follow-up to #8720 adding
* Two improvements to FreeType glyph measurements:
- Ensuring that glyphs are measured with the same hinting as they are
rendered, ref
[#8720#issuecomment-3305408157](https://github.com/ghostty-org/ghostty/pull/8720#issuecomment-3305408157);
- For outline glyphs, using the outline bbox instead of the built-in
metrics, like `renderGlyph()`.
* Basic unit tests for face metrics and their estimators, using the
narrowest and widest fonts from the resource directory, Cozette Vector
and Geist Mono.
---
I also made one unrelated change to `freetype.zig`, replacing
`@alignCast(@ptrCast(...))` with `@ptrCast(@alignCast(...))` on line
173. Autoformatting has been making this change on every save for weeks,
and reverting the hunk before each commit is getting old, so I hope it's
OK that I use this PR to upstream this decree from the formatter.
Powerline glyphs were treated as whitespace, giving the preceding cell a
constraint width of 2 and cutting off icons in people's prompts and
statuslines. It is however correct to not treat Powerline glyphs like
other Nerd Font symbols; they should simply be treated as normal
characters, just like their relatives in the block elements unicode
block.
This resolves
https://discord.com/channels/1005603569187160125/1417236683266592798
(never promoted to an issue, but real and easy to reproduce).
**Tip**
<img width="215" height="63" alt="Screenshot 2025-09-21 at 16 57 58"
src="https://github.com/user-attachments/assets/81e770c5-d688-4d8e-839c-1f4288703c06"
/>
**This PR**
<img width="215" height="63" alt="Screenshot 2025-09-21 at 16 58 42"
src="https://github.com/user-attachments/assets/5d2dd770-0314-46f6-99b5-237a0933998e"
/>
The constraint width logic was untested but contains some quite subtle
interactions, so I wrote a suite of tests covering the cases I'm aware
of.
While working on this code I also resolved a TODO comment to add all the
box drawing/block element type characters to the set of codepoints
excluded from the minimum contrast settings.
> This PR will probably need a rebase on the final outcome of #8550 and
~#8552~ #8580, but I'm putting it out here so folks can begin taking a
look if they want.
This is a rewrite of the code that applies scaling and alignment
constraints. The main intention is to further improve how Nerd Font
icons are matched to the primary font. This PR aligns the calculations
more closely with how the Nerd Font `font-patcher` script works, except
in two cases where we can easily do something unambiguously better (one
because of what's arguably a bug in the script, and one because we do
multi-cell alignment with knowledge of the pixel-rounded cell grid).
A goal of the rewrite is to make the scaling and alignment calculations
as clear and easy to follow as possible.
I'll lead with some screenshots. First the status quo, then this PR.
<img width="505" height="357" alt="Screenshot 2025-09-07 at 17 23 51"
src="https://github.com/user-attachments/assets/8e3ff9fd-3b66-4d54-be38-d54cf3b6cc5b"
/><img width="505" height="357" alt="Screenshot 2025-09-07 at 17 20 39"
src="https://github.com/user-attachments/assets/84fbe076-2e3f-4879-b9b2-91ce86b9ef5f"
/>
Relevant specs: macOS; 1920x1080; Ghostty config:
```ini
font-family = "CommitMono"
font-size = "15"
adjust-cell-height = "+20%"
```
**Points to note**
* Icons are generally larger, making better use of the available space.
* Icons are aligned nearly a pixel lower, better matching the text. This
is because alignment is now calculated from face metrics/bearings, not
the pixel-rounded cell. (See more below.)
* Relative sizes are better matched. Note especially that tall and
narrow icons, like the git branch symbol and icons depicting sheets of
paper, look conspicuously small in the status quo. With this PR, they're
better matched to other icons.
* Look at the letter Z icon I use as prompt character for zsh. It's
_tiny_ in the status quo, but properly sized with this PR. This
demonstrates the most important and clear-cut improvement we make over
`font-patcher`. (See more below.)
* Icons wider than a single cell are now left-aligned rather than
centered across two cells. I think this is preferable and makes better
use of space in most relevant contexts.
- Consider a Neovim bufferline showing the buffer title as a filetype
icon followed by the file name. Padding on the left would be a waste of
space, but having that extra space on the right can improve legibility.
- In listings, such as in the screenshots, columns look tidier when
their left edges are straight rather than ragged.
- This is how `font-patcher` does alignment, and thus what Nerd Font
users and UI designers expect.
**Implementation details**
I won't get too deep in the weeds here; see the code and comments. In
brief:
* `size_horizontal` and `size_vertical` are combined to a single `size`,
which can be `.none, .stretch, .fit, .cover` or `.fit_cover1`. The
latter implements the `pa` rule from `font-patcher`, except it works
better for icons that are small before scaling, like the letter Z prompt
in the screenshots. In short, it preserves aspect ratio while clamping
the size such that the icon `.cover`s at least one cell and `.fit`s
within the available space. See code comments and
ryanoasis/nerd-fonts/pull/1926 for details.
* An alignment mode `.center1` is added, implementing the centering rule
from `font-patcher` that I explained/defended above. In short, we center
the icon _in the first cell_, even it's allowed to span multiple cells.
For icons wider than a single cell, the lower bound that prevents them
from protruding to the left kicks in and turns this into left-alignment.
We keep the regular `.center` rule around for use with emojis, et
cetera.
* Scaling and alignment calculations only use the unrounded face metrics
and bearings. This ensures that pixel rounding of the cell and baseline,
and `adjust-cell-{width,height}`, don't affect scaling or relative
alignment; the icons are always scaled and aligned to the _face_. (The
one place we need to use cell metrics in the calculations is when we use
`cell_width` to obtain the inter-cell padding needed to correctly center
or right-align a glyph across two cells.)
- We can do this with impunity because we're blessed with sprite glyphs
in place of the "icons" that are actually box drawing and block graphics
characters 🙌
**Guide**
The meat of the changes is 100 % in `src/font/face.zig` and
`src/font/nerd_font_codegen.py`. Changes to other files only amount to
a) adding/changing some struct fields to get numbers to where they need
to be (see `src/font/Metrics.zig`), and b) collateral updates to make
otherwise unchanged code and tests work with/take advantage of the
modified structs.
Most files should have a clear and friendly diff. The exception is the
bottom half of `src/font/face.zig`, where the diff is meaningless and
the new code should just be reviewed on its own merits. This is the part
where the `constrain` function is rewritten and refactored. Scarred by
countless hours perusing `font-patcher`, I tried hard to make the math
and logic easy to follow here. I hope I have succeeded 🤞
Fixes#8944
When we drag the only tab out of the tab overview, this triggers an
`n-pages` signal with 0 pages. If we close the window in this state, it
causes both Ghostty to exit AND the drag/drop to fail. Even if we
pre-empt Ghostty exiting by modifying the application class, the
drag/drop still fails and the application leaks memory and enters a bad
state.
The solution is to keep the window open if we go to `n-pages == 0` and
we have the tab overview open.
Interestingly, if you click to close the final tab from the tab
overview, Adwaita closes the tab overview so it still triggers the
window closing behavior (this is good, this is desired).
Fixes#8944
When we drag the only tab out of the tab overview, this triggers
an `n-pages` signal with 0 pages. If we close the window in this state,
it causes both Ghostty to exit AND the drag/drop to fail. Even if we
pre-empt Ghostty exiting by modifying the application class, the
drag/drop still fails and the application leaks memory and enters a bad
state.
The solution is to keep the window open if we go to `n-pages == 0` and
we have the tab overview open.
Interestingly, if you click to close the final tab from the tab
overview, Adwaita closes the tab overview so it still triggers the
window closing behavior (this is good).
Resolves Issue: #8670
Now precision and discrete scrolling can be scaled independently.
Supports following configuration,
```code
# Apply everywhere
mouse-scroll-multiplier = 3
# Apply separately
mouse-scroll-multiplier = precision:0.1,discrete:3 (default)
# Also it's order agnostic
mouse-scroll-multiplier = discrete:3,precision:2
# Apply one, default other
mouse-scroll-multiplier = precision:2
```
The default precision value is set 0.1, as it felt natural to me at
least on my track-pad. I've also set the min clamp value precision to
0.1 as 0.01 felt kind of useless to me but I'm unsure.
This adds functions to extra data out of OSC commands, but it is mostly
boilerplate since I only added one valid data extraction. 😄 The example
was updated to show this.
This also changes OSC strings to be null-terminated to ease lib-vt
integration. This shouldn't have any practical effect on terminal
performance, but it does lower the maximum length of OSC strings by 1
since we always reserve space for the null terminator.
This also changes OSC strings to be null-terminated to ease lib-vt
integration. This shouldn't have any practical effect on terminal
performance, but it does lower the maximum length of OSC strings by 1
since we always reserve space for the null terminator.
Fixes various issues:
- C ABI detection was faulty, which caused some Zig programs to use the
C ABI mode and some C programs not to. Let's be explicit.
- Unit tests now tests C ABI mode.
- Build binary no longer rebuilds on any terminal change (a regression).
- Zig programs can choose to depend on the C ABI version of the terminal
lib by using the `ghostty-vt-c` module.
Fixes various issues:
- C ABI detection was faulty, which caused some Zig programs to use
the C ABI mode and some C programs not to. Let's be explicit.
- Unit tests now tests C ABI mode.
- Build binary no longer rebuilds on any terminal change (a regression).
- Zig programs can choose to depend on the C ABI version of the terminal
lib by using the `ghostty-vt-c` module.
This adds more of the OSC parsing API to the C library of
`libghostty-vt`. This adds the following:
* `ghostty_osc_next` - Push a single character into the OSC parser
* `ghostty_osc_reset` - Reset the parser state and free any temporary
memory
* `ghostty_osc_end` - End a parsing sequence and return the parsed
command
* `ghostty_osc_command_type` - Return the type of command parsed
* `examples/c-vt` is updated to use these new APIs
* Our Zig `osc.Command` tagged union is updated to use a new comptime
func to generate an ABI compatible tag for the C lib.
* **Important change:** Our Zig `osc.Parser.end` function was modified
to return a _pointer_ to the command within its own structure memory
rather than a copy. This eases the C API. This impacts the Zig side but
we ultimately copy it again [for now] in the main `terminal.Parser` so
end result is the same.
* Unit tests cover even the C lib
* Shuffled some code for maintainability
The plan is to use this opaque type with getters to ease ABI
compatibility going forward.
Related to #8924
Zig currenly has a bug where it crashes when compiling Ghostty on
systems with more than 32 cpus (See the linked issue for the gory
details). As a temporary hack, use `sched_setaffinity` on Linux systems
to limit the compile to the first 32 cores. Note that this affects the
build only. The resulting Ghostty executable is not limited in any way.
This is a more general fix than wrapping the Zig compiler with
`taskset`. First of all, it requires no action from the user or
packagers. Second, it will be easier for us to remove once the upstream
Zig bug is fixed.
Related to #8924
Zig currenly has a bug where it crashes when compiling Ghostty on
systems with more than 32 cpus (See the linked issue for the gory
details). As a temporary hack, use `sched_setaffinity` on Linux systems
to limit the compile to the first 32 cores. Note that this affects the
build only. The resulting Ghostty executable is not limited in any way.
This is a more general fix than wrapping the Zig compiler with
`taskset`. First of all, it requires no action from the user or
packagers. Second, it will be easier for us to remove once the upstream
Zig bug is fixed.
Hi!
I'm a full Zig noob but [Mitchell's recent
post](https://mitchellh.com/writing/libghostty-is-coming) made me want
to clone the repo and take a look at the tooling.
My first attempt at running examples though VSCode failed because the
latest version of Zig is 0.15.1, but Ghostty requires Zig 0.14*. When
configuring the extension to use a compatible version if Zig, it
suggested pinning the version in a .zigversion file. I'm not familiar
with the pattern, but if it can help someone else's onboarding, I
figured I'd open a PR to suggest the change.
Cheers
*edit: I had a hard time figuring that out
Fix provided by @jcollie
The swift `open_config` action was triggering an allocation error
`error(gpa): Allocation size 41 bytes does not match free size 40.`.
> A string that was created as a `[:0]const u8` was cast to `[]const u8`
and then freed. The sentinel is the off-by-one.
@jcollie
For full context, see
https://discord.com/channels/1005603569187160125/1420367156071239820
Co-authored-by: Jeffrey C. Ollie <jcollie@dmacc.edu>
This is nicer because it only sets the filetype if it hasn't already
been set. :setf[iletype] has been available since vim version 6.
See: https://vimhelp.org/options.txt.html#%3Asetf
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.17 to 1.2.18.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7baedde84b"><code>7baedde</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/36">#36</a>
from namespacelabs/niklas-docs</li>
<li><a
href="72a48b9085"><code>72a48b9</code></a>
fix docs links</li>
<li><a
href="f5a0d9e059"><code>f5a0d9e</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/35">#35</a>
from namespacelabs/niklas-workflow</li>
<li><a
href="7bb48a0f26"><code>7bb48a0</code></a>
skip bundle exec</li>
<li><a
href="32d69b87dc"><code>32d69b8</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/34">#34</a>
from namespacelabs/niklas-apple-caches</li>
<li><a
href="a49d9d84e9"><code>a49d9d8</code></a>
Add experimental cache modes for Xcode, Swift, Ruby and CocoaPods</li>
<li>See full diff in <a
href="a289cf5d2f...7baedde84b">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
This is nicer because it only sets the filetype if it hasn't already
been set. :setf[iletype] has been available since vim version 6.
See: https://vimhelp.org/options.txt.html#%3Asetf
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.6.2 to 31.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.7.0</h2>
<h2>What's Changed</h2>
<ul>
<li>
<p>feat: set up the environment based on the installer shell scripts by
<a href="https://github.com/sandydoo"><code>@sandydoo</code></a> in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/251">cachix/install-nix-action#251</a></p>
<p>Configures the following environment variables:</p>
<ul>
<li><code>NIX_PROFILES</code></li>
<li><code>NIX_SSL_CERT_FILE</code> (if not set)</li>
</ul>
<p>Adds the bin directory from the user's profile to
<code>$PATH</code>.</p>
</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31.6.2...v31.7.0">https://github.com/cachix/install-nix-action/compare/v31.6.2...v31.7.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9280e7aca8"><code>9280e7a</code></a>
fix: use -e to check for certs</li>
<li><a
href="effa594a17"><code>effa594</code></a>
fix: simplify setting the user profile</li>
<li><a
href="eb0f6c7357"><code>eb0f6c7</code></a>
ci: document where to find available images to test against</li>
<li><a
href="6676c23a71"><code>6676c23</code></a>
ci: add ubuntu-22.04-arm</li>
<li><a
href="cbf4b16d11"><code>cbf4b16</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/251">#251</a>
from cachix/fix-env</li>
<li><a
href="29a4dac2fa"><code>29a4dac</code></a>
tests: refactor tests to run under a single matrix</li>
<li><a
href="7449e8905b"><code>7449e89</code></a>
tests: improve env tests and move to tests dir</li>
<li><a
href="d487f94a7a"><code>d487f94</code></a>
lint</li>
<li><a
href="581a134122"><code>581a134</code></a>
refactor: document ssl-cert-file vs NIX_SSL_CERT_FILE</li>
<li><a
href="d914f6d9e8"><code>d914f6d</code></a>
refactor: drop ssl handling for unsupported platforms</li>
<li>Additional commits viewable in <a
href="a809471b5c...9280e7aca8">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Fixes#8849
Previously, the `parseAutoStruct` function that was used to parse
generic structs for the config simply split the input value on commas
without taking into account quoting or escapes. This led to problems
because it was impossible to include a comma in the value of config
entries that were parsed by `parseAutoStruct`. This is particularly
problematic because `ghostty +show-config --default` would produce
output like the following:
```
command-palette-entry = title:Focus Split: Next,description:Focus the next split, if any.,action:goto_split:next
```
Because the `description` contains a comma, Ghostty is unable to parse
this correctly. The value would be split into four parts:
```
title:Focus Split: Next
description:Focus the next split
if any.
action:goto_split:next
```
Instead of three parts:
```
title:Focus Split: Next
description:Focus the next split, if any.
action:goto_split:next
```
Because `parseAutoStruct` simply looked for commas to split on, no
amount of quoting or escaping would allow that to be parsed correctly.
This is fixed by (1) introducing a parser that will split the input to
`parseAutoStruct` into fields while taking into account quotes and
escaping. And (2) changing the `ghostty +show-config` output to put the
values in `command-palette-entry` into quotes so that Ghostty can parse
it's own output.
`parseAutoStruct` will also now parse double quoted values as a Zig
string literal. This makes it easier to embed control codes, whitespace,
and commas in values.
Resolves#8890
If you try to create the config file when the directory already exists,
you (I) get an error that the _file_ path already exists.
```
warning(config): error creating template config file err=error.PathAlreadyExists
```
Even though the file does not exist. By changing the API entry point,
this error goes away.
I have no solid explanation for why this change works.
| State | Old Behavior | New Behavior |
|--------|--------|--------|
| A config file exists | N/A | N/A |
| No config file, no directory | create directory and config file | N/A
|
| No config file, yes directory | fail to create on config file | create
config file |
This behavior is confirmed on my macOS 26 machine. It is the least
intrusive change I could make, and in all other situations should be a
no-op.
This adds the boilerplate necessary for `libghostty-vt` the C library.
> [!IMPORTANT]
>
> This _does not expose almost any APIs_. The point of this PR is to
setup our boilerplate, build system, docs system, etc. for the
libghostty-vt C lib.
- Adds a `zig build lib-vt` target to _only_ build `libghostty-vt`
- Adds the beginning of `include/ghostty-vt.h` for the C API
- Adds a full custom allocator interface to the C API mimicking Zig
custom allocators
- Adds an example in `example/c-vt` that builds a pure C program and
links to our shared library and calls functions
- Adds the `osc_parser_new/free` C APIs just as a proof of concept that
things work
- Adds a basic Doxygen config so we have _something_ (I'm not at all
committed to Doxygen, but want us to doc from the beginning)
- Updates CI to test building the shared library for macOS, Windows, and
Linux (yes, it builds for Windows!)
**Note:** To use the `dep.artifact` function provided by Zig, we must
install the artifact. But this means that every `zig build` now includes
`libghostty-vt`. That... could be completely fine, but it's something to
consider for packagers.
## Bikeshed
We're at a pivotal point where we must define the general _style_ of our
C API.
This includes the very bike shed things such as capitalization styling,
but also general API form.
ABI compatibility will eventually be important.
I'm very much open and would love to receive feedback form more
experience C programmers on what they feel would constitute a good API.
I've consumed _many_ C APIs but I haven't provided many directly.
cc @gpanders
Fixes#8849
Previously, the `parseAutoStruct` function that was used to parse
generic structs for the config simply split the input value on commas
without taking into account quoting or escapes. This led to problems
because it was impossible to include a comma in the value of config
entries that were parsed by `parseAutoStruct`. This is particularly
problematic because `ghostty +show-config --default` would produce
output like the following:
```
command-palette-entry = title:Focus Split: Next,description:Focus the next split, if any.,action:goto_split:next
```
Because the `description` contains a comma, Ghostty is unable to
parse this correctly. The value would be split into four parts:
```
title:Focus Split: Next
description:Focus the next split
if any.
action:goto_split:next
```
Instead of three parts:
```
title:Focus Split: Next
description:Focus the next split, if any.
action:goto_split:next
```
Because `parseAutoStruct` simply looked for commas to split on, no
amount of quoting or escaping would allow that to be parsed correctly.
This is fixed by (1) introducing a parser that will split the input
to `parseAutoStruct` into fields while taking into account quotes and
escaping. And (2) changing the `ghostty +show-config` output to put the
values in `command-palette-entry` into quotes so that Ghostty can parse
it's own output.
`parseAutoStruct` will also now parse double quoted values as a Zig
string literal. This makes it easier to embed control codes, whitespace,
and commas in values.
This makes a `ghostty-vt` Zig module available from our `build.zig` that
contains a reusable Zig API version of our core terminal emulation layer
including escape sequence parsing, terminal state, and screen state.
This is the groundwork for phase one of my "libghostty" vision.
With SIMD disabled, `ghostty-vt` has no dependencies -- not even on libc
-- and can produce fully static standalone binaries. With SIMD enabled,
`ghostty-vt` only depends on libc.
The point of this PR is primarily to get the bug fixes I found in and to
get this running in CI on every commit so that we don't regress it. In
the future we'll do more (see the future section below).
> [!WARNING]
> **The API is extremely not stable and will definitely change in the
future.** The _functionality/logic_ is very stable, because it's the
same core logic used by Ghostty, but the API itself is not at all. For
this PR, we mostly just expose everything and we'll reshape the API
later.
## What is `libghostty-vt`?
I've stated my vision for a `libghostty` for some time. You can find
background on that. Recently, I've realized that the _scope_ of
`libghostty` is way too large to ship as a single unit. To that end,
`libghostty` will be split into smaller scoped sub-libraries (that may
depend on each other for higher level functionality). The exact mapping
is being worked out.
**The first library I'm extracting is `libghostty-vt` (both Zig and C,
this PR starts with Zig).** This will be a library focused only on core
terminal emulation, terminal state, and screen state. It lacks rendering
support and input handling.
**But why?** The core terminal emulation is the primary source of both
missing functionality and bugs within terminal emulators. Look at this
[simple bug in jediterm](https://github.com/JetBrains/jediterm/pull/311)
that fails to parse a trivially common sequence resulting in horrendous
misrenders. Jediterm is used by every JetBrains IDE! Literally the core
terminal in a many-millions-of-dollars business!
`libghostty-vt` is a _zero dependency_ terminal emulation layer that
exposes a C API which will let any popular language build bindings so
that we can stop reinventing the terminal emulation layer and get best
in class (or near it) terminal emulation capabilities everywhere.
## In This PR
- `ghostty-vt` Zig module
- Example usage of it in `example/zig-vt`
- CI to run Zig module tests, test that our examples build, and test
SIMD on/off
- New feature build flag `-Dsimd` (default on) that turns SIMD on or off
- Unexposed feature flag that allows building the core terminal logic
without regex support (default on right now jus for the ghostty-vt
module as I figure out what our future regex story is in a post-oni
world).
- Fixes for non-SIMD builds
## Future
There's a lot to do in the future outside of this PR:
- Define a more stable Zig API
- Define a C API at all
- Figure out our regex engine story
- Documentation improvements
This fixes test failures when Ghostty's core is run without libc.
Ghostty in the real world (all built executables) require libc so this
bug has never been hit before, but I'm working on a libc-less core and
this caused real test failures (so its already tested, as well).
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm)
from 1.2.3 to 1.2.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vmactions/freebsd-vm/releases">vmactions/freebsd-vm's
releases</a>.</em></p>
<blockquote>
<p>add 15.0</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="487ce35b96"><code>487ce35</code></a>
Update from base vm</li>
<li><a
href="81b0e85cca"><code>81b0e85</code></a>
Generated from base-vm</li>
<li><a
href="4f22df8b8d"><code>4f22df8</code></a>
add 15</li>
<li><a
href="11ecb20fda"><code>11ecb20</code></a>
Update from base vm</li>
<li><a
href="ec67fecd18"><code>ec67fec</code></a>
Generated from base-vm</li>
<li><a
href="709b36f060"><code>709b36f</code></a>
Update version to</li>
<li>See full diff in <a
href="05856381fa...487ce35b96">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.6.1 to 31.6.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.6.2</h2>
<h2>What's Changed</h2>
<ul>
<li>nix: 2.31.1 -> 2.31.2 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/256">cachix/install-nix-action#256</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31...v31.6.2">https://github.com/cachix/install-nix-action/compare/v31...v31.6.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a809471b5c"><code>a809471</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/256">#256</a>
from cachix/create-pull-request/patch</li>
<li><a
href="d5f1c043d0"><code>d5f1c04</code></a>
nix: 2.31.1 -> 2.31.2</li>
<li>See full diff in <a
href="7be5dee142...a809471b5c">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Replaces #8786
The author of the original PR used AI agents to create that PR. To the
extent that this PR borrows code from that PR (mostly in the tests) AI
was used in the creation of this PR.
e0d6 and e0d7 were left out.
Also collapsed everything to a single range; unlikely that the unused
gaps (e0c9, e0cb, e0d3, e0d5) would be used for something else in any
font that ships Powerline glyphs.
...not whitespace. Powerline glyphs can be considered an extension of
the Block Elements unicode block, which is neither whitespace nor
symbols (icons).
This ensures that characters immediately followed by a powerline glyph
are constrained to a single cell (unlike the current behavior where a PL
glyph is considered whitespace), while symbols (icons) immediately
preceded by a powerline glyph are not (unlike if a PL glyph were
considered a symbol). This resolves
https://discord.com/channels/1005603569187160125/1417236683266592798
This makes more dependencies lazy. This has a practical effect of
reducing the number of dependencies that need to be downloaded when
running certain zig build steps.
This is all limited because we're blocked on an upstream Zig issue:
https://github.com/ziglang/zig/issues/21525 This prevents us from fully
avoiding downloading many dependencies, but at least they're relatively
small.
One major improvement here is the usage of `lazyImport` for
`zig-wayland` that prevents downloading `zig_wayland` unconditionally on
all platforms. On macOS, we don't download this at all anymore.
Another, weirder change is that all our transitive dependencies are now
marked lazy (e.g. glslang's upstream source) even if the glslang build
always requires it. This was necessary because without this, even if we
simply referenced glslang in the root build.zig, it would force the
source package to download unconditionally. This no longer happens.
cc @pluiedev Minor improvements here, doesn't change the long term plan,
but improves things in the interim.
This makes more dependencies lazy. This has a practical effect of
reducing the number of dependencies that need to be downloaded when
running certain zig build steps.
This is all limited because we're blocked on an upstream Zig issue:
https://github.com/ziglang/zig/issues/21525 This prevents us from
fully avoiding downloading many dependencies, but at least they're
relatively small.
One major improvement here is the usage of `lazyImport` for
`zig-wayland` that prevents downloading `zig_wayland` unconditionally on
all platforms. On macOS, we don't download this at all anymore.
Another, weirder change is that all our transitive dependencies are now
marked lazy (e.g. glslang's upstream source) even if the glslang build
always requires it. This was necessary because without this, even if we
simply referenced glslang in the root build.zig, it would force the
source package to download unconditionally. This no longer happens.
This was not ported to gtk-ng before old runtime was removed, breaking
shell integration on Flatpak.
This implementation is copied verbatim from old runtime.
This makes it cleaner visually where the separation of concerns is.
There is now the generic `Properties.zig`, and then the
implementation-specific `props_<impl>.zig` files. Despite Zig's lazy
analysis, I find this is much easier to understand as a human.
Doing this resulted in finding one part in `src/terminal` where we were
still inadvertently using ziglyph directly instead of our LUTs! I
switched this out.
After this PR, `src/terminal` as a standalone module no longer depends
on `ziglyph` at all.[^1]
cc @jacobsandlund this is going to cause conflicts in your PR. I'm sorry
about that. But it should make it cleaner to bring in the uucode work by
adding a dedicated `props_uucode.zig` file!
[^1]: Why would I be talking about `src/terminal` as a standalone
module? That's interesting.
This makes it cleaner to add new sources of table generation and also
avoids inadvertently depending on different modules (despite Zig's lazy
analysis).
This also fixes up terminal to only use our look up tables which avoids
bringing ziglyph in for the terminal module.
Avoid leaking snap environment values and in particular the `$SNAP*`
values to the children that we run from the terminal.
Do this programmatically instead of the launcher, since ghostty needs
know the environment it runs in, while it must not leak the info to the
children.
We've also another leak on snap, that creates a more visible problem
(wrong matching of children applications) that is the apparmor security
profile.
I've handled it at
cc3b46f600
but that requires some love in order to fully decouple the snap option
to the build, to avoid including it in non-snap builds, so an help would
be appreciated there.
> This PR was contains code (in `filterSnapPaths`) that was improved by
DeepSeek.
When running in a snap context we need to filtering out all the SNAP_*
variables out there, but this is not enough, because it's also needed
to sanitize them by ensuring that no variable containing a path pointing
to a $SNAP folder is leaked there.
Otherwise we might have (for example) XDG_RUNTIME_DIRS containing a
"private" snap path, and that will be exposed to all the applications
that will be started from ghostty
This is stomping towards minimizing our build.zig dependencies so that
it can be cached more often. Right now, touching almost any file in the
project forces the build.zig to rebuild which is destroying my
productivity.
The first set of work is to move all our completion and syntax file
generation into a data generator exe, e.g. vim, zsh, fish, etc. This
might seem like just shuffling bits but it results in a real tangible
improvement: when you run `zig build test`, we no longer have to rebuild
our `build.zig` when you for example... modify a CLI action.
This is just a small improvement. Our build.zig depends on way too much
stuff, so this PR will be draft while I continue to use commits to
separate out scopes of change.
This is stomping towards minimizing our build.zig dependencies so that
it can be cached more often. Right now, touching almost any file in the
project forces the build.zig to rebuild which is destroying my
productivity.
Fixes#8713
This stores the last closed state of the quick terminal by screen
pointer. We use a weak mapping so if a screen is unplugged we'll clear
the memory. We will not remember the size if you unplug and replug in a
monitor.
Fixes#8713
This stores the last closed state of the quick terminal by screen
pointer. We use a weak mapping so if a screen is unplugged we'll clear
the memory. We will not remember the size if you unplug and replug in a
monitor.
Fixes#8734
This forces the app icon to be set on another event loop tick from the
main startup.
In the future, we should load and set the icon completely in another
thread. It appears that all the logic we have is totally thread-safe.
Fixes#8734
This forces the app icon to be set on another event loop tick from
the main startup.
In the future, we should load and set the icon completely in another
thread. It appears that all the logic we have is totally thread-safe.
Fixes#8785
This is the callback AppKit sends when it wants to know if our
application can handle sending and receiving certain types of data.
The prior implementaiton was incorrect and would erroneously claim
support over combinations that we couldn't handle (at least, at the
SurfaceView layer).
This corrects the implementation. The services we expect still show up
and the error in 8785 goes away.
Fixes#8785
This is the callback AppKit sends when it wants to know if our
application can handle sending and receiving certain types of data.
The prior implementaiton was incorrect and would erroneously claim
support over combinations that we couldn't handle (at least, at the
SurfaceView layer).
This corrects the implementation. The services we expect still show up
and the error in 8785 goes away.
Fixes#8783
Our new tab flow will never have a previously focused window because its
triggered by a service so we need to use the "preferred parent" logic we
have to open this in the last focused window.
Fixes#8783
Our new tab flow will never have a previously focused window because its
triggered by a service so we need to use the "preferred parent" logic we
have to open this in the last focused window.
Hello
This is a small thing I noticed when building Ghostty. The logs for
SetTitle and Pwd are unreadable due to them being shown as an array of
integers, so I added a custom formatter for them to be shows as text.
Previously we're just feeding the compiler source files generated via
capturing stdout, which all by default have the filename `stdout`. Under
some particular yet uncertain circumstances in Zig 0.14 (only factor
we've found so far is a large amount of cores/compilation shards) the
compiler will actually crash on an unreachable code path that assumes
all source files either end in .zig or .zon, causing crashes for
packagers for distros like Nixpkgs and Gentoo.
Given this has been explicitly made illegal in Zig 0.15 (see
ziglang/zig#24957) I don't really see why we shouldn't fix this for 1.2
too. We have to do this at some point no matter what anyways.
Previously we're just feeding the compiler source files generated via
capturing stdout, which all by default have the filename `stdout`.
Under some particular yet uncertain circumstances in Zig 0.14 (only
factor we've found so far is a large amount of cores/compilation shards)
the compiler will actually crash on an unreachable code path that assumes
all source files either end in .zig or .zon, causing crashes for packagers
for distros like Nixpkgs and Gentoo.
Given this has been explicitly made illegal in Zig 0.15
(see ziglang/zig#24957) I don't really see why we shouldn't fix this for
1.2 too. We have to do this at some point no matter what anyways.
Maybe fixes#8736
I thought `windowDidLoad` was early on because its before the window is
shown but apparently not. Let's try `awakeFromNib` which is called just
after the window is loaded from the nib. It is hard to get any earlier
than that.
Maybe fixes#8736
I thought `windowDidLoad` was early on because its before the window is
shown but apparently not. Let's try `awakeFromNib` which is called
just after the window is loaded from the nib. It is hard to get any
earlier than that.
The documentation shows that the enum values should be "new-window" and
"new-tab",
However, "new-window" currently fails, but "window" is still accepted.
The GLSL to MSL conversion process uses a passed-in sampler state for
the `iChannel0` parameter and we weren't providing it. This magically
worked on Apple Silicon for unknown reasons but failed on Intel GPUs.
In normal, hand-written MSL, we'd explicitly create the sampler state as
a normal variable (we do this in `shaders.metal` already!), but the
Shadertoy conversion stuff doesn't do this, probably because the exact
sampler parameters can't be safely known.
This fixes a Metal validation error when using custom shaders:
```
-[MTLDebugRenderCommandEncoder validateCommonDrawErrors:]:5970: failed
assertion `Draw Errors Validation Fragment Function(main0): missing Sampler
binding at index 0 for iChannel0Smplr[0].
```
This wasn't a simple error message, the assertion would cause Xcode 26
to halt the program at that point.
The GLSL to MSL conversion process uses a passed-in sampler state for
the `iChannel0` parameter and we weren't providing it. This magically
worked on Apple Silicon for unknown reasons but failed on Intel GPUs.
In normal, hand-written MSL, we'd explicitly create the sampler state as
a normal variable (we do this in `shaders.metal` already!), but the
Shadertoy conversion stuff doesn't do this, probably because the exact
sampler parameters can't be safely known.
This fixes a Metal validation error when using custom shaders:
```
-[MTLDebugRenderCommandEncoder validateCommonDrawErrors:]:5970: failed
assertion `Draw Errors Validation Fragment Function(main0): missing Sampler
binding at index 0 for iChannel0Smplr[0].
```
This fixes a Metal validation error in Xcode when using custom shaders.
I suspect this is one part of custom shaders not working properly on
Intel macs (probably anything with a discrete GPU).
This happens to work on Apple Silicon but this is undefined behavior and
we're just getting lucky.
There is one more issue I'm chasing down that I think is also still
blocking custom shaders working on Intel macs.
This fixes a Metal validation error in Xcode when using custom shaders.
I suspect this is one part of custom shaders not working properly on
Intel macs (probably anything with a discrete GPU).
This happens to work on Apple Silicon but this is undefined behavior and
we're just getting lucky.
There is one more issue I'm chasing down that I think is also still
blocking custom shaders working on Intel macs.
Measure the ~actual height between the ascender and descender lines as
the height of the~ overall bounding box of the font's ASCII
characters[^1], and use this to upper bound the IC width estimate. This
ensures that the scaling of fallback CJK fonts to a wide-aspect primary
font doesn't result in oversized CJK glyphs.
Fixes#8709.
I'd appreciate feedback from Chinese speakers on the suitability of this
metric across a variety of primary fonts.
Screenshots using an empty Ghostty config on macOS and the test file
from #8651. The font loaded as fallback for CJK is PingFang SC.
**1.2.0**
<img width="592" height="301" alt="Screenshot 2025-09-17 at 09 51 43"
src="https://github.com/user-attachments/assets/e553885d-009a-4205-88c9-24747b195211"
/>
**This PR**
<img width="592" height="301" alt="Screenshot 2025-09-17 at 09 51 25"
src="https://github.com/user-attachments/assets/3a8e8d95-ec0a-4d23-a5f0-85b2f47253e3"
/>
[^1]: Note that this may be different from the difference between the
nominal ascent - descent, as non-letter ASCII characters often exceed
the ascender and descnder lines, and fonts often bake the line gap into
the ascent/descent and set `line_gap` to zero, as per official
recommendations like those from Google Fonts:
https://simoncozens.github.io/gf-docs/metrics.html
Fixes#8683
The selection scrolling logic should only depend on the y value of the
cursor position, not the x value. This presents unwanted scroll
behaviors, such as reversing the scroll direction which was just a side
effect of attempting to scroll tick to begin with.
Fixes#8683
The selection scrolling logic should only depend on the y value of the
cursor position, not the x value. This presents unwanted scroll
behaviors, such as reversing the scroll direction which was just a side
effect of attempting to scroll tick to begin with.
This was a very common pitfall for users. The new logic will reload the
font-size at runtime, but only if the font wasn't manually set by the
user using actions such as `increase_font_size`, `decrease_font_size`,
or `set_font_size`. The `reset_font_size` action will reset our state to
assume the font-size wasn't manually set.
This was requested by the Omarchy project since their themes also can
adjust font size. It makes sense to me.
I also updated a comment about `font-family` not reloading at runtime;
this wasn't true even prior to this commit.
This was a very common pitfall for users. The new logic will reload the
font-size at runtime, but only if the font wasn't manually set by the
user using actions such as `increase_font_size`, `decrease_font_size`,
or `set_font_size`. The `reset_font_size` action will reset our state
to assume the font-size wasn't manually set.
I also updated a comment about `font-family` not reloading at runtime;
this wasn't true even prior to this commit.
Fixes#8667
The binding `a=text:=` didn't parse properly.
This is a band-aid solution. It works and we have test coverage for it
thankfully. Longer term we should move the parser to a fully
state-machine based parser that parses the trigger first then the
action, to avoid these kind of things.
Fixes#8667
The binding `a=text:=` didn't parse properly.
This is a band-aid solution. It works and we have test coverage for it
thankfully. Longer term we should move the parser to a fully
state-machine based parser that parses the trigger first then the
action, to avoid these kind of things.
Added some prepositions not previously added and
changed a word to be more accurate to the portuguese meaning
---------
Signed-off-by: Nilton Perim Neto <niltonperimneto@gmail.com>
Config contains the command, working directory, and environment
variables intended to be passed to the new split, but it looks like we
forgot to include it as an argument in this branch.
Discussion: https://github.com/ghostty-org/ghostty/discussions/8637
Config contains the command, working directory, and environment
variables intended to be passed to the new split, but it looks like we
forgot to include it as an argument in this branch.
Discussion: https://github.com/ghostty-org/ghostty/discussions/8637
Fixes#8616
macOS 26 (as of RC1) has some pathological performance bug where the
terminal becomes unusably slow after some period of time. We aren't 100%
sure what triggers the slowdown, but it is app-wide (new tabs or windows
don't resolve it) and Instruments traces point directly to
NSAutoFillHeuristicController. Specifically, to the `debounceTextUpdate`
selector.
This is all not documented as far as I can find and also not open
source, so I have no idea what's going on.
The best I can tell is that the NSAutoFillHeuristicController has
something to do with enabling heuristic-based autofill such as SMS auth
codes in text input fields. I don't know what is causing it to go
haywire.
SMS autofill is not desirable in a terminal app, nor is any of the other
automatic autofill in macOS I know of (contact info, passwords, etc.).
So, we can just disable it.
This default isn't documented but I found it via a strings dump of the
AppKit binary blob and comparing it to the disassembly to see how it is
used. In my limited testing, this seems to work around the problem.
Fixes#8616
macOS 26 (as of RC1) has some pathological performance bug where the
terminal becomes unusably slow after some period of time. We aren't 100%
sure what triggers the slowdown, but it is app-wide (new tabs or windows
don't resolve it) and Instruments traces point directly to
NSAutoFillHeuristicController. Specifically, to the `debounceTextUpdate`
selector.
This is all not documented as far as I can find and also not open
source, so I have no idea what's going on.
The best I can tell is that the NSAutoFillHeuristicController has
something to do with enabling heuristic-based autofill such as SMS auth
codes in text input fields. I don't know what is causing it to go
haywire.
SMS autofill is not desirable in a terminal app, nor is any of the other
automatic autofill in macOS I know of (contact info, passwords, etc.).
So, we can just disable it.
This default isn't documented but I found it via a strings dump of the
AppKit binary blob and comparing it to the disassembly to see how it is
used. In my limited testing, this seems to work around the problem.
This fixes an issue where new tabs would not have the proper transparent
background set whilst in native fullscreen. This is because in native
fullscreen, the NSTitlebarView always is visible, so our guard was
preventing us from setting it before.
This fixes an issue where new tabs would not have the proper transparent
background set whilst in native fullscreen. This is because in native
fullscreen, the NSTitlebarView always is visible, so our guard was
preventing us from setting it before.
Closes#8608
iterm2_themes now provides terminal-specific releases, this reduces the
size of the dependency from 55MB->53kB.
- Update the `.github/workflows/update-colorschemes.yml` to track the
latest release and fetch the ghostty-specific theme archive.
Fixes#8568
This will hide snap issues from PRs which is not ideal but we can
address that in the future. We still run snap CI for main.
This more importantly ensures that CI can be green for maintainers to
merge.
Use relative cluster positioning to allow identical texts runs in
different row positions to share the same cache entry.
I am opening this PR clean w/o the cache size change. There could be
some benefit to a larger 256->512 shaper cache, but this still performs
amazingly well and I don't know the full memory impacts of moving the
cache size up.
https://github.com/ghostty-org/ghostty/discussions/8547#discussioncomment-14329590
This is a hacky fix to fix some visual glitches when titlebar tabs is on
and we're using the `move_tab` keybinding action (I test via the command
palette).
There is probably a more graceful way to fix this but this might be good
enough for a 1.2 to fix a very obviously nasty UI render.
This is a hacky fix to fix some visual glitches when titlebar tabs is on
and we're using the `move_tab` keybinding action (I test via the command
palette).
There is probably a more graceful way to fix this but this might be good
enough for a 1.2 to fix a very obviously nasty UI render.
Fixes#8497
This works on every other supported version of macOS but doesn't work on
macOS tahoe. Putting it on the next event loop tick works at least on
Sequoia and Tahoe so let's just do that.
Fixes#8497
This works on every other supported version of macOS but doesn't work on
macOS tahoe. Putting it on the next event loop tick works at least on
Sequoia and Tahoe so let's just do that.
Replaces #7952Fixes#7951
This reimplements our color operation parsing completely to make it
fully compatible (as far as I can tell) with xterm. Our previous
implementation had numerous problems, I think because we kept addressing
singular compatibility issues as they were experienced in the field
rather than doing a proper thoughtful audit compared to the xterm
implementation. This PR does that audit.
**Specifically, this updates/adds: OSC 4, 5, 10-19, 104, 105, 110-119.**
To ease maintenance, understanding, and testing, I've pulled color
operation parsing out into a separate file and function that operates on
the full buffered OSC command. This is similar to Kitty protocols
previously. This hurts performance but that's acceptable to me for now
while we get compatibility down and test coverage added.
We can address more performance later if it becomes a bottleneck, but
these color operations are pretty rare.
I've associated each test with a `printf` command you can run in xterm
to compare.
## Xterm Divergence
We purposely diverge from xterm in some scenarios:
- Whitespace is allowed around x11 color names. Kitty allows this.
- Invalid index values for 104/105 are ignored. xterm typically halts
processing. Kitty allows this.
## TODO
- [x] Update our parser to use the new color parsing functions
- [x] Update the stream handler to use the new types
- [x] Fix our stream handler to emit on response per query
These keys are present in some old unix keyboards, but more importantly,
their keycodes can be mapped to physical keys in modern programmable
keyboards.
Using them in Linux is a way to be able to have the same keys for
copy/pasting in GUI apps and in terminal apps instead of switching
between ctrl-c/ctrl-v and ctrl-shift-c/ctrl-shift-v.
These keys are present in some old unix keyboards, but more importantly,
their keycodes can be mapped to physical keys in modern programmable
keyboards.
Using them in Linux is a way to be able to have the same keys for
copy/pasting in GUI apps and in terminal apps instead of switching between
ctrl-c/ctrl-v and ctrl-shift-c/ctrl-shift-v.
this test previously didn't fail when accessing freed members of config
because deiniting `command_arena` was a no-op; `command_arena` was
derived from `arena`, which allocated memory after `command_arena` was
created/used
this test previously didn't fail when accessing freed members of config
because deiniting `command_arena` was a no-op; `command_arena` was derived
from `arena`, which allocated memory after `command_arena` was created/used
This aligns the `buf` of `4096` bytes in the benchmarks to the cache
line, to ensure a consistent number of cache lines are used, and also to
avoid any sub-`usize` alignment issues as seen in
https://github.com/ghostty-org/ghostty/pull/8548.
This has less of an effect as
https://github.com/ghostty-org/ghostty/pull/8548, and looking at the
before and after of the current benchmarks in the repo doesn't show any
noticeable difference.
In my case, I've been comparing the `table` option with [uucode in this
branch](https://github.com/ghostty-org/ghostty/compare/main...jacobsandlund:jacob/uucode?expand=1),
and I did see a difference.
### Before
I ran the before code several times (6 with the exact same binary, but
several more with essentially the same code), always getting something
like this, with `table` edging out `uucode` by something like 3-4ms:
```
Benchmark 1: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
Time (mean ± σ): 927.8 ms ± 1.3 ms [User: 883.7 ms, System: 42.5 ms]
Range (min … max): 926.0 ms … 929.8 ms 10 runs
Benchmark 2: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
Time (mean ± σ): 930.9 ms ± 1.4 ms [User: 886.8 ms, System: 42.5 ms]
Range (min … max): 928.5 ms … 933.4 ms 10 runs
```
### After
After this change, it shows `uucode` coming in at 10-11ms (~1%) faster:
```
Benchmark 1: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
Time (mean ± σ): 930.6 ms ± 1.3 ms [User: 886.5 ms, System: 42.4 ms]
Range (min … max): 928.9 ms … 932.4 ms 10 runs
Benchmark 2: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
Time (mean ± σ): 920.1 ms ± 1.4 ms [User: 876.3 ms, System: 42.1 ms]
Range (min … max): 918.4 ms … 923.3 ms 10 runs
Summary
zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode ran
1.01 ± 0.00 times faster than zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
```
This ~1% faster time checks out, since from looking at the assembly,
it's an exact match minus this small place where the compiler can
optimize `uucode` a little better:
```
# both table.asm/uucode.asm:
140 const high = cp >> 8;
141 const low = cp & 0xFF;
** 142 return self.stage3[self.stage2[self.stage1[high] + low]];
<+464>: ubfx x12, x11, #8, #13
<+468>: ldrh w12, [x27, x12, lsl #1]
<+472>: add x11, x28, w11, uxtb #1
<+476>: ldrh w11, [x11, x12, lsl #1]
# table.asm:
<+480>: lsl x11, x11, #1
** 158 table.get(@intCast(cp)).width);
159 }
160 }
<+484>: ldrb w11, [x22, x11]
# uucode.asm:
** 148 return @field(data(stages, cp), name);
<+480>: ldrh w11, [x22, x11, lsl #1]
```
### More confusion with showing addresses
Confusingly, when I added `std.debug.print("buf addr={}\n",
.{@intFromPtr(&buf)})` to show the addresses, this somehow made the
`before` case show `uucode` as being faster. Then, when I added
alignment, `uucode` and `table` were taking about the same time
(**edit:** _uucode was only ~4 ms faster, but see more in "Edit: more
investigation"_)
If I run without the `std.debug.print` and with `--show-output`, the
times are different, so just making a note of this.
```
Benchmark 1: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
Time (mean ± σ): 904.2 ms ± 1.2 ms [User: 884.6 ms, System: 40.3 ms]
Range (min … max): 902.8 ms … 906.1 ms 10 runs
Benchmark 2: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
Time (mean ± σ): 892.7 ms ± 2.0 ms [User: 873.2 ms, System: 40.1 ms]
Range (min … max): 887.9 ms … 895.6 ms 10 runs
Summary
zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode ran
1.01 ± 0.00 times faster than zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
```
I think, even with this confusing case, aligning is going to be more
consistent than not.
### Edit: more investigation
I wasn't satisfied with the discovery that adding `std.debug.print` made
this difference and I wanted to dig in and figure out exactly what's
going on, but I didn't get a satisfactory answer. Here's what I tried:
* I compared the un-aligned addresses from `stepTable` and `stepUucode`,
but both seemed similar (not aligned to 128, different each run, but
aligned to 8). Note though that `uucode` was running ~1% faster still,
similar to the aligned case even though here it was un-aligned.
* Instead of doing `std.debug.print` in the step function, I printed in
teardown, just in case. This had no difference in the unaligned case,
but with alignment it brought the ~4 ms faster `uucode` (as noted above)
back closer to the original "after" at around 11-12 ms faster (~1%).
* I forced the `buf` in `stepUucode` to not be aligned (e.g. by making
it `= other_aligned_buf[3..4096 + 3]`). Still it was ~1% faster.
* I compared the assembly of `stepTable` and `stepUucode` for both
aligned and not aligned cases, including doing a diff of the diff of
these two across aligned and not aligned. The only difference between
`stepTable` and `stepUucode` is what's noted above, and nothing stood
out in the double diff.
* I tried going back to the original un-aligned non-printing code, but
then swapped the lines that get from `table` or `uucode`, so that
`stepTable` and `stepUucode` were actually doing the opposite. And the
result is`stepTable` (actually `uucode`) was 10-11 ms (~1%) faster, just
like the aligned case!
In summary, I wasn't able to replicate the original benchmark behavior
_and print out buffer addresses that pointed to alignment being the
issue_. I still feel like in theory aligning the buffer ought to make
the benchmark more reliable, and indeed the original un-aligned version
gives the result that is more of an outlier, but the evidence here is
weak, so I'm alright if we stick with the status quo and close. I think
a lesson here is benchmarks are hard to get precise.
Release notes at:
https://github.com/vancluever/z2d/blob/v0.8.0/CHANGELOG.md
This is a small update and likely contains nothing related to Ghostty,
but I wanted to make sure it got in before the 1.2.0 release since it
contains the removal of non-functional code that wasn't compiling, just
for correctness' sake.
Also if for some reason ARGB/XRGB is needed for some utility purpose,
it's there now. 🙂
Release notes at:
https://github.com/vancluever/z2d/blob/v0.8.0/CHANGELOG.md
This is a small update and likely contains nothing related to Ghostty,
but I wanted to make sure it got in before the 1.2.0 release since it
contains the removal of non-functional code that wasn't compiling, just
for correctness' sake.
Also if for some reason ARGB/XRGB is needed for some utility purpose,
it's there now. :)
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.16 to 1.2.17.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/namespacelabs/nscloud-cache-action/releases">namespacelabs/nscloud-cache-action's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.17</h2>
<h2>What's Changed</h2>
<ul>
<li>Delete existing files at path before creating bind mount, this was
already handled correctly for existing directories but not for
files</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/namespacelabs/nscloud-cache-action/compare/v1.2.16...v1.2.17">https://github.com/namespacelabs/nscloud-cache-action/compare/v1.2.16...v1.2.17</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a289cf5d2f"><code>a289cf5</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/33">#33</a>
from namespacelabs/delete-existing-files-at-path-befor...</li>
<li><a
href="3851f57081"><code>3851f57</code></a>
Delete existing files at path before creating bind mount</li>
<li><a
href="58efedf646"><code>58efedf</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/32">#32</a>
from namespacelabs/delete-existing-files-at-path-befor...</li>
<li><a
href="5e60691b8f"><code>5e60691</code></a>
Delete existing files at path before creating bind mount</li>
<li>See full diff in <a
href="https://github.com/namespacelabs/nscloud-cache-action/compare/v1.2.16...a289cf5d2fcd6874376aa92f0ef7f99dc923592a">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.6.0 to 31.6.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.6.1</h2>
<h2>What's Changed</h2>
<ul>
<li>ci: adjust oldest supported installer for macos-15 by <a
href="https://github.com/sandydoo"><code>@sandydoo</code></a> in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/252">cachix/install-nix-action#252</a></li>
<li>nix: 2.31.0 -> 2.31.1 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/253">cachix/install-nix-action#253</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31...v31.6.1">https://github.com/cachix/install-nix-action/compare/v31...v31.6.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7be5dee142"><code>7be5dee</code></a>
docs: update the readme</li>
<li><a
href="150afeae6c"><code>150afea</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/253">#253</a>
from cachix/create-pull-request/patch</li>
<li><a
href="cdda9d991c"><code>cdda9d9</code></a>
nix: 2.31.0 -> 2.31.1</li>
<li><a
href="6f18c7d1a1"><code>6f18c7d</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/252">#252</a>
from cachix/fix-old-installer-darwin</li>
<li><a
href="f0f3cc651e"><code>f0f3cc6</code></a>
ci: adjust oldest supported installer for macos-15</li>
<li>See full diff in <a
href="56a7bb7b56...7be5dee142">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
* Only update appcast (trigger macOS updates) on `main`-branch triggers
* Echo the release URLs for download as part of the job
* Remove the `release-pr` workflow. We can now use `release-tip`
manually dispatched on a branch because it won't update the appcast.
Fixes#8549
This also brings the release tip workflow more in line with the release
tag workflow by using a setup job to create outputs that are reused by
the other jobs.
This PR was almost fully written by AI (Amp) because being a YAML
engineer fucking sucks. I understand GHA and the changes look good to
me, but it's hard to tell until the job is run, AI or not. Full
prompt/context here:
https://ampcode.com/threads/T-e2d431ad-8be8-46d2-aaa3-9fae71f9ff31
Without this change, a phantom space appears after any character with
default emoji presentation that is converted to text with VS15. The only
other terminal I know of that respects variation selectors is Kitty, and
it walks the cursor back, which feels like the best choice, since that
way the behavior is observable (no way to know if the terminal supports
variation selectors otherwise without hard-coding that info per term)
and "dumb" programs like `cat` will output things correctly, and not
gain a phantom space after any VS15'd emoji.
> [!NOTE]
> ### Tests should be added for this behavior, including edge cases like
with cursor pending wrap
Without this change, a phantom space appears after any character with
default emoji presentation that is converted to text with VS15. The only
other terminal I know of that respects variation selectors is Kitty, and
it walks the cursor back, which feels like the best choice, since that
way the behavior is observable (no way to know if the terminal supports
variation selectors otherwise without hard-coding that info per term)
and "dumb" programs like `cat` will output things correctly, and not
gain a phantom space after any VS15'd emoji.
I've been playing with benchmarks over in my [branch swapping out
ziglyph for
uucode](https://github.com/ghostty-org/ghostty/compare/main...jacobsandlund:jacob/uucode?expand=1),
and I ran into an interesting issue where benchmarks were giving odd
numbers.
TL;DR: writing to `buf[0]` ends up slowing down the benchmark in
inconsistent ways because it's the same buffer that's being written and
read in the loop, so switching to `std.mem.doNotOptimizeAway` fixes
this.
## Full story:
I ran the `codepoint-width` benchmark with the following (and also did
similarly for `grapheme-bench` and `is-symbol`):
```
zig-out/bin/ghostty-gen +utf8 | head -c 200000000 > data.txt
hyperfine --warmup 4 'zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table' 'zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode'
```
... and I was surprised to see that `uucode` was 3% slower than Ghostty,
despite similar implementations. I debugged this, bringing the `uucode`
implementation to the exact same assembly (minus offsets) as Ghostty,
even re-using the same table data (fun fact I learned is that even
though these tables are large, zig or LLVM saw they were byte-by-byte
equivalent and optimized them down to one table). Still though, 3%
slower.
Then I realized that if I wrote to a separate `buf` on `self` the
difference went away, and I figured out it's this writing to `buf[0]`
that is tripping up the CPU, because in the next outer loop it'll write
over that again when reading from the data file, and then it's read as
part of getting the code point.
### with buf[0]
```
Benchmark 1: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
Time (mean ± σ): 944.7 ms ± 0.8 ms [User: 900.2 ms, System: 42.8 ms]
Range (min … max): 943.4 ms … 945.9 ms 10 runs
Benchmark 2: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
Time (mean ± σ): 974.0 ms ± 0.7 ms [User: 929.3 ms, System: 43.1 ms]
Range (min … max): 973.3 ms … 975.2 ms 10 runs
Summary
zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table ran
1.03 ± 0.00 times faster than zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
```
### with mem.doNotOptimizeAway
```
Benchmark 1: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
Time (mean ± σ): 929.4 ms ± 2.7 ms [User: 884.8 ms, System: 43.0 ms]
Range (min … max): 926.7 ms … 936.3 ms 10 runs
Benchmark 2: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
Time (mean ± σ): 931.2 ms ± 2.5 ms [User: 886.6 ms, System: 42.9 ms]
Range (min … max): 927.3 ms … 935.7 ms 10 runs
Summary
zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table ran
1.00 ± 0.00 times faster than zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
```
### with buf[0], mode = .uucode
Another interesting thing is that with `buf[0]`, it's highly dependent
on the offsets somehow. If I switched the default mode line from `mode:
Mode = .noop` to `mode: Mode = .uucode`, it shifts the offsets ever so
slightly and even though that default mode is not getting used (since
it's passed in), it flips the results of the benchmark around:
```
Benchmark 1: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
Time (mean ± σ): 973.3 ms ± 2.2 ms [User: 928.9 ms, System: 42.9 ms]
Range (min … max): 968.0 ms … 975.9 ms 10 runs
Benchmark 2: zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode
Time (mean ± σ): 945.8 ms ± 1.4 ms [User: 901.2 ms, System: 42.8 ms]
Range (min … max): 943.5 ms … 948.5 ms 10 runs
Summary
zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=uucode ran
1.03 ± 0.00 times faster than zig-out/bin/ghostty-bench +codepoint-width --data=data.txt --mode=table
```
looking at the assembly with `mode: Mode = .noop`:
```
# table.txt:
165 // away
** 166 buf[0] = @intCast(width);
ghostty-bench[0x100017370] <+508>: strb w11, [x21, #0x4]
ghostty-bench[0x100017374] <+512>: b 0x100017288 ; <+276> at CodepointWidth.zig:168:9
ghostty-bench[0x100017378] <+516>: mov w0, #0x0 ; =0
# uucode.txt:
** 229 buf[0] = @intCast(width);
ghostty-bench[0x1000177bc] <+508>: strb w11, [x21, #0x4]
ghostty-bench[0x1000177c0] <+512>: b 0x1000176d4 ; <+276> at CodepointWidth.zig:231:9
ghostty-bench[0x1000177c4] <+516>: mov w0, #0x0 ; =0
```
vs `mode: Mode = .uucode`:
```
# table.txt:
** 166 buf[0] = @intCast(width);
ghostty-bench[0x100017374] <+508>: strb w11, [x21, #0x4]
ghostty-bench[0x100017378] <+512>: b 0x10001728c ; <+276> at CodepointWidth.zig:168:9
ghostty-bench[0x10001737c] <+516>: mov w0, #0x0 ; =0
# uucode.txt:
** 229 buf[0] = @intCast(width);
ghostty-bench[0x1000177c0] <+508>: strb w11, [x21, #0x4]
ghostty-bench[0x1000177c4] <+512>: b 0x1000176d8 ; <+276> at CodepointWidth.zig:231:9
ghostty-bench[0x1000177c8] <+516>: mov w0, #0x0 ; =0
```
... shows the only difference is the offsets, which somehow have a large
impact on the result of the benchmark.
This fixes an issue I noticed where manually launching the `ghostty`
binary in the app bundle via the CLI would open the app but not create a
window or bring it to the front.
This fixes an issue I noticed where manually launching the `ghostty`
binary in the app bundle via the CLI would open the app but not create a
window or bring it to the front.
Use fast hash function on key for better distribution.
Direct compare glyph in eql to avoid Packed.from() if not neccessary.
16% -> 6.4% reduction during profiling runs.
I noticed that there was an off-by-one error in cell height adjustment
when the number of pixels to add/subtract is odd. The metrics measured
from the top would be shifted by one less than they should, so, for
example, the underline position would move one pixel closer to the
baseline than it had been (or one pixel further away if subtracting).
Also noticed that the overline position was missing here, so added that.
Use fast hash function on key for better distribution.
Direct compare glyph in eql to avoid Packed.from() if not neccessary.
16% -> 6.4% reduction during profiling runs.
Fixes#5934
This was never confirmed to be a real issue on GTK, but it is
theoretically possible and good hygiene in general. Typically, we'd get
the title through a binding which comes from a bindinggroup which comes
from the active surface in the active tab. All of this takes multiple
event loop ticks to settle, if you will.
This commit changes it so that if an explicit, static title is set, we
set that title on startup before the window is mapped. The syncing still
happens later, but at least the window will have a title from the
initialization.
Fixes#5934
This was never confirmed to be a real issue on GTK, but it is
theoretically possible and good hygience in general. Typically, we'd get
the title through a binding which comes from a bindinggroup which comes
from the active surface in the active tab. All of this takes multiple
event loop ticks to settle, if you will.
This commit changes it so that if an explicit, static title is set, we
set that title on startup before the window is mapped. The syncing still
happens later, but at least the window will have a title from the
initialization.
Fixes#8533
Replace the usage of `Stacked` for error pages with programmatically
swapping the child of the `adw.Bin`.
I regret to say I don't know the root cause of this. I only know that
the usage of `Stacked` plus `Gtk.Paned` and the way we programmatically
change the paned position and stack child during initialization causes
major issues.
This change isn't without its warts, too, and you can see them heavily
commented in the diff.
(1) We have to workaround a GTK template double-free bug that is well
known to us: if you bind a template child that is also the direct child
of the template class, GTK does a double free on dispose. We workaround
this by removing our child in dispose. Valgrind verifies the fix.
(2) We have to workaround an issue where setting an `Adw.Bin` child
during a glarea realize causes some kind of critical GTK error that
results in a hard crash. We delay changing our bin child to an idle
tick.
Fixes#8533
Replace the usage of `Stacked` for error pages with programmatically
swapping the child of the `adw.Bin`.
I regret to say I don't know the root cause of this. I only know that
the usage of `Stacked` plus `Gtk.Paned` and the way we programmatically
change the paned position and stack child during initialization causes
major issues.
This change isn't without its warts, too, and you can see them heavily
commented in the diff.
(1) We have to workaround a GTK template double-free bug that is well known
to us: if you bind a template child that is also the direct child of the
template class, GTK does a double free on dispose. We workaround this by
removing our child in dispose. Valgrind verifies the fix.
(2) We have to workaround an issue where setting an `Adw.Bin` child
during a glarea realize causes some kind of critical GTK error that
results in a hard crash. We delay changing our bin child to an idle
tick.
Detecting the launch source frequently failed because various launchers
fail to sanitize the environment variables that Ghostty used to detect
the launch source. For example, if your desktop environment was launched
by `systemd`, but your desktop environment did not sanitize the
`INVOCATION_ID` or the `JOURNAL_STREAM` environment variables, Ghostty
would assume that it had been launched by `systemd` and behave as such.
This led to complaints about Ghostty not creating new windows when users
expected that it would.
To remedy this, Ghostty no longer does any detection of the launch
source. If your launch source is something other than the CLI, it must
be explicitly speciflied on the CLI. All of Ghostty's default desktop
and service files do this. Users or packagers that create custom desktop
or service files will need to take this into account.
On GTK, the `desktop` setting for `gtk-single-instance` is replaced with
`detect`. `detect` behaves as `gtk-single-instance=true` if one of the
following conditions is true:
1. If no CLI arguments have been set.
2. If `--launched-from` has been set to `desktop`, `dbus`, or `systemd`.
Otherwise `detect` behaves as `gtk-single-instance=false`.
This removes `launched-from` entirely and moves our `gtk-single-instance`
detection logic to assume true unless we detect CLI instead of assume
false unless we detect desktop/dbus/systemd.
The "assume true" scenario for single instance is desirable because
detecting a CLI instance is much more reliable.
Removing `launched-from` fixes an issue where we had a
difficult-to-understand relationship between `launched-from`,
`gtk-single-instance`, and `initial-window`. Now, only
`gtk-single-instance` has some hueristic logic. And `initial-window`
ALWAYS sends a GTK activation signal regardless of single instance or
not.
As a result, we need to be explicit in our systemd, dbus, desktop files
about what we want Ghostty to do, but everything works as you'd mostly
expect.
Now, if you put plain old `ghostty` in your terminal, you get a new
Ghostty instance. If you put it anywhere else, you get a GTK single
instance activation call (either creates a first instance or opens a new
window in the existing instance). Works for launchers and so on.
Detecting the launch source frequently failed because various launchers
fail to sanitize the environment variables that Ghostty used to
detect the launch source. For example, if your desktop environment was
launched by `systemd`, but your desktop environment did not sanitize the
`INVOCATION_ID` or the `JOURNAL_STREAM` environment variables, Ghostty
would assume that it had been launched by `systemd` and behave as such.
This led to complaints about Ghostty not creating new windows when users
expected that it would.
To remedy this, Ghostty no longer does any detection of the launch
source. If your launch source is something other than the CLI, it must
be explicitly speciflied on the CLI. All of Ghostty's default desktop
and service files do this. Users or packagers that create custom desktop
or service files will need to take this into account.
On GTK, the `desktop` setting for `gtk-single-instance` is replaced with
`detect`. `detect` behaves as `gtk-single-instance=true` if one of the
following conditions is true:
1. If no CLI arguments have been set.
2. If `--launched-from` has been set to `desktop`, `dbus`, or `systemd`.
Otherwise `detect` behaves as `gtk-single-instance=false`.
This enables agents (namely Amp) to use `/gh-issue <number/url>` to
begin diagnosing a GitHub issue, explaining the problem, and suggesting
a plan of action. This action explicitly prompts the AI to not write
code.
**You can run this manually too,** for testing or curiosity or for
pasting into another LLM. Execute it like any other script:
`.agents/command/gh-issue <issue>`
I've used this manually for months with good results, so now I'm
formalizing it in the repo for other contributors.
Example diagnosing #8523:
https://ampcode.com/threads/T-3e26e8cc-83d1-4e3c-9b5e-02d9111909a7
**I'm going to be highly selective about integrating repository-level
commands.** I think guiding AI contributors in the right direction is
going to result in less AI slop being sent to us. But I want to only add
commands that maintainers use and also can vouch for. The best way to
vouch is to share something like an Amp thread link that shows it
working on real data.
Ironically, no AI was used to write this PR. I did consult Claude Chat
for help on some Nu syntax, but verified it manually with the Nu manual.
This enables agents (namely Amp) to use `/gh-issue <number/url>` to
begin diagnosing a GitHub issue, explaining the problem, and suggesting
a plan of action. This action explicitly prompts the AI to not write
code.
I've used this manually for months with good results, so now I'm
formalizing it in the repo for other contributors.
Example diagnosing #8523:
https://ampcode.com/threads/T-3e26e8cc-83d1-4e3c-9b5e-02d9111909a7
Fixes#8483, fixes#2991
With this change, `font.face.getMetrics` is now infallible, and real
bitmap fonts are properly handled and can be configured as the primary
font (or used as fallbacks), as long as the backend (FreeType or
CoreText) knows how to interpret them, since we now fall back on the
backend for any metrics we can't extract from sfnt tables (which means
we don't need any to be present in the first place).
Also, doing this uncovered a double-free issue in our FreeType
`renderGlyph` code, which thankfully wasn't too hard to track down and
fix.
> [!NOTE]
> We should vendor a true bitmap font in each of the native formats
supported by each backend and add tests for the metrics being computed
right and the glyphs being rendered correctly. Idk if we wanna block
this PR on that or not.
This is a very minimal change to fix a regression from when the
constraints were reworked (moved off the GPU and on to the CPU), where
we weren't constraining dingbats anymore. This fixes that and also adds
several other Unicode blocks to also treat as symbols.
In the future (post-1.2) I'll make a more comprehensive set of custom
constraints that differ depending on a hand-rolled list of character
attributes.
|Before|After|
|-|-|
|<img width="1708" height="2096" alt="image"
src="https://github.com/user-attachments/assets/8ce593ac-f032-4c75-80e1-030dd301115d"
/>|<img width="1708" height="2096" alt="image"
src="https://github.com/user-attachments/assets/06dc2307-342d-4ea2-959d-7416eee6ddc2"
/>|
You'll notice that with spaces in between (so each glyph can be up to 2
cells wide), there's very little change with a font with wider cells,
there would be more of a change for people with narrower fonts. The real
star of the show is when they're all shoved together and need to be
constrained to 1 cell; it's utter chaos without constraints, but with
constraints it's nice and orderly (some glyphs get too small to read
well, but there's nothing we can really do about that, users should
really have a font like Iosevka Fixed installed on their system if they
want a good source for terminal-compatible symbol glyphs)
This caused a malloc fault due to a double free when deiniting the face
if we didn't do this, which makes sense- making it possible to actually
load bitmap fonts revealed this bug which was sitting dormant the whole
time before that ever since I refactored the freetype rasterization.
Before we had a bad day if we tried to get the metrics of a bitmap font,
which would happen if we ever used one as fallback because we started
doing it for all fonts when we added fallback font scaling. This is a
pretty easy fix and finally allows users to configure true bitmap fonts
as their primary font as long as FreeType/CoreText can handle it.
Fixes#8415
When using hidden titlebar with non-native fullscreen, the window would
lose focus after entering the first command. This occurred because:
1. Shell commands update the window title
2. Title changes trigger reapplyHiddenStyle()
3. reapplyHiddenStyle() re-adds .titled to the style mask
4. Style mask changes during fullscreen confuse AppKit, causing focus
loss
Fixed by adding a guard to skip titlebar restyling while fullscreen is
active, using terminalController.fullscreenStyle.isFullscreen for proper
detection of both native and non-native fullscreen modes.
AI disclaimer:
https://ampcode.com/threads/T-c4ef59cc-1232-4fa5-8f09-c65724ee84d3 But I
hand-modified the end.
When using hidden titlebar with non-native fullscreen, the window would
lose focus after entering the first command. This occurred because:
1. Shell commands update the window title
2. Title changes trigger reapplyHiddenStyle()
3. reapplyHiddenStyle() re-adds .titled to the style mask
4. Style mask changes during fullscreen confuse AppKit, causing focus loss
Fixed by adding a guard to skip titlebar restyling while fullscreen is
active, using terminalController.fullscreenStyle.isFullscreen for
proper detection of both native and non-native fullscreen modes.
https://ampcode.com/threads/T-c4ef59cc-1232-4fa5-8f09-c65724ee84d3
I've been using agents a lot more with Ghostty and so are contributors.
Ghostty welcomes AI contributions (but they must be disclosed as AI
assisted), and this AGENTS.md will help everyone using agents work
better with the codebase.
This AGENTS.md has thus far been working for me very successfully,
despite being simple. I suspect we'll add to it as time goes on but I
also want to avoid making it too large and polluting the context.
**Changes should not be made to `AGENTS.md` unless it demonstrably
improves agent behavior.** To demonstrate agent behavior, I'd prefer
sharing an amp thread before and after the change with the same prompt
and model to show an improvement.
This has no meaningful functionality yet, it was one of the paths I was
looking at for #8505 but didn't pursue further. But I still think that
this makes more sense in general for the macOS app and will likely be
more useful later.
This has no meaningful functionality yet, it was one of the paths I was
looking at for #8505 but didn't pursue further. But I still think that
this makes more sense in general for the macOS app and will likely be
more useful later.
I've been using agents a lot more with Ghostty and so are contributors.
Ghostty welcomes AI contributions (but they must be disclosed as AI
assisted), and this AGENTS.md will help everyone using agents work
better with the codebase.
This AGENTS.md has thus far been working for me very successfully,
despite being simple. I suspect we'll add to it as time goes on but I
also want to avoid making it too large and polluting the context.
Fixes#8356
Zoom state should encode as a path so that it can be mapped to a
reference to the node in `root`. Previously, we were encoding a full
node which was instantiating an extra terminal on restore.
AI disclaimer: Claude code did the Codable implementation.
Fixes#8356
Zoom state should encode as a path so that it can be mapped to a
reference to the node in `root`. Previously, we were encoding a full
node which was instantiating an extra terminal on restore.
Make the default keybind for copy_to_clipboard performable. This allows
TUIs to receive events when keybinds aren't used, for example cmd+c on
macos for copy, or ctrl+shift+c elsewhere.
Disclosure: This commit was made with the assistance of AI (ampcode.com)
Make the default keybind for copy_to_clipboard performable. This allows
TUIs to receive events when keybinds aren't used, for example cmd+c on
macos for copy, or ctrl+shift+c elsewhere.
Disclosure: This commit was made with the assistance of AI (ampcode.com)
Fixes#8495
We were incorrectly calling graceful quit under must quit scenarios.
This would do things like confirm quit by inspecting for running
processes. However, must quit scenarios (namely when all windows are
destroyed) should quit immediately without any checks because the
dispose process takes more event loop ticks to fully finish.
Fixes#8495
We were incorrectly calling graceful quit under must quit scenarios.
This would do things like confirm quit by inspecting for running
processes. However, must quit scenarios (namely when all windows are
destroyed) should quit immediately without any checks because the
dispose process takes more event loop ticks to fully finish.
Addresses issue: Add selection-clear-on-copy configuration #8407
Added configuration option `selection-clear-on-copy` that matches with
the `selection-clear-on-typing` option.
And `copy-on-select` is ignored when `selection-clear-on-copy` is true
regardless of whether `copy-on-select` is set to true or clipboard.
Also `.copy_to_clipboard` binding action was refactored to use
`copySelectionToClipboards` for consistent behavior.
> Consulted with Copilot (Claude Sonnet 4) to understand the control
flow of copy operations and help write the docs. Solution was authored
and implemented by me.
This is a small, but I think worthwhile micro optimization in style.zig,
I uncovered while investigating wider ranging optimizations in the
rendering section.
For me it results in ~4-5% increase in fps for DOOM-fire-zig benchmark,
which maximally stresses this code path.
Comparing the fields directly is actually faster than PackedStyle.
I wrote the code in style.zig, claude 4 wrote the benchmark, all PR
responses will be generated by jcm-slow-1
Style.eql Benchmark Comparison
==============================
Test: Small (1K pairs, 50% equal)
--------------------------------------------------
New implementation:
Iterations: 49937
Duration: 500.01 ms
Throughput: 99872402 comparisons/sec
Old implementation:
Iterations: 8508
Duration: 500.06 ms
Throughput: 17014026 comparisons/sec
Performance improvement:
Speedup: 5.87x
Improvement: +487.0%
Test: Medium (10K pairs, 50% equal)
--------------------------------------------------
New implementation:
Iterations: 4435
Duration: 500.09 ms
Throughput: 88684746 comparisons/sec
Old implementation:
Iterations: 850
Duration: 500.50 ms
Throughput: 16983017 comparisons/sec
Performance improvement:
Speedup: 5.22x
Improvement: +422.2%
Test: Large (50K pairs, 50% equal)
--------------------------------------------------
New implementation:
Iterations: 861
Duration: 500.41 ms
Throughput: 86030144 comparisons/sec
Old implementation:
Iterations: 171
Duration: 501.70 ms
Throughput: 17041989 comparisons/sec
Performance improvement:
Speedup: 5.05x
Improvement: +404.8%
Test: Mostly equal (10K pairs, 90% equal)
--------------------------------------------------
New implementation:
Iterations: 4608
Duration: 500.03 ms
Throughput: 92154471 comparisons/sec
Old implementation:
Iterations: 854
Duration: 500.45 ms
Throughput: 17064744 comparisons/sec
Performance improvement:
Speedup: 5.40x
Improvement: +440.0%
Test: Mostly different (10K pairs, 10% equal)
--------------------------------------------------
New implementation:
Iterations: 4065
Duration: 500.03 ms
Throughput: 81294960 comparisons/sec
Old implementation:
Iterations: 848
Duration: 500.21 ms
Throughput: 16952948 comparisons/sec
Performance improvement:
Speedup: 4.80x
Improvement: +379.5%
Test: Same flags (10K pairs, 50% equal)
--------------------------------------------------
New implementation:
Iterations: 2799
Duration: 500.00 ms
Throughput: 55979776 comparisons/sec
Old implementation:
Iterations: 859
Duration: 500.13 ms
Throughput: 17175672 comparisons/sec
Performance improvement:
Speedup: 3.26x
Improvement: +225.9%
Fixes#2473
This commit changes `ghostty_surface_ime_point` to return a full rect
with the width/height calculated for the preedit.
The `firstRect` function, which calls `ghostty_surface_ime_point` was
previously setting the width/height to zero. macOS didn't like this. We
then changed it to just hardcode it to width/height of one cell. This
worked but made it so the IME cursor didn't follow the preedit.
The result is shown in the video below. Notice the dictation icon
follows the text properly:
https://github.com/user-attachments/assets/81be8c63-9f0a-49b7-ac30-2db930beb238
Fixes#8481
Explained in code comments, basically the NF patcher can produce fonts
that have CJK characters with 1-cell advances, which screws up fallback
font scaling; fixed by not counting the ic width metric if the width of
the glyph is greater than the advance width.
> [!NOTE]
> As follow-on work to this it may be worth setting limits for scaling,
so you can't have one font scaled like twice as large as the primary
font, since that's almost always going to indicate something is very
wrong.
Fixes#2473
This commit changes `ghostty_surface_ime_point` to return a full rect
with the width/height calculated for the preedit.
The `firstRect` function, which calls `ghostty_surface_ime_point` was
previously setting the width/height to zero. macOS didn't like this. We
then changed it to just hardcode it to width/height of one cell. This
worked but made it so the IME cursor didn't follow the preedit.
When processing kitty images in a loop in a few places we were returning
under certain conditions where we should instead have just continued the
loop. This caused serious problems for kitty images, especially for apps
that used multiple images on screen at once.
... I have no clue how I originally wrote this code and didn't see such
a trivial mistake, I think I was sleep deprived or something.
Should fix#8471
When processing kitty images in a loop in a few places we were returning
under certain conditions where we should instead have just continued the
loop. This caused serious problems for kitty images, especially for apps
that used multiple images on screen at once.
... I have no clue how I originally wrote this code and didn't see such
a trivial mistake, I think I was sleep deprived or something.
This pull request adds the `--faint-opacity` option, as discussed in
#7637.
The default value of the option is also changed from `0.68` to `0.5` for
greater consistency with other popular terminal emulators.
Fix for discussion #8113
The cursor Y position value exposed to the shader uniforms was
incorrectly calculated. As per the doc in cell_text.v.glsl: In order to
get the top left of the glyph, we compute an offset based on the
bearings. The Y bearing is the distance from the bottom of the cell to
the top of the glyph, so we subtract it from the cell height to get the
y offset.
This calculation was mistakenly left out of the original code.
This will ensure that the custom shaders using
iCurrentCursor/iPreviousCursor get the correct Y coordinate representing
the top-left corner of the cursor rectangle, matching the documented
uniform behavior
This math was incorrect from the start, the previous fix helped OpenGL
but broke positioning under Metal; this commit fixes the math to be
correct under both backends and adds comments explaining exactly what's
going on.
Ghostty 1.2 needs to support GTK 4.14 because that's the version that
ships with Ubuntu 24.04.
This PR ensures that any GTK 4.16 CSS features are not used in any
static CSS and that the runtime CSS loading handles both 4.14 and 4.16+
appropriately.
Ghostty 1.2 needs to support GTK 4.14 because that's the version that
ships with Ubuntu 24.04.
This PR ensures that any GTK 4.16 CSS features are not used in any
static CSS and that the runtime CSS loading handles both 4.14 and 4.16+
appropriately.
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.16 to 1.2.17.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/namespacelabs/nscloud-cache-action/releases">namespacelabs/nscloud-cache-action's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.17</h2>
<h2>What's Changed</h2>
<ul>
<li>Delete existing files at path before creating bind mount, this was
already handled correctly for existing directories but not for
files</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/namespacelabs/nscloud-cache-action/compare/v1.2.16...v1.2.17">https://github.com/namespacelabs/nscloud-cache-action/compare/v1.2.16...v1.2.17</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a289cf5d2f"><code>a289cf5</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/33">#33</a>
from namespacelabs/delete-existing-files-at-path-befor...</li>
<li><a
href="3851f57081"><code>3851f57</code></a>
Delete existing files at path before creating bind mount</li>
<li><a
href="58efedf646"><code>58efedf</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/32">#32</a>
from namespacelabs/delete-existing-files-at-path-befor...</li>
<li><a
href="5e60691b8f"><code>5e60691</code></a>
Delete existing files at path before creating bind mount</li>
<li>See full diff in <a
href="305bfa7ea9...a289cf5d2f">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Also did some copy-editing and removed some things that haven't been
present in the codebase for a while (I thought we nuked conformance
stuff in 1.1 already...?)
Also did some copy-editing and removed some things that haven't been
present in the codebase for a while (I thought we nuked conformance
stuff in 1.1 already...?)
Right now, passing a file path to Ghostty will always execute it
unconditionally. This has various risks associated with it. I think we
can mitigate a lot of risks in the future by inspecting what is being
executed, but to be safe now we should always ask for confirmation.
Fixes#8424
This is the response that will be sent when "escape" is pressed.
This also fixes a null ptr deref that was possible when this fix wasn't
in.
Fixes#8424
This is the response that will be sent when "escape" is pressed.
This also fixes a null ptr deref that was possible when this fix wasn't
in.
Make `MAX_PARAMS` public and increase CSI parameter limit from 16 to 24.
Fix potential out-of-bounds read in SGR partial sequence extraction.
Related discussion:
https://github.com/ghostty-org/ghostty/discussions/5198
DISCLAIMER: the tests were written with Claude Code's help.
Adds tests to ensure CSI and SGR sequences with 17 or more parameters are correctly parsed, fixing a bug where later parameters were previously dropped.
Fixes#8418
This fixes issues where left/right positions would be cut off from the
menu bar. And makes it so that size 100%,100% doesn't overflow into the
non-visible space of the edge of the screen.
I didn't just copy and paste, I tested each of these code paths.
Fixes#8418
This fixes issues where left/right positions would be cut off from the
menu bar. And makes it so that size 100%,100% doesn't overflow into the
non-visible space of the edge of the screen.
- Add more comments, and make existing ones more consistent.
- Rename commands so they consitently have a `conemu_` prefix.
- Ensure that OSC 9 desktop notifications can be sent in the maximum
number of circumstances. There are still many notifications that can't
be sent because of our support for the ConEmu OSCs but that's the
tradeoff we have chosen. We recommend that you switch to OSC 777 to
ensure desktop notifications can be sent in all circumstances.
- Make sure that the tests that exercise the ConEmu OSCs have a
consistent naming structure. That will make them easier to find through
searching as well as make it easier to filter only the ConEmu OSC tests.
- Add more tests to make sure that desktop notifications are sent
properly.
- Add more comments, and make existing ones more consistent.
- Rename commands so they consitently have a `conemu_` prefix.
- Ensure that OSC 9 desktop notifications can be sent in the maximum
number of circumstances. There are still many notifications that can't
be sent because of our support for the ConEmu OSCs but that's the
tradeoff we have chosen. We recommend that you switch to OSC 777 to
ensure desktop notifications can be sent in all circumstances.
- Make sure that the tests that exercise the ConEmu OSCs have a
consistent naming structure. That will make them easier to find
through searching as well as make it easier to filter only the ConEmu
OSC tests.
- Add more tests to make sure that desktop notifications are sent
properly.
If GTK can't acquire an OpenGL context, this shows a message.
Previously, we would only log a warning which was difficult to find. The
GUI previously was the default GTK view which showed "Failed to acquire
EGL display" which was equally confusing.
This is a draft. There are TODOs (listed below).
## TODO
- [x] Disable context menu in error state
- [x] Use property to bind to unhealthy state instead of directly
setting stack child
- [x] Create a web page and put that in the error description
- [x] Set non-transparent background in error state
- [x] Bug where closing the window isn't exiting Ghostty
If GTK can't acquire an OpenGL context, this shows a message.
Previously, we would only log a warning which was difficult to find. The
GUI previously was the default GTK view which showed "Failed to acquire
EGL display" which was equally confusing.
Scrolling with a mouse on macos doesn't work very well when doing small,
single tick scrolls. macos attempts to mimic precision scrolling by
changing the magnitude of the scroll deltas based on scrolling speed.
Slow scrolls only send deltas with a magnitude of 0.1, which isn't
enough to send a single scroll event with the default scroll multiplier
of 3. Changing the scroll multiplier to 10 as a workaround (so even
single small scroll ticks are enough to register a scroll event) cause
scrolling to be way too fast if the scroll speed is ramped up.
This commit causes the yoffset delta to be rounded out to at least a
magnitude of 1 in the appropriate direction. Single scroll ticks now
register as a single vertical cell scroll event, but as scroll speed is
ramped up, the true delta reported to the surface is used again. Setting
a scroll multiplier of 1 with the changes here makes mouse scrolling
feel just as good as trackpad precision scrolling.
Scrolling with a mouse on macos doesn't work very well when doing small,
single tick scrolls. macos attempts to mimic precision scrolling by
changing the magnitude of the scroll deltas based on scrolling speed.
Slow scrolls only send deltas with a magnitude of 0.1, which isn't
enough to send a single scroll event with the default scroll multiplier
of 3. Changing the scroll multiplier to 10 as a workaround (so even
single small scroll ticks are enough to register a scroll event) cause
scrolling to be way too fast if the scroll speed is ramped up.
This commit causes the yoffset delta to be rounded out to at least a
magnitude of 1 in the appropriate direction. For small single scroll
ticks, it's enough to register a scroll event, but as scroll speed is
ramped up, the true delta reported to the surface is used again. Setting
a scroll multiplier of 1 with the changes here makes mouse scrolling
feel just as good as trackpad precision scrolling.
Fullscreen on quick terminal was failing with a crash, when it tried
to save the state of a non-existent toolbar and its accessory view
controllers.
See #7980
You can now resize the quick terminal both vertically and horizontally. To incorporate adjusting the custom secondary size on the quick terminal we needed to have the ability to resize the width (if from top, bottom, or center), and height (if from right, left, or center). The quick terminal will retain the user's manually adjusted size while the app is open. A new feature with this is that when the secondary size is adjusted (or primary if the quick terminal is center), the size will increase or decrease on both sides of the terminal.
Applying the feedback given by @pluiedev to use an enum to specify the
type of quick terminal size configuration given (pixels or percentage).
Updated the Swift code to work with the enum as well.
Added C bindings for the already existing quick-terminal-size
configuration. Created a new QuickTerminalSize struct to hold these
values in Swift. Updated the QuickTerminal implementation to use the
user's configuration if supplied. Retains defaults. Also adds support to
customize the width of the quick terminal (height if quick terminal is
set to right or left).
Fixes#8386
This is a fairly simple implementaion, there's no interactivity or
searching. It will adapt the number of columns to the available width of
the display though.
Will fallback to a plain text dump if there's no tty or the `--plain`
argument is specified on the CLI.
<img width="2112" height="1278" alt="image"
src="https://github.com/user-attachments/assets/0dbeec72-2092-4ed5-b1ed-0df43e5c64a3"
/>
Fixes#8386
This is a fairly simple implementaion, there's no interactivity or
searching. It will adapt the number of columns to the available width of
the display though.
Will fallback to a plain text dump if there's no tty or the `--plain`
argument is specified on the CLI.
Supporting command line, file menu and keybindings. Default mac shortcut
of `super + alt + o` (other)
Not able to test on Linux so excluding `close_other_tabs` from `gtk` for
now
Supporting command line, file menu and keybindings.
Default mac shortcut of `super + alt + o` (other)
Not able to test on Linux so excluding `close_other_tabs` from `gtk` for now
make a default short cut for close other tabs
- Builds with Zig 0.15 now (but still works just fine with Zig 0.14
projects).
- Fixes a double-free if nix-prefetch-git can't be found or errors out
- Adds support for generating Flatpak package metadata natively.
## Description of changes
Added Hungarian locale files, and corresponding translation
For the translation I mainly relied on my native skills, double checked
my work using LLMs.
Copilot generated summary:
This pull request introduces Hungarian language support to the
application by adding translations and updating the locale
configurations. The most important changes include the addition of
Hungarian translations in the `.po` file and registering the new locale
in the application's supported locales.
### Hungarian Language Support:
* Added Hungarian translations for various UI elements and messages in
the `po/hu_HU.UTF-8.po` file. This includes translations for prompts,
dialogs, menus, and other interface components.
* Updated the supported locales list in `src/os/i18n.zig` to include
`hu_HU.UTF-8`, enabling Hungarian as an available language option.
## Picture(s) of the translation

- Builds with Zig 0.15 now (but still works just fine with Zig
0.14 projects).
- Fixes a double-free if nix-prefetch-git can't be found or errors out
- Adds support for generating Flatpak package metadata natively.
Fixes#8229
This was a regression.
The discussion noted in #8229 requests we create a new window on the
non-fullscreen desktop but that isn't how Ghostty has behaved
historically. I bisected back and tried 1.1.3 as well and we always
created a new fullscreen window when the parent was fullscreen.
This behavior matches iTerm2. Its noteworthy that native tabbing and
Apple apps such as Terminal.app and Safari do NOT do this. For both of
these, new window creates a _tab_ when in fullscreen. I don't think
that's particularly desirable, though.
Fixes#8229
This was a regression.
The discussion noted in #8229 requests we create a new window on the
non-fullscreen desktop but that isn't how Ghostty has behaved
historically. I bisected back and tried 1.1.3 as well and we always
created a new fullscreen window when the parent was fullscreen.
This behavior matches iTerm2. Its noteworthy that native tabbing and
Apple apps such as Terminal.app and Safari do NOT do this. For both of
these, new window creates a _tab_ when in fullscreen. I don't think
that's particularly desirable, though.
Release notes at:
https://github.com/vancluever/z2d/blob/v0.7.2/CHANGELOG.md
This is mostly a bugfix release for text rendering, including a fix for
an invalid free flagged as:
https://github.com/vancluever/z2d/security/advisories/GHSA-v7f4-f3hm-282w
Note that the invalid free affects the new in-library text rendering
only and as such is likely not in use in Ghostty.
I'm anticipating *maybe* one more release after this ahead of Ghostty
1.2.0, depending on if I find any more bugs, but close to around the
release I am planning a freeze to ensure a clean versioned release
continues to be set.
Release notes at:
https://github.com/vancluever/z2d/blob/v0.7.2/CHANGELOG.md
This is mostly a bugfix release for text rendering, including a fix for
an invalid free flagged as:
https://github.com/vancluever/z2d/security/advisories/GHSA-v7f4-f3hm-282w
Note that the invalid free affects the new in-library text rendering
only and as such is likely not in use in Ghostty.
I'm anticipating *maybe* one more release after this ahead of Ghostty
1.2.0, depending on if I find any more bugs, but close to around the
release I am planning a freeze to ensure a clean versioned release
continues to be set.
Fixes#8313
The clipboard request flow can result in the apprt immediately
completing the request which itself grabs a lock. For pastes, we should
yield the lock during the clipboard request.
GTK is always async so this worked there, but we want to be resilient to
any apprt behavior here.
Fixes#8313
The clipboard request flow can result in the apprt immediately
completing the request which itself grabs a lock. For pastes, we should
yield the lock during the clipboard request.
GTK is always async so this worked there, but we want to be resilient to
any apprt behavior here.
The Quick Terminal would not appear anymore, as somewhere in the
framework the Quick Terminal Window's geometry gets corrupted when the
window is added to the UI.
This works around by caching the windows geometry and reusing it
afterwards
This might fix#7690
The Quick Terminal would not appear anymore, as somewhere
in the framework the Quick Terminal Window's geometry
gets corrupted when the window is added to the UI.
This works around by caching the windows geometry and
reusing it afterwards
fix: copy_url_to_clipboard for OSC8 hyperlinks
OSC8 links were only detected when exact platform-specific modifiers
were held (Cmd on macOS, Ctrl on Linux), but copy_url_to_clipboard
should work with either. Additionally, OSC8 links were using
selectionString() which gets visible text instead of the actual URI. Now
we use osc8URI() for OSC8 links and fall back to selectionString() for
regex-detected links.
Fixes#7499
On macOS, when using stage manager, ghostty loses focus when a running
process is terminated.
This aims to fix#8336 which has reappeared since the previously fixed
#5108.
Opened a new pr following the closure of the previous #8343
OSC8 links were only detected when exact platform-specific modifiers were held (Cmd on macOS, Ctrl on Linux), but copy_url_to_clipboard should work with either. Additionally, OSC8 links were using selectionString() which gets visible text instead of the actual URI. Now we use osc8URI() for OSC8 links and fall back to selectionString() for regex-detected links.
Fixes#7491
OSC8 links were only detected when exact platform-specific modifiers were held (Cmd on macOS, Ctrl on Linux), but copy_url_to_clipboard should work with either. Additionally, OSC8 links were using selectionString() which gets visible text instead of the actual URI. Now we use osc8URI() for OSC8 links and fall back to selectionString() for regex-detected links.
Fixes#7491
This is a follow-up to #8064, moving the activation into the async block
such that it happens after the window is created. As discussed in #8064,
this is necessary to bring only the newly created window to the front,
rather than both the previous main window and the new window.
Also made the same change for the new tab action, which also needs to
activate in case it was triggered from the dock menu or a global
keybind.
Finally, I removed the activations within AppDelegate that are redundant
now that TerminalController itself takes care of activating.
> [!NOTE]
> The change might have been intentional, and so I lack context. I
mention two ways to fix below, this PR implements the first possible
fix.
This change makes sure that the new window is focused and visible.
When commit 33d128bcff removed the
TerminalManager class and moved its functionality into
TerminalController, it accidentally removed app activation for windows
triggered by global keybinds.
How the bug works:
1. Menu actions (like File → New Window) call AppDelegate.newWindow()
which: 2. Calls TerminalController.newWindow() 3. AND explicitly calls
NSApp.activate(ignoringOtherApps: true) in the AppDelegate
4. Global keybind actions trigger ghosttyNewWindow() notification
handler which:
5. Only calls TerminalController.newWindow()
6. Does NOT call NSApp.activate(ignoringOtherApps: true)
7. While TerminalController.newWindow() does call
NSApp.activate(ignoringOtherApps: true) internally, this call happens
before the async dispatch that shows the window, so the activation
occurs but the window isn't focused when it's actually shown.
8. In the old TerminalManager.newWindow(), the activation happened
immediately before the async dispatch, ensuring proper timing for window
focus.
To see the bug in action:
- run recent Ghostty `main`
- set up a global keybind for `new_window`
- focus some other window
- trigger keybind
- notice that Ghostty doesn't come to the foreground, but when manually
switching to Ghostty you will see that the new window _was_ created
The fix would be to either move the NSApp.activate() call back into
TerminalController.newWindow(), as it was for TerminalManager, or add
the activation call to the notification handlers in AppDelegate.
This change makes sure that the new window is focused and visible.
When commit 33d128bcff removed the
TerminalManager class and moved its functionality into
TerminalController, it accidentally removed app activation for windows
triggered by global keybinds.
How the bug works:
1. Menu actions (like File → New Window) call AppDelegate.newWindow()
which:
2. Calls TerminalController.newWindow()
3. AND explicitly calls NSApp.activate(ignoringOtherApps: true) in
the AppDelegate
4. Global keybind actions trigger ghosttyNewWindow() notification
handler which:
5. Only calls TerminalController.newWindow()
6. Does NOT call NSApp.activate(ignoringOtherApps: true)
7. While TerminalController.newWindow() does call
NSApp.activate(ignoringOtherApps: true) internally, this call
happens before the async dispatch that shows the window, so the
activation occurs but the window isn't focused when it's actually
shown.
8. In the old TerminalManager.newWindow(), the activation happened
immediately before the async dispatch, ensuring proper timing for
window focus.
The fix would be to either move the NSApp.activate() call back into
TerminalController.newWindow(), as it was for TerminalManager, or add
the activation call to the notification handlers in AppDelegate.
## Summary
Implements the theme filtering hotkey as requested in #7930.
## Implementation
- Adds 'f' hotkey to cycle through filtering options: all, dark, and
light.
- Integrates with existing search functionality.
- Preserves CLI `--color` flag behavior for initial state.
- Updates help menu with the new hotkey.
**NOTE**: I noticed another PR
[#8079](https://github.com/ghostty-org/ghostty/pull/8079) opened. I
started this implementation independently prior and don't want to step
on any toes. Happy to collaborate or defer to maintainers.
Compromise solution to #7356
XKB is naughty. It's really really naughty. I don't understand why we
didn't just kill XKB with hammers during the Wayland migration and
change it for something much better. I don't understand why we're
content with what amounts to an OS-level software key remapper that
completely jumbles information about original physical key codes in
order to fake keyboard layouts, and not just let users who really want
to remap keys use some sort of evdev or udev-based mapper program.
In a sane system like macOS, the "c" key is always the "c" key, but it's
understood to produce the Unicode character "ц" when using a Russian
layout. XKB defies sanity, and just pretends that your "c" key is
actually a "ц" key instead, and so when you ask for the keybind "Ctrl+C"
it just shrugs in apathy (#7309). And so, we took matters into our own
hands and interpreted hardware keycodes ourselves.
But then, a *lot* of people have the ingrained muscle memory of swapping
Escape with Caps Lock so that it is easier to hit. We respect that. In a
sane system, they would use a remapper that actually makes the system
think you've hit the Escape key when in reality you've hit the Caps Lock
key, so in all intents and purposes to the OS and any app developer,
these two just have their wires swapped. But not on Linux. Somehow this
and the aforementioned case should be treated by the same key transform
algorithm, which is completely diabolical.
As a result, we have to settle for a compromise that truly satisfies
neither party — by allowing XKB remaps for keys that don't really change
depending on the layout.
The Linux input stack besets all hopes and aspirations.
Compromise solution to #7356
XKB is naughty. It's really really naughty. I don't understand why we
didn't just kill XKB with hammers during the Wayland migration and change
it for something much better. I don't understand why we're content with
what amounts to an OS-level software key remapper that completely jumbles
information about original physical key codes in order to fake keyboard
layouts, and not just let users who really want to remap keys use some
sort of evdev or udev-based mapper program.
In a sane system like macOS, the "c" key is always the "c" key, but it's
understood to produce the Unicode character "ц" when using a Russian
layout. XKB defies sanity, and just pretends that your "c" key is
actually a "ц" key instead, and so when you ask for the keybind "Ctrl+C"
it just shrugs in apathy (#7309). And so, we took matters into our own
hands and interpreted hardware keycodes ourselves.
But then, a *lot* of people have the ingrained muscle memory of swapping
Escape with Caps Lock so that it is easier to hit. We respect that.
In a sane system, they would use a remapper that actually makes the
system think you've hit the Escape key when in reality you've hit the
Caps Lock key, so in all intents and purposes to the OS and any app
developer, these two just have their wires swapped. But not on Linux.
Somehow this and the aforementioned case should be treated by the same
key transform algorithm, which is completely diabolical.
As a result, we have to settle for a compromise that truly satisfies
neither party — by allowing XKB remaps for keys that don't really change
depending on the layout.
The Linux input stack besets all hopes and aspirations.
This PR adds a new configuration option
`macos-dock-drop-folder-behavior` that controls whether folders dropped
onto the Ghostty dock icon open in a new tab (default) or a new window.
## Changes
### Configuration Option Added
- **Option name**: `macos-dock-drop-folder-behavior`
- **Valid values**:
- `tab` (default) - Opens folders in a new tab in the main window
- `window` - Opens folders in a new window
- **Platform**: macOS only
### Files Modified
1. **`src/config/Config.zig`**
- Added `MacOSDockDropFolderBehavior` enum with `tab` and `window`
values
- Added configuration field with default value of `.tab`
- Added documentation explaining the option
2. **`macos/Sources/Ghostty/Package.swift`**
- Added `MacOSDockDropFolderBehavior` enum to match the Zig enum
3. **`macos/Sources/Ghostty/Ghostty.Config.swift`**
- Added `macosDockDropFolderBehavior` computed property to access the
configuration value from Swift
4. **`macos/Sources/App/macOS/AppDelegate.swift`**
- Modified `application(_:openFile:)` method to check the configuration
- When a folder is dropped on the dock icon, it now respects the user's
preference
## Usage
Add to your Ghostty configuration file:
```
macos-dock-drop-folder-behavior = window
```
## Motivation
This feature is useful for users (like me!) who prefer window-based
workflows over tab-based workflows when opening folders via drag and
drop on macOS.
This adds a new configuration option that controls whether folders
dropped onto the Ghostty dock icon open in a new tab (default) or
a new window.
The option accepts two values:
- tab: Opens folders in a new tab in the main window (default)
- window: Opens folders in a new window
This is useful for users who prefer window-based workflows over
tab-based workflows when opening folders via drag and drop.
This PR aims to improve custom icons on macOS in the following ways. (I
based this PR on the discussion #3631)
### Currently
- Current Icon customizations are not persistent *(when closing the
application the icon in dock reverts back to official icon)*
- There is no officially supported way to change icon to be something
completely custom.
### After this PR
- Current icon customizations are persistent (closing the application no
longer reverts back to official icon)
- Ghostty config `macos-icon` has a new option `custom` which by default
looks for icon `~/.config/ghostty/Ghostty.icns`. It has an accompanying
new configuration `macos-custom-icon` which allows for a different path
to be specified, it does support more than just `.icns` as well.
Both changes are based on the thread with @sfsam in
https://github.com/ghostty-org/ghostty/discussions/3631#discussioncomment-12180647
Feedback is always welcome, if I have not done something up to par
please let me know and I will do my best to correct it.
NOTE: I did notice some newlines with indents which seems to be against
convention in those files so I removed the whitespace if this is not
preferred I can revert.
---
P.S. Thanks for all the work you put into making an awesome terminal!
Fix for discussion #8113
The cursor Y position value exposed to the shader uniforms was
incorrectly calculated. As per the doc in cell_text.v.glsl: In order to
get the top left of the glyph, we compute an offset based on the
bearings. The Y bearing is the distance from the bottom of the cell to
the top of the glyph, so we subtract it from the cell height to get the
y offset.
This calculation was mistakenly left out of the original code.
This will ensure that the custom shaders using
iCurrentCursor/iPreviousCursor get the correct Y coordinate representing
the top-left corner of the cursor rectangle, matching the documented
uniform behavior
This switches our macOS builds to build on Tahoe, rather than on
Sequoia.
The primary motivation is to get builds out using a new Xcode version
(our builds at the time of writing this are _still produced_ with beta
1! ONE!). Every subsequent beta has had bugs that have prevented us from
upgrading, amusingly enough. But the later betas _also_ have a bunch of
fixes I want to get in. I hope this one works...
The reason we have to use Tahoe instead of Sequoia is because on
Sequoia, builds in CI _crash xcodebuild_. This is definitely an Apple
bug but I can't reproduce it locally to create a bug report, so I'm not
sure what to do.
The SDK published on Flathub updated to Zig 0.15.1 which broke the
Flathub build in CI. So let's install it ourselves so that we can
control the version.
This contains the various changes necessary to get the full unit test
suite passing Valgrind, and configures CI to run this.
I disabled relatively few (less than 10) tests under Valgrind because
they're way too slow: all `verifyIntegrity` tests, because those run
anyways in debug and check their own memory health, a font test that
fills out font map, and the sprite render test. Everything else runs
as-is.
I found a number of issues, most were in the tests themselves. A couple
in actual code. A funny one was some undefined memory on tabstop resize
if you exceed the default number of tabstops. I don't know any real
world program that ever even did that (memory issue aside), and that
whole file hasn't been touched since 2022, so that was funny.
No memory leaks in actual code, but a number of leaks in tests. All
resolved.
I think we're still missing some reports because of the Zig bug:
https://github.com/ziglang/zig/issues/19148 so I'm gong to audit our
codebase after this and look for cases of that.
When constraints increased or decreased the size significantly, the
fractional position was getting messed up by the scale. This change
separates that out so that it applies correctly.
I noticed this when messing around with constraints, adding this
constraint to every glyph and then running with `font-family=Arial` and
`adjust-cell-width = -35%` (if you want to reproduce this)
```zig
constraint = .{
.size_horizontal = .stretch,
.align_horizontal = .center,
.pad_left = 0.1,
.pad_right = 0.1,
};
```
The padding was disproportionately affecting thin glyphs that were
stretched a lot. The problem was that the padding was being multiplied
by the scale.
This also made it so the top or right of said thin glyphs often got
clipped off by the edge of the canvas.
Anyway I fixed it.
|Before|After|
|-|-|
|<img width="1824" height="1480" alt="image"
src="https://github.com/user-attachments/assets/32779f9d-a048-4a8c-b5ea-0e8a851d5119"
/>|<img width="1824" height="1480" alt="image"
src="https://github.com/user-attachments/assets/5bf449e5-699e-4bdc-ac96-2b776f9fb7fa"
/>|
The SDK published on Flathub updated to Zig 0.15.1 which broke the
Flathub build in CI. So let's install it ourselves so that we can
control the version.
When constraints increased or decreased the size significantly, the
fractional position was getting messed up by the scale. This change
separates that out so that it applies correctly.
This runs Valgrind on our unit test suite in CI. Since we're not
currently passing Valgrind, this will be incrementally updated with the
filters for our passing tests. Ultimately, we'll remove the filters and
run the full suite.
Valgrind is slow and hungry so this is our first and only job currently
on a large instance.
This runs Valgrind on our unit test suite in CI. Since we're not
currently passing Valgrind, this will be incrementally updated with the
filters for our passing tests. Ultimately, we'll remove the filters and
run the full suite.
Valgrind is slow and hungry so this is our first and only job currently
on a large instance.
This works around: https://github.com/ziglang/zig/issues/19148 This lets
our `test-valgrind` command catch some issues. We'll have to follow this
pattern in more places but I want to do it incrementally so things keep
passing.
I **do not** want to blindly follow this pattern everywhere. I want to
start by focusing in only on the structs that set `undefined` as default
fields that we're also about to test in isolation with Valgrind. It's
just too much noise otherwise and not a general style I'm sure of; it's
worth it for Valgrind though.
I'm making this PR separate from any fixes because the diff is so noisy
I don't want to lose the fixes in the noise. **This PR is therefore
functionally a no-op.**
This works around: https://github.com/ziglang/zig/issues/19148
This lets our `test-valgrind` command catch some issues. We'll have to
follow this pattern in more places but I want to do it incrementally so
things keep passing.
I **do not** want to blindly follow this pattern everywhere. I want to
start by focusing in only on the structs that set `undefined` as default
fields that we're also about to test in isolation with Valgrind. Its
just too much noise otherwise and not a general style I'm sure of; it's
worth it for Valgrind though.
This adds two explicit `zig build` steps: `run-valgrind` and
`test-valgrind` to run the Ghostty exe or tests under Valgrind,
respectively.
This simplifies the manual Valgrind calls in a few ways:
1. It automatically sets the CPU to baseline, which is a frequent and
requirement for Valgrind on newer CPUs, and generally safe.
2. It sets up the rather complicated set of flags to call Valgrind with,
importantly setting up our suppressions.
3. It enables pairing it with the typical and comfortable workflow of
specifying extra args (with `--`) or flags like `-Dtest-filter` for
tests.
This adds two explicit `zig build` steps: `run-valgrind` and
`test-valgrind` to run the Ghostty exe or tests under Valgrind,
respectively.
This simplifies the manual Valgrind calls in a few ways:
1. It automatically sets the CPU to baseline, which is a frequent and
requirement for Valgrind on newer CPUs, and generally safe.
2. It sets up the rather complicated set of flags to call Valgrind with,
importantly setting up our suppressions.
3. It enables pairing it with the typical and comfortable workflow of
specifying extra args (with `--`) or flags like `-Dtest-filter` for
tests.
I think, at this stage of AI, it is a common courtesy to disclose this.
In a perfect world, AI assistance would produce equal or higher quality
work than any human. That isn't the world we live in today, and in many
cases it's generating slop. I say this despite being a fan of and using
them successfully myself (with heavy supervision)! I think the major
issue is **inexperienced human drivers of AI** that aren't able to
**adequately review their generated code.** As a result, they're pull
requesting code that I'm sure they would be ashamed of if they knew how
bad it was.
The disclosure is to help maintainers assess how much attention to give
a PR. While we aren't obligated to in any way, I try to assist
inexperienced contributors and coach them to the finish line, because
getting a PR accepted is an achievement to be proud of. But if it's just
an AI on the other side, I don't need to put in this effort, and it's
rude to trick me into doing so.
**I'm a fan of AI assistance and use AI tooling myself.** But, we need
to be responsible about what we're using it for and respectful to the
humans on the other side that may have to review or maintain this code.
(In the spirit of this PR... none of this PR was AI generated. lol.)
Fixes#8187
This properly handles the scenario with our `read_text` C API when the
selection start is outside the viewport. Previously we'd report null (no
text) which would cascade into things like the right click menu not
showing a copy option.
Fixes#8187
This properly handles the scenario with our `read_text` C API when the
selection start is outside the viewport. Previously we'd report null (no
text) which would cascade into things like the right click menu not
showing a copy option.
This MR addresses #4404 following the approach suggested
[here](https://github.com/ghostty-org/ghostty/issues/4404#issuecomment-2708410143).
Implementing the behavior known e.g. from Putty or Windows Terminal.
The following configuration values for `right-click-action` are
provided:
* `context-menu` - Show the context menu.
* `paste` - Paste the contents of the clipboard.
* `copy` - Copy the selected text to the clipboard.
* `copy-and-paste` - Copy the selected text to the clipboard, paste if
nothing is selected.
* `ignore` - Do nothing, ignore the right-click.
I followed #5935 for getting an idea on where to start. I hope this to
be a temporary solution until "bindable mouse bindings" are introduced.
This is my first time writing Zig code, so I am happy to incorporate any
feedback.
Thank you all very much for your work!
The `gdk.Surface` is only ever available *after* the window had been
first presented and mapped. Trying to get the surface during `init` like
what we had previously done will **always** return null.
Previously, when encountering an OOM when copying graphemes, hyperlinks,
or styles to a new page during reflow, the attempted resolution was to
copy the current row in to a new page and continue on- which works in
99% of cases, but isn't sound, since it's possible for a single row to
exceed the capacity on any of these.
This led to rare but real crashes like #8009.
I've added tests that produce all of the failure conditions, and
resolved them by changing the strategy from making a new page to
increasing the capacity of the current one.
There should probably be some level of abstraction added around this,
since multiple places in the code now do this sort of thing- attempt to
add some managed memory to a page, adjusting their capacity upwards as
necessary. But for now, I kept it all inline here.
The `gdk.Surface` is only ever available *after* the window had been
first presented and mapped. Trying to get the surface during `init`
like what we had previously done will **always** return null.
When window-decoration=none, setting the window decoration to null would
just mean it would default to none again, creating a cycle of torment
none can break out of... that sounds a bit too dramatic doesn't it
Fixes#8274
In our webgen we treat 4 consecutive spaces as a code block, which is
often triggered by mistake when a paragraph is encased within a list.
We should probably fix this more thoroughly at some point since I don't
think actual Markdown parsers have the same behavior, but for now we
just fall back to using 3-space indents.
As an example of this occurring on the tip website:
<img width="1177" height="1012" alt="paragraphs being erroneously turned
into code blocks"
src="https://github.com/user-attachments/assets/878a8c83-3e37-41b7-90d9-fbd5b692bf16"
/>
When window-decoration=none, setting the window decoration to null would
just mean it would default to none again, creating a cycle of torment
none can break out of... that sounds a bit too dramatic doesn't it
Fixes#8274
In our webgen we treat 4 consecutive spaces as a code block, which is
often triggered by mistake when a paragraph is encased within a list.
We should probably fix this more thoroughly at some point since I don't
think actual Markdown parsers have the same behavior, but for now we
just fall back to using 3-space indents.
This fixes the incorrect comment and uses $HOME (rather than ~) to be a
little bit more explicit.
Also, our script is named ghostty-integration, not ghostty.zsh, so
update that part of the comment, too.
It's possible for the hyperlink or string capacity to be exceeded in a
single row, in which case it doesn't matter if we move the row to a new
page, it will still be a problem. This was causing actual crashes under
some circumstances.
Improves the bitmap allocator's handling of allocations that are 64
chunks or more.
Previously, an allocation of exactly 64 chunks could not be freed, it
slipped through a crack in the logic and caused `free` to do nothing.
Also, >64 chunk allocations no longer always start at bitmap starts,
they can now start partway through a bitmap. If this had been fixed
before, it would have exposed a memory corruption issue in `free`, since
freeing such an allocation with the old logic would have fully cleared
its starting bitmap regardless of the starting point.
Adds a bunch of tests to exercise edge cases for free.
I didn't benchmark these changes, but I have a feeling that if there's a
performance difference it will be an improvement, since `free` now marks
more efficiently I believe (it was doing one bit at a time before), and
`findFreeChunks` now uses `clz` and `ctz` (on inverted versions of the
bitmaps).
Also, looking more closely as I type this, the old logic in
`findFreeChunks` may have had a potential corruption issue as well,
which could have allowed >64 chunk allocations to overlap the starts of
following allocations. I guess we didn't see it in the wild because our
chunk sizes are chosen in a way which would generally avoid >64 chunk
allocations in the VAST majority of cases.
This previously had logic in it that was very wrong and could lead to
memory corruption or a failure to properly mark data as freed.
Also introduces a bunch of tests for various edge case behavior.
Insead of signals between the ImGui widget and the Inspector widget,
make the Inspector widget a subclass of the ImGui widget and use virtual
methods to handle setup and rendering of the Inspector.
Insead of signals between the ImGui widget and the Inspector widget,
make the Inspector widget a subclass of the ImGui widget and use virtual
methods to handle setup and rendering of the Inspector.
Fixes#8266
When a surface is first created, there's a race condition between when
the config is set on the surface and when the code to check if a border
should be drawn around the surface is run. Fix that by exiting early if
the bell isn't ringing, before we check to see if there's a config set
on the surface and issuing the warning message.
This release contains performance and memory use improvements.
Some of the sprite font test renders had to be updated due to very minor
differences in the anti-aliasing, since the default anti-aliasing method
in z2d has been changed to MSAA rather than SSAA.
This release contains performance and memory use improvements.
Some of the sprite font test renders had to be updated due to very minor
differences in the anti-aliasing, since the default anti-aliasing method
in z2d has been changed to MSAA rather than SSAA.
Grow needs to allocate and might fail midway. It tries to handle this
using "undo" pattern, and restoring old state on error. But this is
exactly what steps into UAF, as, on error, both errdefer and defer are
run, and the old data is freed.
Instead, use a more robust "reservation" pattern, where we first
fallibly resrve all the resources we need, without applying any changes,
and than do the actual change once we are sure that cannot fail.
Similar tests should be added throughout the codebase for any function
that's supposed to gracefully handle OOM conditions. This one was added
because grow previously had a use-after-free bug under OOM, which this
would have caught.
Reordered to form a more logical sequence of steps, cleaned up and
clarified comments, fixed invalid `appendAssumeCapacity` call which
erroneously passed `alloc`, so this compiles again.
Grow needs to allocate and might fail midway. It tries to handle this
using "undo" pattern, and restoring old state on error. But this is
exactly what steps into UAF, as, on error, both errdefer and defer are
run, and the old data is freed.
Instead, use a more robust "reservation" pattern, where we first
fallibly resrve all the resources we need, without applying any changes,
and than do the actual change once we are sure that cannot fail.
Fixes#8243
This adds a check for a zero-sized grid in cursor-related functions.
As an alternate approach, I did look into simply skipping a bunch of
work on zero-sized grids, but that looked like a scarier change to make
now. That may be the better long-term solution but this was an easily
unit testable, focused fix on the crash to start.
Fixes#8243
This adds a check for a zero-sized grid in cursor-related functions.
As an alternate approach, I did look into simply skipping a bunch of
work on zero-sized grids, but that looked like a scarier change to make
now. That may be the better long-term solution but this was an easily
unit testable, focused fix on the crash to start.
The journey to rewrite our legacy GTK backend to a full GObject-based
backend is complete! The full background and motivation can be found in
the original PR: #7961. ~75 PRs later, we've reached **full parity**
with the legacy GTK backend.
Throughout the process, we've tested every feature under Valgrind, and
this build is fully clean of memory leaks and undefined access. Its
impossible to test the existing GTK backend because its full of false
positives, but based on my experience working on `-ng`, I think its
impossible we got it right. This isn't a dig at any of our GTK subsystem
maintainers; I've simply found its very complicated to get all the
memory management behaviors right with GTK. There are subtle, easy to
miss, weakly documented things, such as [clearing weak refs on
dispose](7548dcfe63).[^1]
The point is, **gtk-ng is much higher quality than legacy.**
There is only regression we know of (#8208). I'm willing to swap the
default despite this because the improvements not just in memory safety
but also behavior: splits now support spatial navigation, better
equalization behavior, etc.
At this point, I think we should swap the default to see if we missed
anything else.
[^1]: This isn't a dig at Gnome developers either. Documenting these
details is hard, too.
This was a memory leak under Metal, leaked 1 swapchain worth of targets
every time a surface was closed.
Under OpenGL I think it was all cleaned up when the GL context was
destroyed.
This was a memory leak under Metal, leaked 1 swapchain worth of targets
every time a surface was closed.
Under OpenGL I think it was all cleaned up when the GL context was
destroyed.
This cleans up our close handling of all types (surfaces, tabs,
windows). Surfaces no longer emit their scope; their scope is always
just the surface itself. For tab and window scope we use widget actions.
This makes `close_tab` work properly (previously broken).
This cleans up our close handling of all types (surfaces, tabs, windows).
Surfaces no longer emit their scope; their scope is always just the
surface itself. For tab and window scope we use widget actions.
This makes `close_tab` work properly (previously broken).
This is a (relatively) straightforward port of the terminal inspector
from the old GTK application runtime. It's split into three widgets. At
the lowest level is a widget designed for showing a generic Dear ImGui
application. Above that is a widget that embeds the ImGui widget and
plumbs it into the core Inspector. At the top is a custom Window widget
that embeds the Inspector widget.
And then there's all the plumbing necessary to hook everything into the
rest of Ghostty.
In theory this design _should_ allow showing the Inspector in a split or
a tab in the future, not just in a separate window. It should also make
it easier to display _other_ Dear ImGui applications if they are ever
needed.
This is a (relatively) straightforward port of the terminal inspector
from the old GTK application runtime. It's split into three widgets. At
the lowest level is a widget designed for showing a generic Dear ImGui
application. Above that is a widget that embeds the ImGui widget and
plumbs it into the core Inspector. At the top is a custom Window widget
that embeds the Inspector widget.
And then there's all the plumbing necessary to hook everything into the
rest of Ghostty.
In theory this design _should_ allow showing the Inspector in a split
or a tab in the future, not just in a separate window. It should also
make it easier to display _other_ Dear ImGui applications if they are
ever needed.
This fixes `mouse-hide-while-typing`. Don't know why this worked before
(I tested it yesterday!) but stopped working today. But this now works,
and conceptually makes some sense.
This fixes `mouse-hide-while-typing`. Don't know why this worked before
(I tested it yesterday!) but stopped working today. But this now works,
and conceptually makes some sense.
Supersedes #8129
This is a rewrite but I did take pieces of #8129. I dropped the new
feature that was mixed into the PR because I'm trying not to introduce
new features in `-ng` right now. Feel free to PR that separately
@jcollie. I also dropped some of the action group validation stuff which
admittedly would be nice, so also happy to add that.
A big change I made here is we don't need to expose `bell-features` from
surface, because we can use the relevant config that we have access to.
I passed the config as a closure parameter so it recomputes when config
changes, too.
I also fixed a bug I found where we'd lose computed titles on
non-focused tabs because `active-surface` would start returning null
(since none are focused there). We now fallback to the active surface
being the _last focused_ surface if no focused surface exists, which
matches the behavior we also have on macOS.
This brings together all our title-related functionality (so far).
To make this all work, I heavily use (abuse?) blueprint bindings with
closures. Blueprint sets up property subscription for all closure
parameters and all properties in a chain (`a.b.c`). This makes for a
really long, deeply nested property access but it saves us literally
hundreds (at least 100) lines of `notify` signal subscription
boilerplate.
This also lets some of these properties be truly dynamic and avoid
simply copying around intermediate values up the widget tree.
Unfortunately Blueprint's auto-formatter won't let us split property
access or function parameters onto separate lines so we're going to have
some very, very long lines.
This makes `toggle_split_zoom` work via a new widget action
`split-tree.zoom`. The zoom state is tracked on the core `SplitTree`
data structure. Zoom state is propagated via a `is-zoomed` property on
the split tree in GTK.
I deferred the title changes since I can do that all at once with
subtitle and other things.
Ports the resize split action (tied to the `resize_split` binding
action).
This also includes fixes for splits that are exactly `0` or `1` ratio
width (full width either direction). This would previously cause
crashes.
I've cleaned up the code we use for scaling and positioning glyphs for
raster, under both CoreText and FreeType. Before we had some
imprecision, and under CoreText we were sometimes stretching glyphs in
unseemly ways. These changes make it so that our constraints can
position and size glyphs *exactly* and we don't have any chopped-off
row/column issues for CoreText. With this, PowerLine Extra symbols now
always align *perfectly* with the cell height:
||Before|After|
|-:|-|-|
|**CoreText**|<img width="105" height="245" alt="image"
src="https://github.com/user-attachments/assets/d3c1b1cb-a798-4e18-a0e0-59551893369c"
/>|<img width="106" height="246" alt="image"
src="https://github.com/user-attachments/assets/dac10c49-9ec1-4f4f-8825-a5e8c2fd3402"
/>|
|**FreeType**|<img width="105" height="245" alt="image"
src="https://github.com/user-attachments/assets/160e1e35-4a3c-42d0-9042-215301e636a1"
/>|<img width="106" height="245" alt="image"
src="https://github.com/user-attachments/assets/89bf1538-7271-4baf-88c0-51ebc4d360df"
/>|
The other changes are mainly just cleanup stuff, though one of the
changes makes it so that we do once again properly apply constraints to
symbols from the dingbats block (it was a regression, noted in #7955,
that we stopped doing that).
### Future work
This has been a problem since we introduced the custom constraints, but
I noticed it while preparing the before/after images: the left-edge PLE
symbols (meant to connect to a full block on the right) expand out to
the *right*, so if they're followed immediately by another character
than they actually get squished and don't match the right-edge symbols:
<img width="75" height="114" alt="image"
src="https://github.com/user-attachments/assets/1420b9a5-9950-4210-9934-8ef7cd7a1e19"
/>
I have a WIP change to move constraint logic to the shapers, and at that
point we can maybe do something to allow the constraint to grow in to
whitespace on the left side instead of on the right side.
The upstream GIR for g_weak_ref_get is incorrect - it does not allow the
returned value to be NULL. This PR pulls in a new version of our GObject
bindings with that patched and improves the safety of dealing with the
command palette weak reference held by the window.
See ianprime0509/zig-gobject#117
This continues #8202 by fixing two of the known issues: `goto_split` key
binds work and closing a split moves focus to the proper place.
A big improvement in this PR is that for the first time ever in our GTK
backend, the up/down/left/right `goto_split` bindings **use spatial
navigation.** "Spatial navigation" means that the direction to move
focus is done based on the nearest split _visually_ from the current
split, rather than via a tree traversal. We did this on macOS a couple
months ago, with a lot more details there: #7523
Similar to macOS, the spatial navigation is currently based on top-left
corner. Now that our split tree is implemented in Zig though it should
be a lot easier for us to work in the current cursor position as the
reference point.
~~🚧 TODO: Going to add some unit tests for the spatial navigation before
merge.~~
This adds on to our existing foundations from #8165 and adds the ability
to create and close splits. We're still missing split navigation,
resizing via keybindings, etc. And there are a number of known issues
(listed below). But this is a strict improvement from where we're at and
includes a number of important bug fixes to our split tree.
The only nasty thing in this PR is that I learned that GTK _did not
like_ rebuilding our split widget tree on every data model change. I
don't know enough about how all the re-parenting plus size allocation
interactions work together. As a compromise, this PR adds a listener,
waits for our surface tree to "settle" by having all surfaces have no
parents, then schedules a single rebuild after that. This works well,
but results in some noticeable flashing for a frame or so. I think we
can improve this later, it works completely well enough.
Importantly, all of this is Valgrind clean. I long suspected our splits
on legacy are NOT free of leaks, but never proved it, so this makes me
happy.
## Demo
https://github.com/user-attachments/assets/e231d89f-581e-486b-ade0-1d7e6795262e
## Known Issues
I may fix this in this PR, I may follow up.
- [ ] Focus doesn't go to the right place after closing a split
- [x] Divider with a transparent background is transparent
- [x] Close split doesn't show any close confirmation dialog
- Missing features:
* [ ] Equalize splits
* [ ] Resize splits keybind (manual mouse action works fine)
* [ ] Go to split keybind
Freetype encodes some font names internally in formats other than UTF-8.
This only affects debug logs but it was annoying me so I fixed it. There
may be other encodings that might need to be dealt with but I took care
of the one that I ran across.
We now also have absolute perfect control over the raster position under
FreeType as well. This means that, for example, powerline extended chars
are appropriately clamped to the cell edges at all sizes.
This should be purely an improvement over what we had before, and now it
also matches what we do for CoreText.
Freetype encodes some font names internally in formats other than UTF-8.
This only affects debug logs but it was annoying me so I fixed it. There
may be other encodings that might need to be dealt with but I took care
of the one that I ran across.
The monochrome hinter is very aggressive but makes text actually look
tolerable when rendered in monochrome. If for some god forsaken reason
we get complaints about this, that someone wanted improperly hinted mono
glyphs, we can introduce additonal configuration; but for now, this is
just a straight improvement.
The old method was nice, but had an issue that's intractible without
significant reworking in how we do shaping: combining glyphs need to
position relative to the glyph they're combining with, but if we re-
center that glyph, it will be off by some amount.
Apologies to Apple, the previous comments in this section of the code
were not correct-- `shouldSubpixelQuantizeFonts` does pretty much what
the minimal documentation for it says it does, it simply quantizes the
position of the glyph and nothing more. Various bugs when testing while
writing the old code that led me to include those comments made me not
realize that the positioning is actually a lot simpler than it seems.
With this version of the positioning there are never any cut-off rows or
columns of pixels on the edges of anything and everything scales as it
should... I hope. I checked pretty thoroughly this time and I'm like 99%
sure this is correct in all cases.
This avoids jitter when resizing splits. I didn't see any jitter before
splits but conceptually its possible. The issue is that since we're
updating the overlay DURING A RESIZE, changing the dimensions of any
part of the widget tree causes GTK warnings and a bunch of laggy
updates.
Instead, we copy the label text to a property and update it on the idle
callback along with everything else. This also provides a natural
debounce to the label.
This avoids jitter when resizing splits. I didn't see any jitter before
splits but conceptually its possible. The issue is that since we're
updating the overlay DURING A RESIZE, changing the dimensions of any
part of the widget tree causes GTK warnings and a bunch of laggy
updates.
Instead, we copy the label text to a property and update it on the idle
callback along with everything else. This also provides a natural
debounce to the label.
This also fixes a bug where we were setting custom cursors on the wrong
gtk widget, this showed up most terribly with `mouse-hide-while-typing`
where the mouse would never reappear.
This PR adds a "tabs" title bar style similar to the macOS title bar
style. When `gtk-titlebar-style=tabs` the title bar and the tab bar will
be merged together.
The config entry for controlling this is kept separate from macOS as
macOS has more styles defined that don't map to a GTK title bar style
and it's likely that users that use both macOS and GTK would want
different settings for each platform.
<img width="922" height="722" alt="Screenshot From 2025-08-06 16-38-28"
src="https://github.com/user-attachments/assets/3c2db235-695a-457e-9c96-5039120263fc"
/>
This also fixes a bug where we were setting custom cursors on the wrong
gtk widget, this showed up most terribly with `mouse-hide-while-typing`
where the mouse would never reappear.
This PR adds a "tabs" title bar style similar to the macOS title bar
style. When `gtk-titlebar-style=tabs` the title bar and the tab bar
will be merged together.
The config entry for controlling this is kept separate from macOS as
macOS has more styles defined that don't map to a GTK title bar style
and it's likely that users that use both macOS and GTK would want
different settings for each platform.
We get a ton of leaks from GTK.PopOver when we run the steps given in
the suppression file. I don't see how this could be us since we don't
create or do anything with the popover manually; its simply defined in
the Blueprint file.
The leaks specifically only happen when a Popover shows a sub-menu.
Without that, everything is completely clean. So I actually suspect
there's some leaks in GTK related to this behavior (not sure if they're
on purpose not, a brief look at the code doesn't look like they're
reused).
I tried alternate approaches where we create the Popover AND/OR the
MenuModel in code without the Blueprint file and we get the same leaks.
I'm kind of suspicious about this one but don't see how we can do
anything about it, so I'm going to suppress for now. The suppression
file has detailed repro steps that people can use to hopefully test this
later.
SwiftUI's ImageRenderer must not be called outside the main thread.
The `@MainActor` annotation is only relevant for our own code, not
for calls from frameworks. The machinations around Shortcuts end up
calling the displayRepresentation method outside the main thread.
By capturing the screenshot as NSImage, all data is retained and can
be processed outside the main thread.
Thread in Discord:
https://discord.com/channels/1005603569187160125/1403384231694172372
We get a ton of leaks from GTK.PopOver when we run the steps given in
the suppression file. I don't see how this could be us since we don't
create or do anything with the popover manually; its simply defined in
the Blueprint file.
SwiftUI's ImageRenderer must not be called outside the main thread.
The `@MainActor` annotation is only relevant for our own code, not
for calls from frameworks. The machinations around Shortcuts end up
calling the displayRepresentation method outside the main thread.
By capturing the screenshot as NSImage, all data is retained and can
be processed outside the main thread.
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.15 to 1.2.16.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="305bfa7ea9"><code>305bfa7</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/31">#31</a>
from namespacelabs/niklas-new-pnpm</li>
<li><a
href="ca35d05e60"><code>ca35d05</code></a>
Do not touch node_modules in PNPM mode.</li>
<li>See full diff in <a
href="f2d0a9e9ed...305bfa7ea9">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
This begins to bring back splits to `gtk-ng`. As of this PR, **splits
still don't work**, but the architectural underpinnings for them are all
present. Namely, our tab view now embeds a `GhosttySplitTree` widget
which has a full split tree present, and all the signals and active
surface properties and so on are hooked up to the tree.
In theory, once we hook up split creation, close, etc. everything should
_just work_.
But, this PR is already very large and I don't want to make it even
larger, so I'm opening this PR to add the foundations for this while
keeping `gtk-ng` in the state it more or less is on main right now.
The meat of this is in a pure Zig data structure `SplitTree` added to
`src/datastruct`. This is a Ziggified port of our macOS implementation
(but, much better if I do say so myself). Being in pure Zig lets us
write unit tests easily, control allocations tightly, get our safety
checks, etc. There is coverage in this PR.
## Other Bugs Fixed
- Boxed accessors use the proper `g_boxed_copy/free` functions. Didn't
really cause any issues because this is the first PR where we actually
use custom implementations for that.
- `Surface` properly emits a notify event for focus change
Closes#5372
Discretionary ligatures (denoted by the OpenType feature tag `dlig`) are
sometimes used by programming fonts (e.g. Iosevka) to provide more
"complex" and uncommon ligatures that may be useful in a programming
context. Unfortunately, this has some nasty side effects with certain
Japanese fallback fonts (#5372) due to perhaps a misaligned
understanding of the OpenType spec[^spec].
The spec details that `dlig` ligatures should only be used to contract
sequences of glyphs together into one glyph, and that it should be used
only for "special effect", **at the user's preference** (emphasis mine).
Indeed, it also suggests that:
> UI suggestion: This feature should be off by default.
All of this, combined with the fact that historical, nowadays unused and
even unintelligible Kanji ligatures are explicitly included as examples
of discretionary ligatures, shows that in the Japanese context at least
that the "level of discretion" is significantly higher than what is
found in programming fonts, where it is more understood to be
"opinionated and uncommon", rather than "obsolete and unreadable".
Furthermore, it appears that a lot of common programming fonts don't
even make use of the `dlig` feature — JetBrains Mono, FiraCode and
MonoLisa lack a `dlig` feature altogether, while Inconsolata seems to
only use it for ligatures that are more commonly found in `liga` or
`calt`, such as the `->` ligature. To a lot of people, then, this change
would literally alter nothing.
Therefore, it's my opinion that we should disable `dlig` by default.
It's arguably not being used correctly in the programming font space (or
at least not in a way that's coherent with other fonts), and it only
provides a marginal benefit while potentially rendering entire sentences
in Japanese (and possibly other languages) unreadable out of the box.
If someone upgrades to tip or 1.2 and then asks "why aren't the
ligatures working anymore", then at least they can always just turn on
`dlig` by themselves.
[^spec]:
https://learn.microsoft.com/en-us/typography/opentype/spec/features_ae#tag-dlig
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.14 to 1.2.15.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f2d0a9e9ed"><code>f2d0a9e</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/30">#30</a>
from namespacelabs/niklas-wipe-debug</li>
<li><a
href="c532e9b02c"><code>c532e9b</code></a>
Fix cache content test, and add debug outputs.</li>
<li>See full diff in <a
href="a2c6b4830e...f2d0a9e9ed">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Closes#5372
Discretionary ligatures (denoted by the OpenType feature tag `dlig`) are
sometimes used by programming fonts (e.g. Iosevka) to provide more
"complex" and uncommon ligatures that may be useful in a programming
context. Unfortunately, this has some nasty side effects with certain
Japanese fallback fonts (#5372) due to perhaps a misaligned understanding
of the OpenType spec[^spec].
The spec details that `dlig` ligatures should only be used to contract
sequences of glyphs together into one glyph, and that it should be used
only for "special effect", **at the user's preference** (emphasis mine).
Indeed, it also suggests that:
> UI suggestion: This feature should be off by default.
All of this, combined with the fact that historical, nowadays unused and
even unintelligible Kanji ligatures are explicitly included as examples
of discretionary ligatures, shows that in the Japanese context at least
that the "level of discretion" is significantly higher than what is found
in programming fonts, where it is more understood to be "opinionated and
uncommon", rather than "obsolete and unreadable".
Furthermore, it appears that a lot of common programming fonts don't even
make use of the `dlig` feature — JetBrains Mono, FiraCode and MonoLisa
lack a `dlig` feature altogether, while Inconsolata seems to only use it
for ligatures that are more commonly found in `liga` or `calt`, such as
the `->` ligature. To a lot of people, then, this change would literally
alter nothing.
Therefore, it's my opinion that we should disable `dlig` by default.
It's arguably not being used correctly in the programming font space
(or at least not in a way that's coherent with other fonts), and it only
provides a marginal benefit while potentially rendering entire sentences
in Japanese (and possibly other languages) unreadable out of the box.
If someone upgrades to tip or 1.2 and then asks "why aren't the ligatures
working anymore", then at least they can always just turn on `dlig` by
themselves.
[^spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/features_ae#tag-dlig
GNU gettext simply is a PITA on certain platforms (i.e. Windows, musl
Linux, etc.) and currently it's not possible to cleanly remove i18n
from the build process, making building Ghostty on the aforementioned
platforms difficult. By providing users with a way to opt-out of the
i18n mechanisms (or opt-in, on platforms where i18n is disabled by
default) we can make sure that people at least have *some* way of
building Ghostty before i18n mechanisms can be integrated neatly.
Bumps
[actions/download-artifact](https://github.com/actions/download-artifact)
from 4.3.0 to 5.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/download-artifact/releases">actions/download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@nebuk89</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/407">actions/download-artifact#407</a></li>
<li>BREAKING fix: inconsistent path behavior for single artifact
downloads by ID by <a
href="https://github.com/GrantBirki"><code>@GrantBirki</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/416">actions/download-artifact#416</a></li>
</ul>
<h2>v5.0.0</h2>
<h3>🚨 Breaking Change</h3>
<p>This release fixes an inconsistency in path behavior for single
artifact downloads by ID. <strong>If you're downloading single artifacts
by ID, the output path may change.</strong></p>
<h4>What Changed</h4>
<p>Previously, <strong>single artifact downloads</strong> behaved
differently depending on how you specified the artifact:</p>
<ul>
<li><strong>By name</strong>: <code>name: my-artifact</code> → extracted
to <code>path/</code> (direct)</li>
<li><strong>By ID</strong>: <code>artifact-ids: 12345</code> → extracted
to <code>path/my-artifact/</code> (nested)</li>
</ul>
<p>Now both methods are consistent:</p>
<ul>
<li><strong>By name</strong>: <code>name: my-artifact</code> → extracted
to <code>path/</code> (unchanged)</li>
<li><strong>By ID</strong>: <code>artifact-ids: 12345</code> → extracted
to <code>path/</code> (fixed - now direct)</li>
</ul>
<h4>Migration Guide</h4>
<h5>✅ No Action Needed If:</h5>
<ul>
<li>You download artifacts by <strong>name</strong></li>
<li>You download <strong>multiple</strong> artifacts by ID</li>
<li>You already use <code>merge-multiple: true</code> as a
workaround</li>
</ul>
<h5>⚠️ Action Required If:</h5>
<p>You download <strong>single artifacts by ID</strong> and your
workflows expect the nested directory structure.</p>
<p><strong>Before v5 (nested structure):</strong></p>
<pre lang="yaml"><code>- uses: actions/download-artifact@v4
with:
artifact-ids: 12345
path: dist
# Files were in: dist/my-artifact/
</code></pre>
<blockquote>
<p>Where <code>my-artifact</code> is the name of the artifact you
previously uploaded</p>
</blockquote>
<p><strong>To maintain old behavior (if needed):</strong></p>
<pre lang="yaml"><code></tr></table>
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="634f93cb29"><code>634f93c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/416">#416</a>
from actions/single-artifact-id-download-path</li>
<li><a
href="b19ff43027"><code>b19ff43</code></a>
refactor: resolve download path correctly in artifact download tests
(mainly ...</li>
<li><a
href="e262cbee4a"><code>e262cbe</code></a>
bundle dist</li>
<li><a
href="bff23f9308"><code>bff23f9</code></a>
update docs</li>
<li><a
href="fff8c148a8"><code>fff8c14</code></a>
fix download path logic when downloading a single artifact by id</li>
<li><a
href="448e3f862a"><code>448e3f8</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/407">#407</a>
from actions/nebuk89-patch-1</li>
<li><a
href="47225c44b3"><code>47225c4</code></a>
Update README.md</li>
<li>See full diff in <a
href="d3f86a106a...634f93cb29">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
A couple of minor fixes that I found while exploring tonight:
- OSC 22 wasn't working on gtk-ng. Fixed by setting the cursor shape
directly on the surface.
- Removed use of deprecated property on GLArea
Fix a bug introduced by #6885
`fixZhLocale` returns locale string literal without copying it into the
buffer, causing the `LANGUAGE` environment variable to be set
incorrectly.
```log
$ LANG="" zig build run
...
debug(os_locale): setting LANGUAGE from preferred languages value=�����.UTF-8:�����.UTF-8:en_US.UTF-8:ja_CN.UTF-8
...
```
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.13 to 1.2.14.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/namespacelabs/nscloud-cache-action/releases">namespacelabs/nscloud-cache-action's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.14</h2>
<ul>
<li><a
href="93ab075400">Fix
post path cache summary and wipe non-cacheable contents in post
step.</a></li>
<li>[PNPM] <a
href="48cddb643a">Set
copy mode and avoid spurious warnings.</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a2c6b4830e"><code>a2c6b48</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/29">#29</a>
from namespacelabs/niklas-wipe</li>
<li><a
href="93ab075400"><code>93ab075</code></a>
Fix post path cache summary and wipe non-cacheable contents in post
step.</li>
<li><a
href="fa4ddeb1d7"><code>fa4ddeb</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/28">#28</a>
from namespacelabs/niklas-pnpm-copy</li>
<li><a
href="48cddb643a"><code>48cddb6</code></a>
Set copy mode and avoid spurious warnings.</li>
<li>See full diff in <a
href="9ff6d4004d...a2c6b4830e">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Not a lot here, ported the action accelerators which was small.
Besides that, cleaned up a bunch of explicit error sets which allowed us
to remove some forwarded errors because they're so unlikely, and could
unify others.
We only need properties for things that are bound via the blueprint
files. Otherwise, its kind of just a pain. This fixes a bug where it
wasn't being properly set initially anyways because we didn't trigger
syncAppearance.
Port with changes:
* Utilizes the Surface blueprint for defining the `PopoverMenu`
* We can't attach it directly to the Overlay using blueprints because an
overlay can only have a single child property and you can't see other
children via Blueprint. To overcome this, use a `Box`
* Utilizing a `menu` signal the window can listen to to refresh its
action map instead of digging into ancestor hierarchy.
Ports global shortcuts.
This is mostly a direct logic copy. The primary difference is I
converted `GlobalShortcuts` to a `GObject` which has a config and dbus
property and emits a trigger signal. Importantly, it's no longer tied or
dependent on the `gio.Application` in any way. The config and dbus
connection are updated as normal properties.
Verified with Valgrind we're clean. Found one memory leak I ported back
to legacy.
Without this, if you create a new tab by clicking on the split button it
will be the focus for any new input. So for example if you create a new
tab and then immediately press the space bar a bunch of new tabs will be
created. Other keypresses will just "disappear". Only by clicking in the
new tab to focus it will keyboard input go to "the right place".
As discussed here
https://github.com/ghostty-org/ghostty/discussions/8021
This fixes invalid fish shell syntax.
As an example, run ghostty like so: `XDG_CONFIG_HOME=/tmp ghostty
--shell-integration-features=ssh-env --command="/usr/bin/env fish"`.
Setting `XDG_CONFIG_HOME` to `/tmp` is just to start from the default
configs for ghostty and fish.
Before:
```
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish
robbiev@neo ~/s/ghostty (fish-shell-ssh)> ssh git@github.com
env: ‘command’: No such file or directory
robbiev@neo ~/s/ghostty (fish-shell-ssh) [127]>
```
After:
```
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish
robbiev@neo ~/s/ghostty (fish-shell-ssh)> ssh git@github.com
PTY allocation request failed on channel 0
Hi robbiev! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.
robbiev@neo ~/s/ghostty (fish-shell-ssh) [1]>
```
(This fails correctly)
I've also tried with a correctly accessible ssh server but of course you
can't repro that easily if you don't have some place to ssh to.
```
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish
robbiev@neo ~/s/ghostty (fish-shell-ssh)> ssh trinity
[robbiev@trinity:~]$ echo $TERM
xterm-ghostty
```
My understanding of the fix follows.
The script is using `command` to make sure it calls the actual ssh
binary and not some intermediate shell function with the same name (`man
command` explains).
`env` can be useful for fish compat < 3.1, where [the KEY=value syntax
was
introduced](https://fishshell.com/docs/current/faq.html#how-do-i-set-or-clear-an-environment-variable).
However because `command` is a builtin it doesn't work in this case.
So a simple solution is this, requiring fish >= 3.1
```
TERM="$ssh_term" command ssh $ssh_opts $argv
```
An
[alternative](https://serverfault.com/questions/164305/how-can-i-set-environment-variable-for-just-one-command-in-fish-shell)
for maximum fish compat could be the following:
```
begin
set -lx TERM "$ssh_term"
command ssh $ssh_opts $argv
end
```
According to `man set`, `-l` means local to the block and `-x` means
export.
I'm in favour of keeping `command` as it makes the integration more
predicable.
The reason I went with the current fix:
- It's easier to understand without knowing fish shell.
- [kat found that fish 3.1 should be widely
available](https://github.com/ghostty-org/ghostty/discussions/8021#discussioncomment-13877129).
As discussed here https://github.com/ghostty-org/ghostty/discussions/8021
This fixes invalid fish shell syntax.
As an example, run ghostty like so: `XDG_CONFIG_HOME=/tmp ghostty --shell-integration-features=ssh-env --command="/usr/bin/env fish"`. Setting XDG_CONFIG_HOME to /tmp is just to start from the default config.
Before:
```
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish
robbiev@neo ~/s/ghostty (fish-shell-ssh)> ssh git@github.com
env: ‘command’: No such file or directory
robbiev@neo ~/s/ghostty (fish-shell-ssh) [127]>
```
After:
```
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish
robbiev@neo ~/s/ghostty (fish-shell-ssh)> ssh git@github.com
PTY allocation request failed on channel 0
Hi robbiev! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.
robbiev@neo ~/s/ghostty (fish-shell-ssh) [1]>
```
My understanding of the fix follows.
The script is using `command` to make sure it calls the actual ssh binary and not some intermediate shell function with the same name (`man command` explains).
`env` can be useful for fish compat < 3.1, where [the KEY=value syntax was introduced](https://fishshell.com/docs/current/faq.html#how-do-i-set-or-clear-an-environment-variable). However because `command` is a builtin it doesn't work in this case.
So a simple solution is this, requiring fish >= 3.1
```
TERM="$ssh_term" command ssh $ssh_opts $argv
```
An [alternative](https://serverfault.com/questions/164305/how-can-i-set-environment-variable-for-just-one-command-in-fish-shell) for maximum fish compat could be the following:
```
begin
set -lx TERM "$ssh_term"
command ssh $ssh_opts $argv
end
```
According to `man set`, `-l` means local to the block and `-x` means export.
I'm in favour of keeping `command` as it makes the integration more predicable.
The reason I went with the current fix:
- It's easier to understand without knowing fish shell.
- [kat found that fish 3.1 should be widely available](https://github.com/ghostty-org/ghostty/discussions/8021#discussioncomment-13877129).
This ports over the winproto behaviors to gtk-ng. The core winproto
logic is unchanged except for trivial typing changes. The interaction
with winproto is a bit different in ng due to the class separation of
logic between surfaces and windows, but functionally the same.
Ran against Valgrind and all looks good.
This ports the same behavior from GTK, mostly. This also fixes a bug
where the limits would be enforced on reload. Instead, we should only
enforce them on the first surface ever.
This ports the same behavior from GTK, mostly. This also fixes a bug
where the limits would be enforced on reload. Instead, we should only
enforce them on the first surface ever.
Ensure the surface has a correct initial size when created. This avoids
a rapid resize event and also the pty reports the correct size for
startup scripts.
This is a departure from macOS and legacy GTK. This has been an issue in
Ghostty for awhile so this is the proper path forward.
This works by deferring Surface initialization until the first resize
event. This MIGHT result in a frame or two not rendering but I haven't
noticed anything visually and having the correct size is far more
important.
Ensure the surface has a correct initial size when created. This avoids
a rapid resize event and also the pty reports the correct size for
startup scripts.
This is a departure from macOS and legacy GTK. This has been an issue in
Ghostty for awhile so this is the proper path forward.
This works by deferring Surface initialization until the first resize
event. This MIGHT result in a frame or two not rendering but I haven't
noticed anything visually and having the correct size is far more
important.
This brings back all tabbing behaviors.
I ran through create/close tabs and windows with Valgrind and everything
ran clean.
TODO:
- [x] goto tab keybinding
- [x] move tab
- [x] toggle tab overview (binding only, UI works!)
- [x] create window action to pull tab out into a window
Bumps
[cachix/install-nix-action](https://github.com/cachix/install-nix-action)
from 31.5.1 to 31.5.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/install-nix-action/releases">cachix/install-nix-action's
releases</a>.</em></p>
<blockquote>
<h2>v31.5.2</h2>
<h2>What's Changed</h2>
<ul>
<li>nix: 2.30.1 -> 2.30.2 by <a
href="https://github.com/github-actions"><code>@github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/cachix/install-nix-action/pull/246">cachix/install-nix-action#246</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/install-nix-action/compare/v31...v31.5.2">https://github.com/cachix/install-nix-action/compare/v31...v31.5.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fc6e360bed"><code>fc6e360</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/install-nix-action/issues/246">#246</a>
from cachix/create-pull-request/patch</li>
<li><a
href="9d5112343e"><code>9d51123</code></a>
nix: 2.30.1 -> 2.30.2</li>
<li>See full diff in <a
href="c134e4c9e3...fc6e360bed">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
This brings in configuration reloading and toasts to gtk-ng.
Config reloading is fairly different in ng than legacy because we rely
on our GObject `Config` class and ref counting more heavily. We rely on
various property bindings and notify signals to propagate configuration
changes out to all subscribers. Previously we manually had to chain this
together.
Toasts are straightforward, with the main difference being that the
window owns its own toasts (surfaces can't trigger them) and triggers
them via signal emission.
This adds functionality for choosing different normalization metrics for
each fallback font. It's not exposed as a config option, but could be in
the future, which would probably go a long way towards addressing
concerns like #7929.
The currently available reference metrics are, in priority order:
`ic_width, ex_height, cap_height, line_height, em_size`. The default
value is `ic_width`.
By priority order, I mean that if the chosen metric is not defined in
the fallback font, we move to the next metric in the list---we don't
normalize by an estimated metric from the fallback font (however, we're
happy to use an estimated metric from the primary font, that's how
`ic_width` normalization between CJK and Latin fonts work). This extends
the pattern that was used between `ic_width` and `ex_height` in the
existing hardcoded rule. `line_height` is always defined, so the buck
stops there.
What motivated me to implement this was the fact that, with the existing
hardcoded rule, the embedded symbols-only Nerd Font was always scaled up
by a factor of 1.2, which turned out to be an important reason why it's
been difficult to make icon scaling work to everyone's satisfaction.
Accordingly, the symbols-only font is the first to take advantage of the
new functionality. If this PR is merged, #7917 is no longer needed. (To
limit the scope of this PR, it only includes the minimal changes to let
icon scaling take advantage of this functionality. I may submit a
follow-up PR with some further icon scaling improvement enabled by
this.)
This ports over a basic headerbar, maximize, and fullscreen. The
headerbar only has the main menu button for now since we have no tabbing
or splits. The main menu has the full main menu that mainline GTK has
but most of it is disabled since we don't implement the actions yet.
I didn't use anything from your branch @tristan957 so I didn't add
coauthor but I want to note that @tristan957 worked on this as well and
I suspect there's overlap.
This fixes a double-free that Valgrind found when the quit action was
used (the keybinding to quit or the menu item). This fixes it in both
the gtk and gtk-ng apprts.
The issue stems from the fact that our quit action worked by traversing
the toplevels and destroying all windows. When all windows are
destroyed, GTK exits the main loop.
When fcitx is used as the input method editor (IME), it appears to hold
its own `gtk.Window` widget as a property (probably for the IME popup).
Unfortunately this does not react well to being destroyed externally and
triggers a double-free when the IME widget also tries to dispose itself.
I think this is probably a bug somewhere in the GTK IME widget because
it should be resilient to this kind of destruction. But, we can't
tolerate a double free in the mean time.
We can still quit by destroying only OUR windows (which cascades to
destroy everything else).
This fixes a double-free that Valgrind found when the quit action was
used (the keybinding to quit or the menu item).
The issue stems from the fact that our quit action worked by traversing the
toplevels and destroying all windows. When all windows are destroyed,
GTK exits the main loop.
When fcitx is used as the input method editor (IME), it appears to hold
its own `gtk.Window` widget as a property (probably for the IME popup).
Unfortunately this does not react well to being destroyed externally and
triggers a double-free when the IME widget also tries to dispose itself.
I think this is probably a bug somewhere in the GTK IME widget because
it should be resilient to this kind of destruction. But, we can't
tolerate a double free in the mean time.
We can still quit by destroying only OUR windows (which cascades to
destroy everything else).
I changed my mind, this is a pretty small change and relevant to the
intent of the PR. This brings the appearance of the embedded symbols
much closer to patched fonts.
With this, the sizes of most symbols are nearly identical to a patched
font, the only big difference is positioning (and TBH I think we do a
better job positioning than the patcher does, since we have knowledge
about the cell size).
Move size adjustment logic out of `Entry`, I understand the impulse to
put it there but it results in passing a lot of stuff around which isn't
great.
Rework `add(...)` in to `add(...)` and `addDeferred(...)`, faces are
passed directly now instead of passing an entry, and an options struct
is used instead of positional arguments for things like style, fallback,
and size adjustment.
Change size adjustment test back to a half pixel tolerance instead of 5%
because the previous commit (allowing fractional pixel sizes) fixed the
root cause of large differences.
This makes the `new_window` action properly inherit properties from the
parent surface that initiated the action. Today, that is only the pwd
and font size.
This makes the `new_window` action properly inherit properties from the
parent surface that initiated the action. Today, that is only the pwd
and font size.
For now, this just emits a signal that an embedding widget will react to
to do something like flashing the window.
I think we should implement the audio bell in the actual surface view
but I don't currently have audio drivers hooked up in my Linux VM and
I'm away from my desktop PC. :)
cc @jcollie if you're interested, pretty tightly scoped.
For now, this just emits a signal that an embedding widget will react to
to do something like flashing the window.
I think we should implement the audio bell in the actual surface view
but I don't currently have audio drivers hooked up in my Linux VM and
I'm away from my desktop PC. :)
This change might be good, but it is incomplete and not relevant to the
PR that it's a part of. I'll explore making this change separately after
this, since it might be a good idea.
(It's incomplete since the attribute data was not re-generated based on
the non-mono file.)
A variety of naming, commenting, and formatting improvements + a few
explicit error sets. This commit has no functional changes, though it
does remove a couple functions that didn't really need to exist.
This ports the child exited overlay.
We're able to use Zig comptime and Blueprint templates to use the same
Surface blueprint for this even if libadwaita is too old to support
banners (< 1.3) by inheriting from `gtk.Widget` instead and not
instantiating the blueprint. Its a bit noisy to maintain the `noop`
version but we should be able to test that compilation in CI (we do via
Debian 12).
GTK dynamically loads librsvg when it needs to convert a SVG-only icon
(or any other SVG-only resource) for display. This PR adds the libraries
that GTK needs so that running programs from within the developer shell
can display those SVG resources.
GTK dynamically loads librsvg when it needs to convert a SVG-only icon
(or any other SVG-only resource) for display. This PR adds the libraries
that GTK needs so that running programs from within the developer shell
can display those SVG resources.
This adds a new icon for the GTK-based application that adheres (mostly)
to the Gnome Human Interface Guidelines (HIG). The icon is designed to
fit in better with other Gnome applications.
While there isn't a single standard "native" style amongst Linux
applications, I believe this better fits the general Linux desktop
ecosystem over our macOS icon.
The icon itself is undeniably Ghostty. The core design language is the
same and I don't think ayone will mistake it for anything else. I wanted
to keep the brand the same, but making it fit in better aligns with
Ghostty's goal of being "platform native".
As of this PR, you can't use the macOS icon on Linux without modifying
the source. We may provide a compile-time option to swap icons in the
future (unfortunately Linux desktop applications require hardcoding an
icon path in the desktop files, so making it runtime selectable is...
messy!)
<img width="2048" height="2048" alt="2048"
src="https://github.com/user-attachments/assets/0b15e6db-bb6d-424c-8a83-ca809759b0c4"
/>
This adds a new icon for the GTK-based application that adheres (mostly)
to the Gnome Human Interface Guidelines (HIG). The icon is designed to
fit in better with other Gnome applications.
While there isn't a single standard "native" style amongst Linux
applications, I believe this better fits the general Linux desktop
ecosystem over our macOS icon.
The icon itself is undeniably Ghostty. The core design language is the
same and I don't think ayone will mistake it for anything else. I wanted
to keep the brand the same, but making it fit in better aligns with
Ghostty's goal of being "platform native".
Add serialization for tab titles in SurfaceView to persist user-set titles across app restarts. Bump TerminalRestorableState version to 4 to handle the new format.
This creates a helper so that we can call
[`gtk_widget_class_bind_template_callback_full`](https://docs.gtk.org/gtk4/class_method.Widget.bind_template_callback_full.html)
and register signal handlers directly in our Blueprint file. This gets
rid of a LOT of boilerplate!
A draft, since there are TODOs:
- [x] Add comptime verification of the `func` param
- [x] Convert more blueprint files
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.12 to 1.2.13.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9ff6d4004d"><code>9ff6d40</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/27">#27</a>
from namespacelabs/niklas-go-single-command</li>
<li><a
href="11812fa33c"><code>11812fa</code></a>
Use a single <code>go env</code> invocation in <code>go</code> cache
mode.</li>
<li><a
href="5dd70d607c"><code>5dd70d6</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/26">#26</a>
from namespacelabs/niklas-doc</li>
<li><a
href="834227b234"><code>834227b</code></a>
document brew mode</li>
<li>See full diff in <a
href="c343d6c4c2...9ff6d4004d">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
I became far less stupid and figured out how to figure this out by
reading the source code and since then I've been enlightened and can
clean up our Blueprints quite a bit. Yay!
I learned that you can add overlays to a `gtk.Overlay` with the
`[overlay]` child type. And if you embed event controllers directly,
`gtk.Widget` adds them
(https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gtk/gtkwidget.c#L8805-8808).
cc @tristan957 yay!
I became far less stupid and figured out how to figure this out by
reading the source code and since then I've been enlightened and can
clean up our Blueprints quite a bit. Yay!
Hi there, this is just a low-hanging fruit and it also prepares the way
for the future 0.15, which removes addStaticLibrary.
Please, let me know what to do on the `// TODO` comments.
This ports over read/write clipboard to gtk-ng.
This was a surprisingly massive amount of work! The clipboard
confirmation dialog is non-trivial: it supports multiple read/write
types, blurring, remember choice, and spans multiple Adw versions. I was
able to port all of the functionality into a single
`CloseConfirmationDialog` class and make use of a good amount of
Blueprint binds to simplify some stuff.
xterm docs explicitly say that empty payloads should be permitted and
are used to clear the selected clipboards, so we need to implement that
correctly. The GTK apprt still shows a "Copied to Clipboard" toast though
and we might want to change that too
This implements the resize overlay. This is implemented using a new
mostly generic `ResizeOverlay` class that can probably be renamed one
day to something like `TemporaryOverlay` since it is generic to show a
label with a duration.
The only user-facing change here is `after-first` behavior has been
changed in the config to actually mean "after a delay." The
`after-first` behavior has been problematic since we introduced it on
both macOS and Linux because a lot of windowing systems may perform
multiple resizes very quickly at startup (especially tiling ones) so its
less about being "first" and more about semantically only showing the
overlay for user-driven resizes. We should rename this, eventually.
The valgrind suppression file change is mostly to handle an alternate
machine, but its all the same stuff (GTK renderers, GPU drivers, etc.),
nothing new in our app code.
This PR tidies up our quit logic to match the GTK implementation,
respecting quit delays and also showing a confirmation dialog if
required. There shouldn't be anything surprising here, its mostly a
copy/paste of the old logic with very small tweaks to fit the new style.
Continuing to plumb along the APIs necessary basic surface functionality
before moving onto windowing functionality. This adds title/pwd as
properties to Surface, adds abstractions necessary to manage that memory
correctly, and also adds a tiny change to renderers to make everything
slightly more usable under Valgrind.
Relatively simple port. A few cool things:
1. We use properties on `GhosttySurface` to set this now and standard
property listeners
2. We make `terminal.MouseShape` a GObject enum if we have gobject
available.
3. The property based approach means we don't have to manage
`*gdk.Cursor` memory anywhere anymore.
And, we're still Valgrind clean.
Found by Valgrind:
```
==265734== 320 bytes in 8 blocks are definitely lost in loss record 13,786 of 15,141
==265734== at 0x5A65810: malloc (in /nix/store/l431jn8ahj09g5c1arrl7q6wcxngg21q-valgrind-3.24.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==265734== by 0x3D799EB: onig_region_resize (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:923)
==265734== by 0x3D81BCA: onig_region_resize_clear (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:949)
==265734== by 0x3DA814F: search_in_range (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5454)
==265734== by 0x3DA7F25: onig_search (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5414)
==265734== by 0x382E7E8: regex.Regex.searchAdvanced (regex.zig:61)
==265734== by 0x382E974: regex.Regex.search (regex.zig:48)
==265734== by 0x382EC08: terminal.StringMap.SearchIterator.next (StringMap.zig:40)
==265734== by 0x39ADDCD: renderer.link.Set.matchSetFromLinks (link.zig:320)
==265734== by 0x39AE5C7: renderer.link.Set.matchSet (link.zig:95)
==265734== by 0x39BCCE1: renderer.generic.Renderer(renderer.OpenGL).rebuildCells (generic.zig:2307)
==265734== by 0x39C3BDB: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1228)
==265734== by 0x3993E88: renderer.Thread.renderCallback (Thread.zig:607)
==265734== by 0x3993CFF: renderer.Thread.wakeupCallback (Thread.zig:524)
==265734== by 0x39C522E: callback (async.zig:679)
==265734== by 0x39C522E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_592685__struct_596870.callback (async.zig:181)
==265734== by 0x3970EAE: backend.io_uring.Completion.invoke (io_uring.zig:804)
==265734== by 0x3973AD8: backend.io_uring.Loop.tick___anon_586861 (io_uring.zig:193)
==265734== by 0x3973BCD: backend.io_uring.Loop.run (io_uring.zig:84)
==265734== by 0x3978673: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==265734== by 0x3978972: renderer.Thread.threadMain_ (Thread.zig:263)
==265734== by 0x3954580: renderer.Thread.threadMain (Thread.zig:202)
==265734== by 0x39279CA: Thread.callFn__anon_573552 (Thread.zig:488)
==265734== by 0x38F4594: Thread.PosixThreadImpl.spawn__anon_570448.Instance.entryFn (Thread.zig:757)
==265734== by 0x6E567EA: start_thread (pthread_create.c:448)
==265734== by 0x6ED9FB3: clone (clone.S:100)
==265734==
```
Found by Valgrind:
```
==265734== 320 bytes in 8 blocks are definitely lost in loss record 13,786 of 15,141
==265734== at 0x5A65810: malloc (in /nix/store/l431jn8ahj09g5c1arrl7q6wcxngg21q-valgrind-3.24.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==265734== by 0x3D799EB: onig_region_resize (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:923)
==265734== by 0x3D81BCA: onig_region_resize_clear (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:949)
==265734== by 0x3DA814F: search_in_range (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5454)
==265734== by 0x3DA7F25: onig_search (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5414)
==265734== by 0x382E7E8: regex.Regex.searchAdvanced (regex.zig:61)
==265734== by 0x382E974: regex.Regex.search (regex.zig:48)
==265734== by 0x382EC08: terminal.StringMap.SearchIterator.next (StringMap.zig:40)
==265734== by 0x39ADDCD: renderer.link.Set.matchSetFromLinks (link.zig:320)
==265734== by 0x39AE5C7: renderer.link.Set.matchSet (link.zig:95)
==265734== by 0x39BCCE1: renderer.generic.Renderer(renderer.OpenGL).rebuildCells (generic.zig:2307)
==265734== by 0x39C3BDB: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1228)
==265734== by 0x3993E88: renderer.Thread.renderCallback (Thread.zig:607)
==265734== by 0x3993CFF: renderer.Thread.wakeupCallback (Thread.zig:524)
==265734== by 0x39C522E: callback (async.zig:679)
==265734== by 0x39C522E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_592685__struct_596870.callback (async.zig:181)
==265734== by 0x3970EAE: backend.io_uring.Completion.invoke (io_uring.zig:804)
==265734== by 0x3973AD8: backend.io_uring.Loop.tick___anon_586861 (io_uring.zig:193)
==265734== by 0x3973BCD: backend.io_uring.Loop.run (io_uring.zig:84)
==265734== by 0x3978673: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==265734== by 0x3978972: renderer.Thread.threadMain_ (Thread.zig:263)
==265734== by 0x3954580: renderer.Thread.threadMain (Thread.zig:202)
==265734== by 0x39279CA: Thread.callFn__anon_573552 (Thread.zig:488)
==265734== by 0x38F4594: Thread.PosixThreadImpl.spawn__anon_570448.Instance.entryFn (Thread.zig:757)
==265734== by 0x6E567EA: start_thread (pthread_create.c:448)
==265734== by 0x6ED9FB3: clone (clone.S:100)
==265734==
```
This introduces a new `GhosttyDialog` class that either inherits from
`adw.MessageDialog` or `adw.AlertDialog`, depending on the version of
libadwaita we compile against. This is the same logic we used
previously.
This lets us have a single libadw 1.2 blueprint file for all dialogs and
we just do the right thing at compile time!
This introduces a new `GhosttyDialog` class that either inherits from
`adw.MessageDialog` or `adw.AlertDialog`, depending on the version of
libadwaita we compile against. This is the same logic we used
previously.
This lets us have a single libadw 1.2 blueprint file for all dialogs and
we just do the right thing at compile time!
A small, simple change. This implements the `Surface.close` apprt
required function. After this PR, a process exiting within the terminal
will close the window properly (unless `wait-after-command` is set of
course!).
This doesn't yet show close confirmation. I'm working on some dialog
refactors on the side to see if we can simplify our Adw 1.2 vs. 1.5
dialogs and didn't want to include it here.
Close now works by the `GhosttySurface` class emitting the
`close-request` signal (similar to a `gtk.Window`) and the parent
container is responsible for closing it. This will let us reuse the
surface within different contexts: tabs, splits, etc.
This also remove the unused `shouldClose`, `setShouldClose` apprt APIs
which are a holdover from the glfw days!
This is a tiny addon from the recent gtk-ng work. We've moved redraw
requests into the apprt action system (the `render` action). I waited
until I had my macOS machine to verify that this fix could work. We can
now remove this completely.
We can probably remove the redraw inspector API too at some point but
I'm not there yet with the GTK backend so I'll just wait on it.
This is a tiny addon from the recent gtk-ng work. We've moved redraw
requests into the apprt action system (the `render` action). I waited
until I had my macOS machine to verify that this fix could work. We can
now remove this completely.
We can probably remove the redraw inspector API too at some point but
I'm not there yet with the GTK backend so I'll just wait on it.
This ports back all our event controllers back to the `GhosttySurface`.
With this PR, the terminal is now usable again at a very very simple
level!
This also brings back `winproto` but its still filled with
incompatibilities. I just need to bring that back so modifiers worked
properly. We'll fix that up in a future PR.
This also fixes one undefined memory access in debug modes found by
Valgrind.
This ports over the `new_window` functionality and shows a single
no-input, no-tab, no-split surface. The surface renders (e.g. the cursor
blinks) and it is a full Ghostty surface underneath so the shell prompt
shows up and everything. However, the surface doesn't respond to input.
I'm going to put this PR up in this state so that it isn't too much all
at once.
This work also required some core libghostty improvements that just
didn't fit the model being built here. They're extremely minimal,
however (basically going from struct fields to struct decls so we can do
some logic).
A couple new Valgrind suppressions had to be added to deal with GLAreas.
These suppressions were necessary before we ever hooked up our renderers
so they're caused by GTK itself. Only two, though! Other than that,
Ghostty runs **Valgrind clean**.
<img width="1726" height="714" alt="2025-07-18-131732_hyprshot"
src="https://github.com/user-attachments/assets/9c8bfe86-705c-4173-916b-df2b9b54dbfd"
/>
Even though the viewport pin isn't used unless the `viewport` is `pin`,
it's still possible to access undefined data through `clone`. Valgrind
found this:
```
==107091== Conditional jump or move depends on uninitialised value(s)
==107091== at 0x392B96A: terminal.PageList.clone (PageList.zig:540)
==107091== by 0x392C9A0: terminal.Screen.clonePool (Screen.zig:348)
==107091== by 0x392DF7A: terminal.Screen.clone (Screen.zig:330)
==107091== by 0x394E6D4: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1129)
==107091== by 0x3919BF8: renderer.Thread.renderCallback (Thread.zig:607)
==107091== by 0x3919A6F: renderer.Thread.wakeupCallback (Thread.zig:524)
==107091== by 0x394FA6E: callback (async.zig:679)
==107091== by 0x394FA6E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_436371__struct_440666.callback (async.zig:181)
==107091== by 0x38F781E: backend.io_uring.Completion.invoke (io_uring.zig:804)
==107091== by 0x38FA448: backend.io_uring.Loop.tick___anon_431479 (io_uring.zig:193)
==107091== by 0x38FA53D: backend.io_uring.Loop.run (io_uring.zig:84)
==107091== by 0x38FEFE3: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==107091== by 0x38FF2E2: renderer.Thread.threadMain_ (Thread.zig:263)
==107091== by 0x38DDF80: renderer.Thread.threadMain (Thread.zig:202)
==107091== by 0x38B5C0A: Thread.callFn__anon_421402 (Thread.zig:488)
==107091== by 0x3888604: Thread.PosixThreadImpl.spawn__anon_418943.Instance.entryFn (Thread.zig:757)
==107091== by 0x6C6E7EA: start_thread (pthread_create.c:448)
==107091== by 0x6CF1FB3: clone (clone.S:100)
==107091==
```
Even though the viewport pin isn't used unless the `viewport` is `pin`,
it's still possible to access undefined data through `clone`. Valgrind
found this:
```
==107091== Conditional jump or move depends on uninitialised value(s)
==107091== at 0x392B96A: terminal.PageList.clone (PageList.zig:540)
==107091== by 0x392C9A0: terminal.Screen.clonePool (Screen.zig:348)
==107091== by 0x392DF7A: terminal.Screen.clone (Screen.zig:330)
==107091== by 0x394E6D4: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1129)
==107091== by 0x3919BF8: renderer.Thread.renderCallback (Thread.zig:607)
==107091== by 0x3919A6F: renderer.Thread.wakeupCallback (Thread.zig:524)
==107091== by 0x394FA6E: callback (async.zig:679)
==107091== by 0x394FA6E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_436371__struct_440666.callback (async.zig:181)
==107091== by 0x38F781E: backend.io_uring.Completion.invoke (io_uring.zig:804)
==107091== by 0x38FA448: backend.io_uring.Loop.tick___anon_431479 (io_uring.zig:193)
==107091== by 0x38FA53D: backend.io_uring.Loop.run (io_uring.zig:84)
==107091== by 0x38FEFE3: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==107091== by 0x38FF2E2: renderer.Thread.threadMain_ (Thread.zig:263)
==107091== by 0x38DDF80: renderer.Thread.threadMain (Thread.zig:202)
==107091== by 0x38B5C0A: Thread.callFn__anon_421402 (Thread.zig:488)
==107091== by 0x3888604: Thread.PosixThreadImpl.spawn__anon_418943.Instance.entryFn (Thread.zig:757)
==107091== by 0x6C6E7EA: start_thread (pthread_create.c:448)
==107091== by 0x6CF1FB3: clone (clone.S:100)
==107091==
```
Every GObject class we're ever going to make has the same handful of
methods. This adds *just enough* noise to be annoying. This commit
extracts the common ones so far into a central mixin. Since Zig is
removing `usingnamespace` and has no other mixin mechanism, we must
forward the decls, but this is still cleaner imo than what we did
before.
I suspect longer term we can probably abstract more of the `zig-gobject`
boilerplate into a higher level abstraction but I'm not confident doing
that yet across the 4 classes we have so far.
Credit to @pluiedev for pointing this out.
Every GObject class we're ever going to make has the same handful of
methods. This adds *just enough* noise to be annoying. This commit
extracts the common ones so far into a central mixin. Since Zig is
removing `usingnamespace` and has no other mixin mechanism, we must
forward the decls, but this is still cleaner imo than what we did
before.
I suspect longer term we can probably abstract more of the `zig-gobject`
boilerplate into a higher level abstraction but I'm not confident doing
that yet across the 4 classes we have so far.
Ghostty has had support for a while (since PR #3124) for parsing
progress reports but never did anything with them. This PR adds the core
infrastructure and an implementation for GTK.
On GTK, the progress bar will show up as a thin bar along the top of the
terminal. Under normal circumstances it will use whatever you have set
as your accent color. If the progam sending the progress report
indicates an error, it will change to a reddish color.
Ghostty has had support for a while (since PR #3124) for parsing progress
reports but never did anything with them. This PR adds the core
infrastructure and an implementation for GTK.
On GTK, the progress bar will show up as a thin bar along the top of
the terminal. Under normal circumstances it will use whatever you have
set as your accent color. If the progam sending the progress report
indicates an error, it will change to a reddish color.
This ports the config errors dialog from `apprt/gtk` to `gtk-ng`.
The major change here is that we now use proper template bindings for
the content. To do this, a `ghostty.Config` is now wrapped in a GObject
`GhosttyConfig` to make it safe to pass around (ref count) and to
provide helpful properties like the diagnostics buffer we bind to.
As a minor change, I stripped the `Ghostty` prefix from our GObject
classes in Zig code. For templates its all still there as is the norm.
This retains the exact same version requirements and layout as the
existing one.
Looks like 52354b8 missed noting the outgoing screen selection state's
rectangle flag when setting the selection on mouse release, this was
causing the selection that was actually set to be
standard/wrap-selected. This corrects that by just shipping said flag
when calling `setSelection`.
Looks like 52354b8 missed noting the outgoing screen selection state's
rectangle flag when setting the selection on mouse release, this was
causing the selection that was actually set to be
standard/wrap-selected. This corrects that by just shipping said flag
when calling setSelection.
Surprise, @ghostty-org/gtk! Hopefully a happy one.
This PR introduces the boilerplate for a new apprt I'm calling `gtk-ng`.
The `gtk-ng` apprt is still GTK, but built up from first principles
using the GObject type system, Blueprint files, etc. This will
ultimately replace and become `gtk` (the `-ng` suffix will be stripped
once we fully replace our existing GTK apprt).
In this PR, the `gtk-ng` apprt does nothing more but show a "Hello,
Ghostty" GTK window. It doesn't run a terminal, yet. 😄 I want to
use this PR to introduce the boilerplate and share my motivations.
Since `gtk-ng` and `gtk` are separate apprts, I can PR small,
reviewable, and risky changes into `gtk-ng` rather than opening some
mega-PR that replaces everything all at once. Simultaneously, we can
continue to iterate on and maintain our shipping `gtk` apprt without
dealing with conflicts.
> [!IMPORTANT]
>
> To reiterate, this PR doesn't change anything about our `gtk` apprt.
Builds by default will still use the `gtk` apprt and we can continue to
build both `gtk` and `gtk-ng` side by side (actually, a very important
property until we can be confident we've reached parity).
## A Refactor, Not a Rewrite
The primary goal of this apprt is to _primarily_ be a **refactor, not a
rewrite.**
As much as possible, I'm going to be bringing over a lot of the same
logic from `gtk` as long as it fits and makes sense, but applying it to
our new structure and lifecycle. For example in this PR you can see how
we handle style manager, cgroups, etc. and how that fits within the new
`GhosttyApplication` class.
Our GTK apprt from a business logic standpoint is _pretty damn good_ and
_pretty damn stable_. There's no need to rock that boat and try to
rewrite core logic such as input handling, X11/Wayland stuff, etc. It
just has to be massaged into the new structure.
## Why?
**Object-oriented, reference-counted systems are good for UI,
actually.** Experience iterating on the non-trivial macOS application
has really reaffirmed that OOP and memory managed systems are really,
really nice for GUI. I'm not a huge OOP fan in general, but it fits GUI
patterns extremely well. And memory management of any form (GC, Ref
Counts, etc.) is important in GUIs where "objects" are handed off to
various owners at different times, the most concrete example being:
splits moving across windows or into an undo management system.
**Blueprint and UI definitions have been a success.** These were
introduced in an incremental way into the `apprt/gtk` (thanks ❤️ )
and have been great. But our existing non-GObject system makes it hard
to go _all in_ on them, e.g. bindings. Moving to a full GObject-based
system will let us fully adopt this.
**`zig-gobject` is good and stable.** This didn't really exist when we
started the GTK apprt (see the long history below). Since adopting it,
its proven to be an excellent, stable dependency. I'm ready to go all-in
on it.
**Memory management has been a challenge.** Our mix of GObject and
non-GObject lifetimes within the GTK apprt has consistently been a
source of memory leaks at best and crashes at worst. For example,
`Window`, `Surface`, `Tab`, etc. have weird lifetimes that we try to
pair alongside their GTK counterparts and its nasty and I don't think
anyone who maintains this will disagree. By representing all of these
concepts as GObject or Widget subclasses, we'll align all their
lifetimes as expected.
**Personally, I've grown a lot, particularly from working on the macOS
side.** I think all of us as programmers can agree that _programming in
multiple languages makes us better programmers_. Similarly, building the
macOS app has shown me patterns and techniques that would make our
GTK-based application better. I'd like to bring those to the GTK side.
(Likewise, I've improved the macOS side from periods of time working on
the GTK side and I suspect that might happen again!)
### Longer Background
It's easy to rewrite. And I think our maintainers know that I'm not a
fan of rewrites. I think its the wrong decision most of the time. It is
easy to look at "legacy" code (especially code you didn't write
yourself), be disgusted, and think you can rewrite it all better. But no
engineer sets out to create technical debt, and I think its worth
respecting how and why some code came to be before embarking on
something new. This section does that.
#### The Beginning
Ghostty started as a pure Zig-based GLFW app, with no concept of
"apprt". It was Linux-only, and X11-only. At some point, I refactored
out the "apprt" system in order to introduce GTK4 (GTK4 came before any
macOS work). For the initial GTK4 work, I decided to just call into the
libgtk C APIs directly. There were various contributing factors for this
decision:
1. Zig was _rapidly_ changing, and we were on nightly Zig. This was
around the Zig 0.11, 0.12 times. Taking on new Zig dependencies was
really dangerous because Zig nightly could break all of us at any
moment.
2. [`zig-gobject`](https://github.com/ianprime0509/zig-gobject) was
brand new and unstable. Given point 1, I discarded it and did straight C
APIs.
3. Ghostty itself was very simple. We didn't support tabs, we didn't
support splits. We were still primarily concerned with making the
terminal stable. We weren't on the "native UI" part of our roadmap quite
yet. This was our initial foray in that direction.
4. On a personal level, I hadn't done real native GUI programming in a
_long_ time (on any platform). Recall the macOS apprt didn't exist yet,
either. Jumping into "plain old Zig" with "plain old C APIs" was a
practical, no-nonsense way for me to get going.
Given all this, I still believe I (it was only me then) made the right
decision for the time.
#### Zig, GTK apprt Stabilization
Eventually, the factors listed above changed: (1) Zig stabilized more
and Ghostty moved to stable Zig for various reasons. (2) `zig-gobject`
became a mature, stable library. (3) Ghostty the application has become
increasingly complex (in a good way, we support a ton of awesome
"platform UI" features).
Socially, the @ghostty-org/gtk subsystem team was created and is filled
with people who are experienced with GTK and Linux in general. This team
introduced more idiomatic GTK concepts into the project such as
blueprint files, a `zig-gobject` migration, and more.
The @ghostty-org/gtk subsystem maintainers have done an awesome job
iterating on this change within the existing `apprt/gtk`. This has been
often frustrating, but it was a pragmatic approach to move us towards
the future and let us ship new features into GTK4 to pursue our platform
UI goals.
#### GUI Maturity
We're now at the point where the core Ghostty terminal (the core,
terminal emulation) is incredibly stable. We don't have exact numbers
but we can confidently assume its used by thousands of people everyday
for real, professional work.
As such, most of the changes within the 1.1 and 1.2 cycle have been at
the apprt/GUI layer, introducing significantly more complexity:
localization, more X11/Wayland integrations, more text to native
elements like the process exit overlay, etc.
I recently rewrote the entire terminal, tab, and split data model in the
macOS app to give us a better foundation for future functionality, and
to improve our memory management story (surface leaks were a common
problem before, and they haven't happened since since the lifetime of a
surface is so much more obvious). This also let me iterate more quickly
on more features such as undo/redo, but will also more easily enable
things like split titles, merging splits into tabs/windows, etc. (not
done yet).
I think its time for this type of change within the GTK apprt as well.
We have the collective real world experience and we've put in the work
in iteration to understand what needs to be done.
The XDG Freedesktop Portal has a _major_ undocumented requirement for
programs that are launched/controlled by `systemd` to interact with the
Portal. The unit _must_ be named `app-<appid>.service`. The Portal uses
the systemd unit name figure out what the program's application ID is
and it will only look at unit names that begin with `app-`. I can find
no place that this is documented other than by inspecting the code or
the
issue and PR that introduced this feature. See the following code:
7d4d48cf07/src/xdp-utils.c (L152-L220)
This may fix many people's issues with getting global shortcuts
to work.
Note that this is a breaking change if you have been using Ghostty
compiled from source since
https://github.com/ghostty-org/ghostty/pull/7433 was merged. You will
need to ensure
that any Ghosty systemd unit files _not_ prefixed with `app-` are
deleted.
Original discussion/PR in the XDG Desktop Portal repository:
https://github.com/flatpak/xdg-desktop-portal/issues/579https://github.com/flatpak/xdg-desktop-portal/pull/719
Originally discussed on Discord:
https://discord.com/channels/1005603569187160125/1394845362186879026
Co-authored-by: ambareeshbalaji@gmail.com
The XDG Freedesktop Portal has a _major_ undocumented requirement for
programs that are launched/controlled by `systemd` to interact with the
Portal. The unit _must_ be named `app-<appid>.service`. The Portal uses
the systemd unit name figure out what the program's application ID is
and it will only look at unit names that begin with `app-`. I can find
no place that this is documented other than by inspecting the code or the
issue and PR that introduced this feature. See the following code:
7d4d48cf07/src/xdp-utils.c (L152-L220)
This may fix many people's issues with getting global shortcuts
to work.
Note that this is a breaking change if you have been using Ghostty
compiled from source since #7433 was merged. You will need to ensure
that any Ghosty systemd unit files _not_ prefixed with `app-` are
deleted.
Original discussion/PR in the XDG Desktop Portal repository:
https://github.com/flatpak/xdg-desktop-portal/issues/579https://github.com/flatpak/xdg-desktop-portal/pull/719
Originally discussed on Discord:
https://discord.com/channels/1005603569187160125/1394845362186879026
Co-authored-by: ambareeshbalaji@gmail.com
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
There are still problems linking due to `gettext`. No idea if this
actually _works_ on Windows. File locking had to be disabled on Windows
because of a bug in the Zig std library. Adding all of the explicit
error sets happened due to disabling file locking. Fixing permissions
had to be disabled on Windows as the Windows file system does not
support permissions in the way that POSIX systems like macOS and Linux
do.
There are still problems linking due to `gettext`. No idea if this
actually _works_ on Windows. File locking had to be disabled on Windows
because of a bug in the Zig std library. Adding all of the explicit
error sets happened due to disabling file locking. Fixing permissions
had to be disabled on Windows as the Windows file system does not
support permissions in the way that POSIX systems like macOS and Linux
do.
This will (on GTK) use a D-Bus method call to tell a running Ghostty
instance to open a new window. If D-Bus activation is configured
properly, Ghostty does not need to be running first.
This could be extended to other platforms, e.g. AppleScript on macOS.
When Ghostty develops a native API, that could be used instead to create
a new window.
Instead of using @hasDecl, use a performAction-stype API. The C
interface for interfacing with macOS (or any other apprt where Ghostty
is embedded) is unfinished.
This adds the `-e` flag to the `+new-window` CLI action. This allows a
command to be passed from the CLI to the running instance of Ghostty.
Nothing is done with that command besides logging its presence.
- Add a `GHOSTTY_CLASS` environment variables to any command executed by
Ghostty to make discovering the correct application ID easier.
- Add a flag to force the relelase application ID.
- Ensure that CLI flags to `+new-window` are mutually exclusive.
- Fix documentation about D-Bus activation requirements.
This will (on GTK) use a D-Bus method call to tell a running
Ghostty instance to open a new window. If D-Bus activation is
configured properly, Ghostty does not need to be running first.
This could be extended to other platforms, e.g. AppleScript on macOS
eventually.
When Ghostty develops a native API, that could be used instead to create
a new window.
Fixes#7941
I don't fully understand the fix here. Its code we've had for awhile it
was just in the wrong place after I refactored our window management.
The comment clearly states why its there but I don't know why it is
required.
Fixes#7941
I don't fully understand the fix here. Its code we've had for awhile it
was just in the wrong place after I refactored our window management.
The comment clearly states why its there but I don't know why it is
required.
This drops our `usingnamespace` usage to one more library (zigimg via
libvaxis) which should get fixed soon. With that, we have zero
usingnamespace and we should be able to test incremental compilation
with Zig (no binary mode, so Sema only) amongst other future features.
This drops our `usingnamespace` usage to one more library (zigimg via
libvaxis) which should get fixed soon. With that, we have zero
usingnamespace and we should be able to test incremental compilation
with Zig (no binary mode, so Sema only) amongst other future features.
Setting `zig/flake-compat` to follow `""` (the current flake, ghostty)
is incorrect and introduces an unnecessary dependency cycle. This causes
problems in my NixOS configuration, because I collect flake inputs
recursively and add them to my system closure to work around
https://github.com/NixOS/nix/issues/3995#issuecomment-1537108310.
The other change to `flake.lock` was done automatically by my version of
Nix (Lix 2.91.3).
Without this, the only indication would be the difference between a red
or a green background which would be problematic for users with limited
vision or color blindness.
If a native GUI is shown by the runtime when a child exits, use the
returned boolean to determine if text should be show in the terminal to
avoid duplicating information.
Addresses #7649 for the core and GTK. macOS support will need to be
added later.
This adds an apprt action to show a native GUI warning of some kind when
the child process of a terminal exits.
Also adds a basic GTK implementation of this. In GTK it overlays an
Adwaita banner at the bottom of the window (similar to the banner that
shows up in at the top of windows in debug builds).
Related to #7879
This commit updates `zig build test` to run Xcode tests, too. These run
in parallel to the Zig tests, so they don't add any time to the test.
The Xcode tests will _not_ run when: (1) the target is not macOS, or (2)
the `-Dtest-filter` option is non-empty. This makes it so that this
change doesn't affect non-macOS and doesn't affect the general dev cycle
because you usually will run `-Dtest-filter` when developing a core
feature.
I didn't add a step to only run Xcode tests because I find that when I'm
working in Xcode I'm probably going to run the tests from there anyways.
The integration with `zig build test` is just a convenience, especially
around CI.
Speaking of CI, this change also makes it so this will run in CI.
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.8 to 1.2.9.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="0ac1550c04"><code>0ac1550</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/21">#21</a>
from namespacelabs/niklas-not-silent</li>
<li><a
href="02b3441b28"><code>02b3441</code></a>
Make tool executions not silent</li>
<li>See full diff in <a
href="449c929cd5...0ac1550c04">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Related to #7879
This commit updates `zig build test` to run Xcode tests, too. These run
in parallel to the Zig tests, so they don't add any time to the test.
The Xcode tests will _not_ run when: (1) the target is not macOS, or (2)
the `-Dtest-filter` option is non-empty. This makes it so that this
change doesn't affect non-macOS and doesn't affect the general dev cycle
because you usually will run `-Dtest-filter` when developing a core
feature.
I didn't add a step to only run Xcode tests because I find that when I'm
working in Xcode I'm probably going to run the tests from there anyways.
The integration with `zig build test` is just a convenience, especially
around CI.
Speaking of CI, this change also makes it so this will run in CI.
Our bash shell integration code uses ENV (in POSIX mode) to bootstrap
our shell integration script. This had the side effect of overwriting an
existing ENV value.
This change preserves ENV by storing it temporarily in GHOSTTY_BASH_ENV.
Note that this doesn't enable --posix mode support for automatic shell
integration. (--posix does work; we just skip shell integration when
that flag is specified.) We can reconsider implementing full --posix
support separately.
Add shellcheck to CI pipeline to ensure shell scripts follow best practices
and catch common errors. Fix existing shellcheck warnings in test scripts
to pass the new linting requirements.
Fixes#7893
Previously, custom shader cursor uniforms were only updated when the
cursor glyph was in the front (block) cursor list. This caused non-block
cursors (such as bar, underline, hollow block, and lock) to be missing
from custom shader effects.
This commit adds a helper to the cell contents struct to retrieve the
current cursor glyph from either the front or back cursor lists, and
updates the renderer to use this helper when setting custom shader
uniforms. As a result, custom shaders now receive correct cursor
information for all supported cursor styles.
The previous implementation wasn't quite working. This revision reworks
it in a few ways:
- Fix various syntax issues
- Redirect the `ssh` command to our 'ssh-integration' function
- Locate the `ghostty` binary using $GHOSTTY_BIN_DIR
- Use os:temp-dir to create our temporary directory
Also, consistently use 2-space indents, which is the Elvish standard.
The viewport pin was allowed to go below the active pin during resize,
which caused a use of null when trying to get the bottom right of the
viewport since going down by the active row count from the viewport pin
ends up below the bottom of the active area.
Ran in to this while doing font work because I was scrolled up slightly
and increasing and decreasing to see stuff at different sizes.
This PR modernizes our benchmarks. This PR focuses on the benchmark
_framework_ and not the benchmarks themselves. That will come in later
PRs.
We now produce two binaries with `-Demit-bench`: `ghostty-bench` and
`ghostty-gen`. The former is our benchmark tool. The latter is our
synthetic data generation tool. The benchmarking CLI usually takes in
data from the synthetic generator but we want to do that offline because
synthetic data generation can be slow and CPU intensive and mess up our
benchmarks.
Our previous benchmark-specific binaries (like
`ghostty-bench-codepoint-width`) are all gone. This is all executed as
subcommands in the format similar to Ghostty users: `ghostty-bench
+codepoint-width --other --args`.
Previously, synthetic data generation was a mess and all unified with
`ghostty-bench-stream` which is just nasty. A dedicated CLI now gets us
args like `ghostty-gen +osc --p-valid=0.5`. Neat!
## Signposts and Xcode/Instruments on macOS
The benchmark framework now automatically emits
[signposts](https://developer.apple.com/documentation/os/recording-performance-data)
around the code that is under test. This is surfaced in Instruments as a
region that you can visualize and zoom in on so you can omit any of the
other overhead.
Additionally, I've integrated benchmarks with libghostty and our Xcode
project so you can just right click a benchmark to open it in
Instruments.
These are macOS-specific niceties but the core benchmarking tool is
platform-agnostic.
## Generalized CLI Actions
The `src/cli/action.zig` file was generalized so that it can be shared
amongst our three action-ized binaries. The Ghostty-specific actions are
now in `src/cli/ghostty.zig`. As an added bonus, our action parsing is
now fully unit tested.
I don't like mixing refactors in with other tasks in PRs but in this
case this one was done to enable not one but two other consumers in the
same PR, so I think it fits.
## TODO
Some things I want to do before merge.
- [ ] Add flags to `ghostty-bench` to configure once mode vs duration
mode
This was in pursuit of trying to get line numbers in `zig build run` on
macOS to work, but I wasn't able to figure that out and this wasn't the
right path because static libs can't have dsyms. But, it may still be
useful to make the xcframework step dsym aware for future use so I'm
PRing this.
This also updates our libghostty build steps to use the new
`root_module` form which is recommend for Zig 0.14 and we forgot to
update long ago.
This was in pursuit of trying to get line numbers in `zig build run` on
macOS to work, but I wasn't able to figure that out and this wasn't the
right path because static libs can't have dsyms. But, it may still be
useful to make the xcframework step dsym aware for future use so I'm
PRing this.
Before, if the row count increase past the active area then we added new
rows to make sure that we had enough for the active area, but we didn't
make sure that the viewport pin wasn't below the active area pin, which
meant that later on if someone tried to get the bottom right pin for the
viewport it would overshoot and we'd use null. This resulted in either a
memory corruption bug in ReleaseFast if you scaled down the font while
scrolled up slightly, or in Debug mode it was just a crash.
Fixes#7893
Previously, custom shader cursor uniforms were only updated when the
cursor glyph was in the front (block) cursor list. This caused non-block
cursors (such as bar, underline, hollow block, and lock) to be missing
from custom shader effects.
This commit adds a helper to the cell contents struct to retrieve the
current cursor glyph from either the front or back cursor lists, and
updates the renderer to use this helper when setting custom shader
uniforms. As a result, custom shaders now receive correct cursor
information for all supported cursor styles.
The previous implementation wasn't quite working. This revision reworks
it in a few ways:
- Fix various syntax issues
- Redirect the `ssh` command to our 'ssh-integration' function
- Locate the `ghostty` binary using $GHOSTTY_BIN_DIR
- Use os:temp-dir to create our temporary directory
Also, consistently use 2-space indents, which is the Elvish standard.
This value is always set to a non-empty string, and we only need this
value after we've determined that 'ssh_hostname' is non-empty.
In bash and zsh, we also don't need to check for the 'ghostty' command
before we attempt to add the target to the cache. That command will
safely fail silently if it's not available.
This enables `systemd` to send SIGUSR2 to Ghostty to signal it to reload
the configuration. This is much easier and reliable than using a script
to search process listings for Ghostty's main PID to send the signal to.
The command to do so is:
`systemctl reload --user com.mitchellh.ghostty.service`
This allows `systemctl` to send SIGUSR2 to Ghostty to trigger a reload,
which is more convenient than scripting `ps` and `kill` to find the
Ghostty main PID.
The old math didn't allow fractional pixels on the left and bottom, and
stretched glyphs vertically since the height was always rounded up. At
very small font sizes this looked good, but at medium and even large
sizes this just made things inconsistent and janky.
These new calculations are practically pixel-identical to whatever
CoreText is doing in 99% of cases, and the remaining cases seem to be
some sort of auto-hinting since it's internal features of the glyph
getting repositioned.
Over all, I still prefer this to CoreText's quantize option, but if this
causes further issues we should probably just revert the whole thing and
go ahead and add an extra pixel of padding to the bottom and left...
The old math didn't allow fractional pixels on the left and bottom, and
stretched glyphs vertically since the height was always rounded up. At
very small font sizes this looked good, but at medium and even large
sizes this just made things inconsistent and janky.
These new calculations are practically pixel-identical to whatever
CoreText is doing in 99% of cases, and the remaining cases seem to be
some sort of auto-hinting since it's internal features of the glyph
getting repositioned.
Over all, I still prefer this to CoreText's quantize option, but if this
causes further issues we should probably just revert the whole thing and
go ahead and add an extra pixel of padding to the bottom and left...
Addresses #4156 and #5892, specifically by implementing @mitchellh's
[request](https://github.com/ghostty-org/ghostty/discussions/5892#discussioncomment-12283628)
for "opt-in shell integration".
## Problem
Ghostty's custom `xterm-ghostty` TERM value breaks terminal
functionality when SSHing to remote systems that lack the corresponding
terminfo entry. This affects enterprise environments, legacy servers,
and ephemeral instances.
## Solution
Adds two independent SSH integration flags within the existing
`shell-integration-features` configuration:
```
shell-integration-features = ssh-env,ssh-terminfo
```
- **`ssh-env`**: TERM compatibility fix (xterm-ghostty → xterm-256color)
+ environment variable propagation (COLORTERM, TERM_PROGRAM,
TERM_PROGRAM_VERSION)
- **`ssh-terminfo`**: Automatic terminfo installation on remote hosts
with caching and utility commands
Flags work independently and harmoniously when combined, allowing users
granular control over SSH integration behavior.
## Implementation
Adds SSH wrapper functions across bash, zsh, fish, and elvish that
handle TERM compatibility, environment propagation, and terminfo
installation. Follows the same pattern as existing shell integration
features - client-side only with graceful fallback behavior.
The flag-based approach allows users to choose exactly the features they
need:
- Environment compatibility only: `ssh-env`
- Terminfo installation only: `ssh-terminfo`
- Optimal experience: `ssh-env,ssh-terminfo`
- No SSH integration: omit both flags
## Evolution Note
Based on maintainer feedback, this evolved from a progressive
enhancement approach (`ssh-integration = basic | full`) to independent
flags within `shell-integration-features`. See discussion below for the
complete architectural evolution and rationale.
Continuation of discussion in
https://github.com/ghostty-org/ghostty/discussions/3134
This changes the behaviour of the new bold-color option to only affect
the default foreground text when set to a static colour. By using a
static colour, the remaining colours are rendered as bright.
For each config entry, add a comment specifying in which release it was
first added. In some cases I note when certain aspects of each config
entry were modified.
CI is currently configured to fail if there are any fuzzy matches in
translation files. This change prevents `msgmerge` from creating any
fuzzy matches when translations are updated.
For each config entry, add a comment specifying in which release it was
first added. In some cases I note when certain aspects of each config
entry were modified.
- The order of the arguments to xgettext influences the output. Since
directorty walking does not guarantee that files will be listed in
a deterministic order (especially when run on different systems) the
translation files would see a lot of churn depending on who updated
them last.
In this update the files are sorted so that the arguments to xgettext
are always in the same order. This should reduce churn in the future.
- Mark all of the files as inputs so that the Zig build system caching
will work properly.
- The order of the arguments to xgettext influences the output. Since
directorty walking does not guarantee that files will be listed in
a deterministic order (especially when run on different systems) the
translation files would see a lot of churn depending on who updated
them last.
In this update the files are sorted so that the arguments to xgettext
are always in the same order. This should reduce churn in the future.
- Mark all of the files as inputs so that the Zig build system caching
will work properly.
With these two changes (see commits for details) our scaling should be
identical to the nerd font patcher in all the ways that matter and even
slightly better in some others.
I'm really hoping this is the last change I have to make to the nerd
font scaling...
We do this by characterizing the shared bounding boxes in a static copy
of the symbols only nerd font when we're doing the codegen. This allows
us to get results of our scaling that are just as good as in a patched
font, since related glyphs can now be sized and positioned relative to
each other.
This stops things like folder icons from becoming over-wide. The patcher
typically makes these glyphs always 1 cell wide, but since we know how
it will be displayed we have the benefit of being able to make it more
than 1 cell when there's room. This makes our dynamic scaling *better*
than a static patched font :D
Using the "subpixel quantization" option for rendering our glyph was
creating bad edge cases where we'd lose the bottom or left row / column
of pixels in a glyph sometimes. I investigated the exact effect of this
option and it seems like beyond quantizing the position and scale it's
also doing some rudimentary auto-hinting. That said, the auto-hinting
doesn't do that much for us, and the fact that it horizontally snaps
coordinates to thirds of a pixel instead of whole pixels makes things
worse in terms of legibility at small pixel sizes, so ultimately it's
better with our own handling anyway.
I extensively compared the result of Apple's option with our own manual
quantization here and I'm pretty sure this will always match the whole
pixel sizes, and where it differs (other than things like crossbars) it
seems to make glyphs generally more legible not less.
- Default ssh_term to xterm-256color to eliminate fallback assignments
- Remove base64 and replace infocmp -Q2 with standard -0 -x options for
compatibility
- Use process substitution instead of intermediate ssh_config variable
- Always set TERM explicitly since ssh_term is always defined
The Ghostty Apple ID has been frozen. I'm working on figuring out how to
get it back. In the meantime, this switches the notarization to my
personal Apple ID.
I originally created the dedicated Apple ID to limit access since we
were using app passwords. But I've since discovered that we can create
API tokens that have limited access, so I don't think this is a problem
anymore.
The Ghostty Apple ID has been frozen. I'm working on figuring out how to
get it back. In the meantime, this switches the notarization to my
personal Apple ID.
I originally created the dedicated Apple ID to limit access since we
were using app passwords. But I've since discovered that we can create
API tokens that have limited access, so I don't think this is a problem
anymore.
- Simplify feature detection to use single wildcard check
- Replace ssh_env array with simple ssh_term string variable
- Use TERM environment prefix instead of save/restore pattern
- Remove unnecessary backgrounded subshell for cache operations
Nerd font icons were ***WAY*** too big depending on your font setup,
this is because we were always using the full cell height when the nerd
font patcher instead uses an "icon height" for most things. The patcher
calculates the icon height as two thirds of the font's cap height and
one third of the line height, but I've chosen to instead use 1.2 times
the cap height for more consistent results across fonts-- if the user
wants their icons bigger, they can use the `adjust-icon-height` metric
modifier (and they can also use it to make them smaller if they want
that for some reason).
I also adjusted the attributes to user horizontal cover + vertical fit
for `^` stretch modes (proportional scaling but scale up), which makes
it so that it never exceeds the cell size, since first it covers
horizontally and then scales down to fit vertically if necessary;
previously, if there were a particularly wide glyph that was scaled with
cover/cover it would exceed the available width and overflow in to
neighboring cells which wasn't good.
- Remove complex ssh_exported_vars tracking and local environment
modification in favor of trusting Ghostty's local environment
- Replace regex patterns with glob-based feature detection for better
performance
- Fix local variable declaration consistency throughout
- Streamline logic while maintaining all functionality
Icons were often WAY too big before because they were filling the whole
cell in height, which isn't great lol. This commit adds an `icon_height`
metric which is used to constrain glyphs that shouldn't be the size of
the entire cell.
This was subtly wrong in a way that was most obvious when text switched
from regular to bold, where it would seem to wiggle since the bearings
of each letter would shift by a pixel in either direction. This affected
applications like fzf which uses bold to dynamically highlight the line
you have selected.
This was subtly wrong in a way that was most obvious when text switched
from regular to bold, where it would seem to wiggle since the bearings
of each letter would shift by a pixel in either direction. This affected
applications like fzf which uses bold to dynamically highlight the line
you have selected.
This PR changes `font.Collection` to automagically adjust the sizes of
added fonts so that their metrics (specifically their ex height, or
their ideograph character width if they have one) match the primary
font. This is like
[`font-size-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust)
from CSS.
This is a big win for users who use mixed writing systems and rely
heavily on fallback fonts. For example, in #7774 it's pointed out that
CJK characters are not very well harmonized with existing Latin glyphs,
well:
|Before (`main`)|After (this PR)|
|-|-|
|<img width="326" alt="image"
src="https://github.com/user-attachments/assets/c11d372d-ec69-426d-b008-1f56a7430f23"
/>|<img width="326" alt="image"
src="https://github.com/user-attachments/assets/efcb56ea-0572-481a-b632-a0b5cd170fa9"
/>|
This also improves our handling of the horizontal alignment of fallback
glyphs. It's not an ideal solution; it only works for glyphs narrower
than the cell width because it messes with ligatures if we include
glyphs wider than the cell width; and most things would look better if
the center were proportionally remapped based on the ratio from the
glyph advance to the cell width, but that messes with glyphs designed to
align vertically so it can't be done, instead the original advance width
is centered in the cell width.
Previously produced very wrong values when calling Collection.setSize,
since it was assuming that the provided face had the same point size as
the primary face, which isn't true during resize-- so instead we just
have faces keep track of their set size, this is generally useful.
Fixes#5256
This updates the macOS apprt to implement the `OPEN_URL` apprt action to
use the NSWorkspace APIs instead of the `open` command line utility.
As part of this, we removed the `ghostty_config_open` libghostty API and
instead introduced a new `ghostty_config_open_path` API that returns the
path to open, and then we use the `NSWorkspace` APIs to open it (same
function as the `OPEN_URL` action).
Fixes#5256
This updates the macOS apprt to implement the `OPEN_URL` apprt action to
use the NSWorkspace APIs instead of the `open` command line utility.
As part of this, we removed the `ghostty_config_open` libghostty API and
instead introduced a new `ghostty_config_open_path` API that returns the
path to open, and then we use the `NSWorkspace` APIs to open it (same
function as the `OPEN_URL` action).
In #7808, we stopped using PS0 to reset the cursor shape because
restoring PS0 in __ghostty_preexec was causing issues (#7802).
The alternate approach of printing the cursor reset escape sequence
directly from __ghostty_preexec caused a new issue: the input cursor
would persist longer than intended, such as when a suspended vim process
was restored to the foreground.
This change takes a different approach. We now conditionally add the
cursor shape escape sequences to PS0 (and PS1, for consistency) when
they don't already appear. The fixes the cursor shape reset problem.
The main downside to this approach is that PS0 will continue to contain
this escape sequence; it won't be cleared/reset in __ghostty_preexec for
the reasons described in #7808. This feels like an acceptable outcome
because there's no harm in the modified PS0 existing for the life of the
bash session (rather than it being modified and then restored for each
command cycle), and it's consistent with how some other terminals' bash
integration works (e.g. kitty).
The nerd font patcher uses `ypadding` as a single subtraction from the
cell height, which means that half of it should go to the top padding
and the other half to the bottom, but we were setting it for both the
top and bottom! This was making the heavy brackets way too small lol
(0.4 of the cell height instead of 0.7)
#7834 introduced the *opposite* problem to what it fixed haha. This
fixes it for real!
The nerd font patcher uses `ypadding` as a single subtraction from the
cell height, which means that half of it should go to the top padding
and the other half to the bottom, this was making the heavy brackets way
too small lol (0.4 of the cell height instead of 0.7)
This better harmonizes fallback fonts with the primary font by matching
the heights of lowercase letters. This should be a big improvement for
users who use mixed scripts and so rely heavily on fallback fonts.
In #7808, we stopped using PS0 to reset the cursor shape because
restoring PS0 in __ghostty_preexec was causing issues (#7802).
The alternate approach of printing the cursor reset escape sequence
directly from __ghostty_preexec caused a new issue: the input cursor
would persist longer than intended, such as when a suspended vim process
was restored to the foreground.
This change takes a different approach. We now conditionally add the
cursor shape escape sequences to PS0 (and PS1, for consistency) when
they don't already appear. The fixes the cursor shape reset problem.
The main downside to this approach is that PS0 will continue to contain
this escape sequence; it won't be cleared/reset in __ghostty_preexec for
the reasons described in #7808. This feels like an acceptable outcome
because there's no harm in the modified PS0 existing for the life of the
bash session (rather than it being modified and then restored for each
command cycle), and it's consistent with how some other terminals' bash
integration works (e.g. kitty).
This generally adjusts the bearings of any glyph whose original advance
was narrower than the cell, which helps a lot with proportional fallback
glyphs so they aren't just left-aligned. This only applies to situations
where the glyph was originally narrower than the cell, so that we don't
mess up ligatures, and this centers the old advance width in the new one
rather than adjusting proportionally, because otherwise we can mess up
glyphs that are meant to align with others when placed vertically.
Partial implementation of #5256
This implements the core changes necessary to open urls using an apprt
action rather than doing it directly from the core.
Implements the open_url action in the GTK apprt.
Note that this should not be merged until a macOS-savvy developer can
add an implementation of the open_url action for the macOS apprt.
Partial implementation of #5256
This implements the core changes necessary to open urls using an apprt
action rather than doing it directly from the core.
Implements the open_url action in the GTK and GLFW apprts.
Note that this should not be merged until a macOS-savvy developer can add
an implementation of the open_url action for the macOS apprt.
We can reintroduce `advance` if we ever want to do proportional string
drawing, but we don't use it anywhere right now. And we also don't need
`sprite` anymore since that was just there to disable constraints for
sprites back when we did them on the GPU.
As discussed in https://github.com/ghostty-org/ghostty/discussions/3134
To allow for the option to render bold text in a different colour for
better visibility as an extension of `bold-is-bright`.
This is a feature that is available in other terminals.
Fixes#7820, and while fixing that I noticed that we need to respect the
cell width constraints since certain glyphs should not expand to 2
cells; before fixing that the heavy bracket would align differently
depending on if it had whitespace after it, which was obviously wrong
and looked terrible.
Fixes#7673
This adds `Ctrl+Alt+T` as a KDE shortcut to the desktop file. If Konsole
is installed (or any other prorgam that has the same shortcut) the user
will need to go into the KDE system settings and manually reassign the
`Ctrl+Alt+T` shortcut to Ghostty.
If Ghostty is the only terminal installed that claims that shortcut KDE
_should_ automatically enable the shortcut (but YMMV).
Non-KDE systems will ignore this setting and if the user desires a
global shortcut to open a Ghostty window it will need to be accomplished
in other ways.
Fixes#7673
This adds `Ctrl+Alt+T` as a KDE shortcut to the desktop file. If Konsole
is installed (or any other prorgam that has the same shortcut) the user
will need to go into the KDE system settings and manually reassign the
`Ctrl+Alt+T` shortcut to Ghostty.
If Ghostty is the only terminal installed that claims that shortcut KDE
_should_ automatically enable the shortcut (but YMMV).
Non-KDE systems will ignore this setting and if the user desires a
global shortcut to open a Ghostty window it will need to be accomplished
in other ways.
This mostly applies to powerline glyphs, but is also relevant for heavy
bracket characters, which need to always be 1 wide otherwise they look
silly because they misalign depending on if there's a space after them
or not.
Previously `ypadding` was effectively ignored, since it's mutually
exclusive with `overlap`. This had a noticeable effect on the heavy
bracket characters U+276C...U+2771, which were much taller than they
should have been.
I also fixed the vertical overlap limit, since negative `overlap` values
are used in the nerd font attributes to create padding on all sides of
the cell, so we don't want to limit the magnitude of the overlap for
vertical padding, we only want to limit it if the value is positive.
That change fixed the vertical padding for a handful of ranges, which
should give more consistent results.
This reverts commit 2fca0477bc.
The idea of using stdin and stdout was the integrate it in to the build
script, but since we don't want to do that because it contains an eval,
it just makes it more annoying to use.
The termio directory contains the implementation of many terminal
features that those in the terminal reviewer group may want to be
notified about.
---
An example of a terminal related PR that only changed files in the
`termio` directory (and thus didn't assign @ghostty-org/terminal):
https://github.com/ghostty-org/ghostty/pull/7725
I included the entire `termio` directory. If others in the
@ghostty-org/terminal group think this is too broad we can narrow it
down.
Follow-up to #7809, a handful of small fixes/improvements, explained
individually in each commit message.
> [!NOTE]
> Similar to the inverted "monochrome" ft flag fix from #7809, it was
pointed out that "force-autohint" was also inverted, so I did invert
that too, this has the same result of no impact on users who haven't
explicitly set it, but a breaking behavior change for users who have set
it. *These changes definitely need to be pointed out in the 1.2 release
notes!*
This makes it so `zig build run` can take arguments such as
`--config-default-files=false` or any other configuration. Previously,
it only accepted commands such as `+version`.
Incidentally, this also makes it so that the app in general can now take
configuration arguments via the CLI if it is launched as a new instance
via `open`. For example:
open -n Ghostty.app --args --config-default-files=false
This previously didn't work. This is kind of cool.
To make this work, the libghostty C API was modified so that
initialization requires the CLI args, and there is a new C API to try to
execute an action if it was set.
Mainly added type annotations, cleaned up weird AI slop and used more
idiomatic stuff.
This requires Python 3.12 to run (I can downgrade it if need be).
This makes it so `zig build run` can take arguments such as
`--config-default-files=false` or any other configuration. Previously,
it only accepted commands such as `+version`.
Incidentally, this also makes it so that the app in general can now take
configuration arguments via the CLI if it is launched as a new instance
via `open`. For example:
open -n Ghostty.app --args --config-default-files=false
This previously didn't work. This is kind of cool.
To make this work, the libghostty C API was modified so that
initialization requires the CLI args, and there is a new C API to try to
execute an action if it was set.
We no longer need a margin in the atlas because we always sample with
nearest neighbor and our glyphs are always pixel perfect, no worry about
interpolation between adjacent glyphs anymore!
This sets up for a couple improvments (see TODO comments) and also sets
the glyph atlas textures to nearest neighbor sampling since we can do
that now that we never scale glyphs.
The behavior of this flag was the opposite of its description in the
docs- luckily, at the same time, the default (true) was the opposite
from what the default actually is in freetype, so users who haven't
explicitly set this flag won't see a behavior difference from this.
Previously, many glyphs were having their top and right row/column of
pixels clipped off due to not accounting for the slight bearing in the
width and height calculation here.
This is a big'un.
- **Glyph constraint logic is now done fully on the CPU** at the
rasterization stage, so it only needs to be done once per glyph instead
of every frame. This also lets us eliminate padding between glyphs on
the atlas because we're doing nearest-neighbor sampling instead of
interpolating-- which ever so slightly increases our packing efficiency.
- **Special constraints for nerd font glyphs** are applied based roughly
on the constraints they use in their patcher. It's a simplification of
what they do, the largest difference being that they scale groups of
glyphs based on a shared bounding box so that they maintain relative
size to one another, but that would require loading all glyphs on the
group and I'd want to do that on font load TBH and at that point I'd
basically be re-implementing the nerd fonts patcher in Zig to patch
fonts at load time which is way beyond the scope I want to have. (Fixes
#7069)
- These constraints allow for **perfectly sized and centered emojis**,
this is very nice.
- **Changed the default embedded fonts** from 4 copies (regular, italic,
bold, bold italic) of a patched (and outdated) JetBrains Mono to a
single JetBrains Mono variable font and a single Nerd Fonts Symbols Only
font. This cuts the weight of those down from 9MB to 3MB!
- **FreeType's `renderGlyph` is significantly reworked**, and the new
code is, IMO, much cleaner- although there are probably some edge case
behavior differences I've introduced.
> [!NOTE]
> One breaking change I definitely introduced is changing the
`monochrome` freetype load flag config from its previous completely
backwards meaning to instead the correct one (I also changed the
default, so this won't affect any user who hasn't touched it, but users
who set the `monochrome` flag will find their fonts quite crispy after
this change because they will have no anti-aliasing anymore)
### Future work
Following this change I want to get to work on automatic font size
matching (a la CSS
[`font-size-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust)).
I set the stage for that quite some time ago so it shouldn't be too much
work and it will be a big benefit for users who regularly use multiple
writing systems and so have multiple fonts for them that aren't
necessarily size-compatible.
This PR does two things.
1. Build system improvements to make developing on macOS more enjoyable
2. Delete the GLFW apprt
## Build System Improvements (macOS)
On macOS, there are a few major improvements:
* `zig build` now produces a full macOS app bundle and copies it into
`zig-out`
* `zig build run` now builds the macOS app and runs it, streaming logs
directly into the terminal
* `-Demit-macos-app` can control whether app bundle is created
* `-Dxcframework-target` can be set to one of `native` or `universal` to
control whether the xcframework uses only your target machines arch or
creates a universal one with macOS and iOS. This defaults to `native`
for the `run` command and `universal` for all others.
* The general flow of the `build.zig` file was improved to be more
declarative
## Nuke GLFW
> This deletes the GLFW apprt from the Ghostty codebase.
>
> The GLFW apprt was the original apprt used by Ghostty (well, before
> Ghostty even had the concept of an "apprt" -- it was all just a single
> application then). It let me iterate on the core terminal features,
> rendering, etc. without bothering about the UI. It was a good way to
get
> started. But it has long since outlived its usefulness.
>
> We've had a stable GTK apprt for Linux (and Windows via WSL) and a
> native macOS app via libghostty for awhile now. The GLFW apprt only
> remained within the tree for a few reasons:
>
> 1. Primarily, it provided a faster feedback loop on macOS because
> building the macOS app historically required us to hop out of the
> zig build system and into Xcode, which is slow and cumbersome.
>
> 2. It was a convenient way to narrow whether a bug was in the
> core Ghostty codebase or in the apprt itself. If a bug was in both
> the glfw and macOS app then it was likely in the core.
>
> 3. It provided us a way on macOS to test OpenGL.
>
> All of these reasons are no longer valid. Respectively:
>
> 1. Our Zig build scripts now execute the `xcodebuild` CLI directly and
> can open the resulting app, stream logs, etc. This is the same
> experience we have on Linux. (Xcode has always been a dependency of
> building on macOS in general, so this is not cumbersome.)
>
> 2. We have a healthy group of maintainers, many of which have access
> to both macOS and Linux, so we can quickly narrow down bugs
> regardless of the apprt.
>
> 3. Our OpenGL renderer hasn't been compatible with macOS for some time
> now, so this is no longer a useful feature.
>
> At this point, the GLFW apprt is just a burden. It adds complexity
> across the board, and some people try to run Ghostty with it in the
real
> world and get confused when it doesn't work (it's always been lacking
in
> features and buggy compared to the other apprts).
>
> So, it's time to say goodbye. Its bittersweet because it is a big part
> of Ghostty's history, but we've grown up now and it's time to move on.
> Thank you, goodbye.
>
> (NOTE: If you are a user of the GLFW apprt, then please fork the
project
> prior to this commit or start a new project based on it. We've warned
> against using it for a very, very long time now.)
- Replace dual-loop SSH config parsing with efficient single-pass case
statement
- Remove overly cautious timeout logic from cache checks for simplicity
- Add base64 availability check with xterm-256color fallback when
missing
- Include hostname in terminfo setup messages for better UX
- Maintain SendEnv/SetEnv dual approach for maximum OpenSSH
compatibility (relying on SetEnv alone seems to drop some vars during my
tests, despite them being explicitly included in AcceptEnv on the remote
host)
Our use of PS0 (which bash runs before command execution) was causing
raw command sequences to be printed between multiple commands in a
sequence.
$ alias garbage='echo start
> echo end'
$ garbage
start
�\���dend
I wasn't able to definitely track down all of the reasons for why this
only happens in the command sequence case, but I suspect it's related to
the way that __ghostty_preexec runs from within the bash DEBUG trap (by
way of bash-preexec).
This problem occurs when PS0 is set to _any_ string (even "") inside of
__ghostty_preexec, which also rules out most/any Ghostty-specific code.
PS1 and PS2 appear to be safe to (re)set in this context.
Fortunately, we can avoid using PS0 entirely by instead printing the
cursor reset escape sequence directly from __ghostty_precmd because it
also runs just before command execution.
Fixes#7802
Our use of PS0 (which bash runs before command execution) was causing
raw command sequences to be printed between multiple commands in a
sequence.
$ alias garbage='echo start
> echo end'
$ garbage
start
�\���dend
I wasn't able to definitely track down all of the reasons for why this
only happens in the command sequence case, but I suspect it's related to
the way that __ghostty_preexec runs from within the bash DEBUG trap (by
way of bash-preexec).
This problem occurs when PS0 is set to _any_ string (even "") inside of
__ghostty_preexec, which also rules out most/any Ghostty-specific code.
PS1 and PS2 appear to be safe to (re)set in this context.
Fortunately, we can avoid using PS0 entirely by instead printing the
cursor reset escape sequence directly from __ghostty_precmd because it
also runs just before command execution.
Now that it's done at the rasterization stage, we don't need to handle
it on the GPU. This also means that we can switch to nearest neighbor
interpolation in the Metal shader since we're guaranteed to be pixel
perfect. Accidentally, we were already nearest neighbor in the OpenGL
shaders because I used the Rectangle texture mode in the big renderer
rework, which doesn't support interpolation- anyway, that's no longer
problematic since we won't be scaling glyphs on the GPU anymore.
This is in preparation to move constraint off the GPU to simplify our
shaders, instead we only need to constrain once at raster time and never
again.
This also significantly reworks the freetype renderGlyph function to be
generally much cleaner and more straightforward.
This commit doesn't actually apply the constraints to anything yet, that
will be in following commits.
This deletes the GLFW apprt from the Ghostty codebase.
The GLFW apprt was the original apprt used by Ghostty (well, before
Ghostty even had the concept of an "apprt" -- it was all just a single
application then). It let me iterate on the core terminal features,
rendering, etc. without bothering about the UI. It was a good way to get
started. But it has long since outlived its usefulness.
We've had a stable GTK apprt for Linux (and Windows via WSL) and a
native macOS app via libghostty for awhile now. The GLFW apprt only
remained within the tree for a few reasons:
1. Primarily, it provided a faster feedback loop on macOS because
building the macOS app historically required us to hop out of the
zig build system and into Xcode, which is slow and cumbersome.
2. It was a convenient way to narrow whether a bug was in the
core Ghostty codebase or in the apprt itself. If a bug was in both
the glfw and macOS app then it was likely in the core.
3. It provided us a way on macOS to test OpenGL.
All of these reasons are no longer valid. Respectively:
1. Our Zig build scripts now execute the `xcodebuild` CLI directly and
can open the resulting app, stream logs, etc. This is the same
experience we have on Linux. (Xcode has always been a dependency of
building on macOS in general, so this is not cumbersome.)
2. We have a healthy group of maintainers, many of which have access
to both macOS and Linux, so we can quickly narrow down bugs
regardless of the apprt.
3. Our OpenGL renderer hasn't been compatible with macOS for some time
now, so this is no longer a useful feature.
At this point, the GLFW apprt is just a burden. It adds complexity
across the board, and some people try to run Ghostty with it in the real
world and get confused when it doesn't work (it's always been lacking in
features and buggy compared to the other apprts).
So, it's time to say goodbye. Its bittersweet because it is a big part
of Ghostty's history, but we've grown up now and it's time to move on.
Thank you, goodbye.
(NOTE: If you are a user of the GLFW apprt, then please fork the project
prior to this commit or start a new project based on it. We've warned
against using it for a very, very long time now.)
`zig build run` on macOS now builds the app bundle via the `xcodebuild`
CLI and runs it. The experience for running the app is now very similar
to Linux or the prior GLFW build, where the app runs, blocks the zig
command, and logs to the terminal.
`xcodebuild` has its own build cache system that we can't really hook
into so it runs on every `zig build run` command, but it does cache and
I find its actually relatively fast so I think this is a good
replacement for the glfw-based system.
Fixes#7794
This commit also resets some terminal state to give us a better chance
of getting an encoded key, such as ensuring keyboard input is enabled
and disabling any Kitty protocols. This shouldn't ever be set but just
in case!
This fixes an Apple Shortcuts crash for macOS 15 and earlier.
Unfortunately it looks like we can't guard these with `@available`. I'm
going to report an Apple Feedback about this but for now this gets
shortcuts working on macOS 15 and earlier.
Fixes#7794
This commit also resets some terminal state to give us a better chance
of getting an encoded key, such as ensuring keyboard input is enabled
and disabling any Kitty protocols. This shouldn't ever be set but just
in case!
This fixes an Apple Shortcuts crash for macOS 15 and earlier.
Unfortunately it looks like we can't guard these with `@available`. I'm
going to report an Apple Feedback about this but for now this gets
shortcuts working on macOS 15 and earlier.
Fixes#7792
Our error handling for `exec` failing within the forked process never
actually worked! It triggered all sorts of issues. We didn't catch this
before because it used to be exceptionally hard to fail an exec because
we used to wrap ALL commands in a `/bin/sh -c`.
However, we now support direction execution, most notably when you do
`ghostty -e <command>` but also via the `direct:` prefix on configured
commands.
This fixes up our exec failure handling by printing a useful error
message and avoiding any errdefers in the child which was causing the
double-close.
Fixes#7792
Our error handling for `exec` failing within the forked process never
actually worked! It triggered all sorts of issues. We didn't catch this
before because it used to be exceptionally hard to fail an exec because
we used to wrap ALL commands in a `/bin/sh -c`.
However, we now support direction execution, most notably when you do
`ghostty -e <command>` but also via the `direct:` prefix on configured
commands.
This fixes up our exec failure handling by printing a useful error
message and avoiding any errdefers in the child which was causing the
double-close.
- Eliminates standalone bash dependency
- Consolidates `+list-ssh-cache` and `+clear-ssh-cache` actions into
single `+ssh-cache` action with args
- Structured cache format with timestamps and expiration support
- Memory-safe entry handling with proper file locking
- Comprehensive hostname validation (IPv4/IPv6/domains)
- Atomic updates via temp file + rename
- Updated shell integrations for improved cross-platform support and
reliability
- Cache operations are now unit-testable
- Minor tweak to Config.zig to show the new action.
- Rolled back README.md to remove reference to the now non-existent
'shared' subdir and bash-based cache script.
Rather than using binaries statically in our source tree; this makes
them easier to update. This also makes it so that they are separated
from each other rather than using a patched JB mono as our fallback.
Fixes#7786
Fixes regression from #7683
This is a band-aid fix. The issue is that performable keybinds don't
show up in the reverse mapping that GUI toolkits use to find their key
equivalents. The full explanation of why is already in Binding.zig.
For macOS, we have a way to validate menu items before they're triggered
so we ideally do want a way to get reverse mappings even with
performable keybinds. But I think this wants to be optional and that's
all a bigger change. For now, this is a simple fix that will work.
Fixes#7786
Fixes regression from #7683
This is a band-aid fix. The issue is that performable keybinds don't
show up in the reverse mapping that GUI toolkits use to find their key
equivalents. The full explanation of why is already in Binding.zig.
For macOS, we have a way to validate menu items before they're triggered
so we ideally do want a way to get reverse mappings even with
performable keybinds. But I think this wants to be optional and that's
all a bigger change. For now, this is a simple fix that will work.
Discussed in #7714, fix confirmed by user as working.
Okay, so, what the hell? This implies that on this user's system,
something that *should* be an integer multiple of the cell width, when
divided by the cell width, is giving some epsilon less than the proper
result. I can only guess that this is driver or hardware weirdness,
possibly the fragcoord is being converted from a low precision float in
NDC to the fragment-space coordinate so we're not actually getting an
integer multiple, and the epsilon less is actually before the division?
Regardless, switching it back to use halves for pixel coordinates fixes
this and shouldn't break the math at all, since `floor((n * k + 0.5)/k)`
should always yield `n` just like without the `+ 0.5`.
Implemented cell color for Metal
Removed use of selection-invert-fg-bg
Mirrored feature to OpenGL
Added tests for SelectionColor
Fixed selection on inverted cell behavior
Implemented cell colors for cursor-text
Implemented cell colors for cursor-color, removed uses of cursor-invert-fg-bg during rendering
Updated docs for dynamically colored options
Updated docstrings, cleaned up awkward formatting, and moved style computation to avoid unnecssary invocations
Bump version in docstrings
By using integers for the fragcoords I may have stepped on an edge case
which causes cell background positions to be shifted by 1 px under some
circumstances. I couldn't reproduce that issue in a VM, so I'm making
this commit for the user who was having the problem to test it.
This PR contains fixes for 4 different memory leaks that affected
Ghostty on macOS.
1. (whenever a font is loaded) CoreText font features dict list wasn't
properly released. Fixed by releasing.
2. (whenever a font is searched for) CoreText discovery iterator
descriptors weren't properly released. Fixed by releasing.
3. (during resize) Metal texture descriptors were not properly released.
Fixed by releasing.
4. (every frame) Objective-C runtime blocks for buffer completion
handler and IOSurfaceLayer set surface were not properly deallocated due
to issues with the internal implementation in `zig-objc`. Fixed in
`zig-objc`, dependency hash updated with fix.
A handful of small apparent leaks remain but their cause is not clear
and they're all static (not increasing over time, seemingly).
### Xcode memory graph "leaks" comparison
|Before (main)|After (this PR)|
|-|-|
|<img width="445" alt="image"
src="https://github.com/user-attachments/assets/d1c89918-8ab2-4201-bf1e-9b3a519a85a8"
/>|<img width="445" alt="image"
src="https://github.com/user-attachments/assets/88c60807-756e-48d8-9918-2a52d6556035"/>|
<sup>Images taken after launching Ghostty, creating 4 tabs, and rapidly
switching between them to force render many frames.</sup>
---
Hopefully this fixes the occasional OOM issues some users have reported.
This update also fixes a memory leak that was caused by blocks not being
deallocated and just collecting every single frame, slowly accumulating
memory until OOM.
This will allow users to enable Ghostty startup on login. Users will
need to explicitly enable startup on login via this command:
```sh
systemctl enable --user com.mitchellh.ghostty.service
```
This is done at the apprt-level for a couple reasons.
(1) For libghostty, we don't have a way to know what the embedding
application is doing, so its risky to create signal handlers that
might overwrite the application's signal handlers.
(2) It's extremely messy to deal with signals and multi-threading.
Apprts have framework access that handles this for us.
For GTK, we use g_unix_signal_add.
For macOS, we use `DispatchSource.makeSignalSource`. This is an awkward
API but made for this purpose.
This is done at the apprt-level for a couple reasons.
(1) For libghostty, we don't have a way to know what the embedding
application is doing, so its risky to create signal handlers that
might overwrite the application's signal handlers.
(2) It's extremely messy to deal with signals and multi-threading.
Apprts have framework access that handles this for us.
For GTK, we use g_unix_signal_add.
For macOS, we use `DispatchSource.makeSignalSource`. This is an awkward
API but made for this purpose.
Introduces `fill`, which fills between two `Fraction`s, use this instead
of `yHalfs` and friends wherever they're used, which also means we can
remove `rect`.
This commit does change alignment of the vertical/horizontal eighths in
certain cell sizes, but the change is for the better IMO. Also changes
the center-point alignment of smooth mosaics for odd cell widths, but
the change is no more than half a pixel at worst and is probably an
improvement ultimately.
This will allow users to enable Ghostty startup on login. Users will
need to explicitly enable startup on login via this command:
```sh
systemctl enable --user com.mitchellh.ghostty.service
```
I've included a compatibility test here to make sure that the numbers
from this are in line with the numbers produced by xHalfs, yThirds, etc.
After this commit I'll introduce a helper function that fills based on a
span specified with this enum to replace any uses of xHalfs and friends.
Once I do that I'll remove them and the compatibility test, this should
be a much cleaner interface for this and make it easier to consistently
align block elements with each other.
Use `xHalfs` and `yHalfs` so that the dimensions of each quadrant are
appropriately aligned with block elements like the one half block, which
could be 1px taller than the bottom quadrants before this change.
This is in line with what we do for sextants, the fact that on odd-sized
cells there's a 1px overlap is considered acceptable there so I assume
it's acceptable here too.
- Large rework of how we draw sprite font glyphs, explained in the
commit message and comments.
- Adds separated block sextants from symbols for legacy computing
supplement
- Adds explicit underline cursor instead of using underline glyph for
it, resolves#7651, supersedes #7685
Currently we support these glyphs with the sprite font:
```
─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿▀▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▐░▒▓▔▕▖▗▘▙▚▛▜▝▞▟◢◣◤◥◸◹◺◿⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿🬀🬁🬂🬃🬄🬅🬆🬇🬈🬉🬊🬋🬌🬍🬎🬏🬐🬑🬒🬓🬔🬕🬖🬗🬘🬙🬚🬛🬜🬝🬞🬟🬠🬡🬢🬣🬤🬥🬦🬧🬨🬩🬪🬫🬬🬭🬮🬯🬰🬱🬲🬳🬴🬵🬶🬷🬸🬹🬺🬻🬼🬽🬾🬿🭀🭁🭂🭃🭄🭅🭆🭇🭈🭉🭊🭋🭌🭍🭎🭏🭐🭑🭒🭓🭔🭕🭖🭗🭘🭙🭚🭛🭜🭝🭞🭟🭠🭡🭢🭣🭤🭥🭦🭧🭨🭩🭪🭫🭬🭭🭮🭯🭰🭱🭲🭳🭴🭵🭶🭷🭸🭹🭺🭻🭼🭽🭾🭿🮀🮁🮂🮃🮄🮅🮆🮇🮈🮉🮊🮋🮌🮍🮎🮏🮐🮑🮒🮔🮕🮖🮗🮘🮙🮚🮛🮜🮝🮞🮟🮠🮡🮢🮣🮤🮥🮦🮧🮨🮩🮪🮫🮬🮭🮮🮯🮽🮾🮿
```
Screenshot:
<img width="1121" alt="image"
src="https://github.com/user-attachments/assets/d0979bf0-f585-415b-bbc7-b7b399adce25"
/>
I plan on doing a follow-up PR with a lot of new supported glyphs now
that it's so much easier to add them.
This pull request adds Korean translations.
I do want to add that while I do speak Korean _I am not a native
speaker_. I've done my best to provide translations as natural as
possible, but I'd love it if a native speaker could improve it before
merging.
Fixes#7625 (which apparently didn't catch anyone's attention, but I'd
be very surprised if no one were able to reproduce it if they tried;
also, re discord chat)
We need to use this version of z2d so that we can get reproducible PNG
exports in CI for testing, since previously the PNG export was affected
by the CPU arch / features because it depended on vector width.
This was creating problems with the branch drawing glyphs at some sizes.
In the future the whole "foreground modes" thing needs to be reworked,
so this is just a stopgap until that gets turned in to something nicer.
This is a fairly large rework of how we handle the sprite font drawing.
Drawing routines are now context-less, provided only a canvas and some
metrics. There is now a separate file per unicode block / PUA area.
Sprites are now drawn on canvases with an extra quarter-cell of padding
on each edge, and automatically cropped when sent to the atlas, this
allows sprites to extend past cell boundaries which makes it possible to
have, for example, diagonal box drawing characters that connect across
cell diagonals instead of being pinched in.
Most of the sprites the code is just directly ported from the old code,
but I've rewritten a handful. Moving forward, I'd like to rewrite more
of these since the way they're currently written isn't ideal.
This rework, in addition to improving the packing efficiency of sprites
on the atlas, and allowing for out-of-cell drawing, will make it a lot
easier to add new sprites in the future, since all it takes now is to
add a single function and an import (if it's a new file).
I reworked the regression/change testing to be more robust as well, it
now covers all sprite glyphs (except non-codepoint ones) and does so at
4 different sizes. Addition/removal of glyphs will no longer create diff
noise in the generated diff image, since the position in the image of
each glyph is now fixed.
Adds the config option as described in #4515.
Plumbed it into the `RunIterator` and updated all the tests that rely on
that, adding additional cases to the cursor boundary tests where I found
them.
Closes#4515
This is an extension agreed upon by modern terminals to indicate that
they support copying to the clipboard with XTerm's OSC 52 sequence. It
is only reported when writing to the clipboard is actually allowed.
Ref: #7590
This test-only function wraps testWriteString with semantic prompt
marking. This replaces the manual, row-based semantic_prompt field
manipulation we were doing in all of our prompt-related test setups.
This function's heuristics are a little complex because it wraps
testWriteString as a "black box"; we don't benefit from that function's
own line-based logic to know which rows need to be updated with the
semantic prompt flag. We need to infer them externally instead.
I considered adding an options argument to testWriteString that would
allow passing e.g. a semantic_prompt prompt. Given that it's called from
200+ places, that would involve a lot of unrelated changes, but it
remains an "option" (ha!) if there's value there for other cases.
I also have plans that move us from row-based to cell-based semantic
tracking, where the current semantic type is tracked by the cursor. In
that implementation, testWriteString can update the written cells
directly, and testWriteSemanticString just helps manage the cursor's
state. Introducing testWriteSemanticString here and now therefore helps
bridge us to that world while maintaining test consistency.
Fixes#7724
Background at the end of the commit message. The fix in libxev is
described in the PR and commit we pin to here, but basically we swap
read for poll for eventfd/timerfd. libxev commit:
75a10d0fb3
From Jens Axboe on X:
> This will fix it: https://pastebin.com/n7JSZWpW which makes me
suspicious
> that it's an S_IFREG check somewhere else, as anon inodes are now
listed as
> regular files. Has potentially pretty broad implications...
> I think I can already answer why that breaks things - io_uring checks
if
> this is a regular file, and if it is, it doesn't do short reads. Short
> reads on regular files (or a bdev) will cause application issues, as
> basically nobody expects them.
> Now we have what acts like a char dev, but where io_uring will retry
IO
> because the application asked for more data than what was delivered.
This
> will cause the weird slowdowns as data isn't delivered as soon as it's
> available.
## Backporting to 1.1.3
It's a bit risky, but we can back port the libxev update to 1.1.3 (and
release a 1.1.4) since we don't plan on a 1.2 release for a couple
months. Realistically, we've been running the latest libxev on tip
(before this commit) for awhile and there haven't been issues so I think
it's safe enough.
I think we should let this merge settle for a few days at least before
we do that back port though.
This PR introduces support for Irish (Gaeilge), the first official
language of Ireland and an EU working language.
The translation file was initialized using the standard gettext tooling:
` msginit -i po/com.mitchellh.ghostty.pot -l $LANG -o "po/$LANG.po"`
The locale code `ga_IE` follows ISO standards for Gaeilge as spoken in
Ireland.
I'm happy to volunteer as the ongoing maintainer of the Irish
translation and will keep it up to date as Ghostty evolves.
Go raibh maith agat!
This test-only function wraps testWriteString with semantic prompt
marking. This replaces the manual, row-based semantic_prompt field
manipulation we were doing in all of our prompt-related test setups.
This function's heuristics are a little complex because it wraps
testWriteString as a "black box"; we don't benefit from that function's
own line-based logic to know which rows need to be updated with the
semantic prompt flag. We need to infer them externally instead.
I considered adding an options argument to testWriteString that would
allow passing e.g. a semantic_prompt prompt. Given that it's called from
200+ places, that would involve a lot of unrelated changes, but it
remains an "option" (ha!) if there's value there for other cases.
I also have plans that move us from row-based to cell-based semantic
tracking, where the current semantic type is tracked by the cursor. In
that implementation, testWriteString can update the written cells
directly, and testWriteSemanticString just helps manage the cursor's
state. Introducing testWriteSemanticString here and now therefore helps
bridge us to that world while maintaining test consistency.
Fixes#7724
Background at the end of the commit message. The fix in libxev is
described in the PR and commit we pin to here, but basically we swap
read for poll for eventfd/timerfd.
From Jens Axboe on X:
> This will fix it: https://pastebin.com/n7JSZWpW which makes me suspicious
> that it's an S_IFREG check somewhere else, as anon inodes are now listed as
> regular files. Has potentially pretty broad implications...
> I think I can already answer why that breaks things - io_uring checks if
> this is a regular file, and if it is, it doesn't do short reads. Short
> reads on regular files (or a bdev) will cause application issues, as
> basically nobody expects them.
> Now we have what acts like a char dev, but where io_uring will retry IO
> because the application asked for more data than what was delivered. This
> will cause the weird slowdowns as data isn't delivered as soon as it's
> available.
This is an extension agreed upon by modern terminals to indicate that
they support copying to the clipboard with XTerm's OSC 52 sequence. It
is only reported when writing to the clipboard is actually allowed.
This introduces an action for copying the path of a written
screen/selection file into the clipboard.
Pasting the path into the terminal doesn't work well if you have a
program still running and opening the file outside the terminal (on
macOS in TextEdit by default) isn't always a great experience.
Allowing to copy the file path into the clipboard seems like a minor and
hopefully uncontroversial addition. 😅
Fix regression from d44a6cde2c where we
halted parsing on deprecated fields, which was not the intended
behavior.
This commit fixes that and adds a test to verify it.
Fix regression from d44a6cde2c where
we halted parsing on deprecated fields, which was not the intended
behavior.
This commit fixes that and adds a test to verify it.
Fixes#7706
We previously had a very specific backwards compatibility handler for
handling renamed fields. We always knew that wouldn't scale but I wanted
to wait for a real case. Well, #7706 is a real case, so here we are.
This commit makes our backwards compatibility handler more general
purpose, and makes a special-case handler for renamed fields built on
top of this same general purpose system. The new system lets us do a lot
more with regards to backwards compatibility.
To start, this addresses #7706 by allowing us to handle a removed single
enum value of a still-existing field.
In the future, I think this may continue to get _more_ general purpose
by moving the handlers from functions to structs so we can have more
metadata like descriptions and so on that we may use to generate docs or
other help strings.
Fixes#7706
We previously had a very specific backwards compatibility handler for
handling renamed fields. We always knew that wouldn't scale but I wanted
to wait for a real case. Well, #7706 is a real case, so here we are.
This commit makes our backwards compatibility handler more general
purpose, and makes a special-case handler for renamed fields built on
top of this same general purpose system. The new system lets us do a lot
more with regards to backwards compatibility.
To start, this addresses #7706 by allowing us to handle a removed single
enum value of a still-existing field.
Supersedes #7154
In gtk4-layer-shell versions < 1.0.4, the app could crash upon opening a
quick terminal window on certain compositors that implement the
`xdg_wm_dialog_v1` protocol. The exact reason is a bit complicated, but
is nicely summarized in the upstream issue (wmww/gtk4-layer-shell#50).
The circumstances that could cause this crash to occur should gradually
diminish as distros update to newer gtk4-layer-shell versions, but this
is known to crash on Fedora 41 and Hyprland, which could be a sizable
chunk of our userbase given that this would also occur on GNOME/Mutter
and KDE/KWin. The diff should be minimal enough that this can be removed
or reverted once this band-aid fix is no longer necessary.
Supersedes #7154
In gtk4-layer-shell versions < 1.0.4, the app could crash upon opening
a quick terminal window on certain compositors that implement the
`xdg_wm_dialog_v1` protocol. The exact reason is a bit complicated,
but is nicely summarized in the upstream issue (wmww/gtk4-layer-shell#50).
The circumstances that could cause this crash to occur should gradually
diminish as distros update to newer gtk4-layer-shell versions, but this
is known to crash on Fedora 41 and Hyprland, which could be a sizable
chunk of our userbase given that this would also occur on GNOME/Mutter
and KDE/KWin. The diff should be minimal enough that this can be removed
or reverted once this band-aid fix is no longer necessary.
This fixes#7702 by no longer passing the arena allocator to a task that
outlives its caller.
As far as I can tell, a reference to `config_path` may live on in the
`argv` field of the ChildProcess after the arena is cleared (unless
`argv` itself becomes a dangling pointer because the literal it's
referencing goes out of scope? Struggling to wrap my head around some of
the finer points of Zig array/slice/literal semantics). However, I
suppose it's safe to assume that `argv` is never referenced after
`exe.spawn()` has returned, in which case there's no issue. If this were
a problem, it would apply equally to all uses of `internal_os.open`, not
just the open config code path.
Closes#6632
When compiling the dynamic lib and linking, the rpath resolves to the
compile cache location instead of the install location for the lib. This
resulted in loading the dylib failing when the compile cache was removed
or the install location is changed.
Based on https://github.com/ziglang/zig/issues/5827 , we specify the
rpath to search relative to the executable.
Previous state:
- dynamic lib was not installed to the output directory -> now appears
in `$OUT/lib/libgtk4-layer-shell.so`
- rpath only included the compile cache location, not the install
location.
Before:
```sh
$ patchelf --print-rpath zig-out/bin/ghostty
/home/anthony/dev/ghostty-pure/.zig-cache/o/0254fb4753185c5429180337a720248d
$ ldd zig-out/bin/ghostty
...
libgtk4-layer-shell.so => /home/anthony/dev/ghostty-pure/.zig-cache/o/0254fb4753185c5429180337a720248d/libgtk4-layer-shell.so (0x00007f7975468000)
...
$ ldd zig-out/bin/ghostty
...
libgtk4-layer-shell.so => not found
...
```
After:
```sh
$ zig build
$ patchelf --print-rpath zig-out/bin/ghostty
/home/anthony/dev/ghostty/.zig-cache/o/f45360ddde653cb3bc70966c326dd96d:$ORIGIN/../lib/
$ ldd zig-out/bin/ghostty
...
libgtk4-layer-shell.so => /home/anthony/dev/ghostty/.zig-cache/o/f45360ddde653cb3bc70966c326dd96d/libgtk4-layer-shell.so (0x00007fbf81ad8000)
...
$ rm -r .zig-cache/
$ ldd zig-out/bin/ghostty
...
libgtk4-layer-shell.so => /home/anthony/dev/ghostty/zig-out/bin/../lib/libgtk4-layer-shell.so (0x00007f60dc087000)
...
```
We default system-integration to true as this is a shared library that
must be installed on a library path and it is recommended to use the
system package.
If the system does not package gtk4-layer-shell then doing `zig build
-fno-sys` will now correctly build and install the shared library under
a lib/ subdirectory of the output prefix.
The output prefix should be a default library path (`/lib`, `/usr/lib`,
or a lib64 variant) otherwise a custom library path can be configured
using ldconfig (see `man ld.so 8`)
This changes equalization so it only counts children oriented in the
same direction.
This makes splits a bit more aesthetic, and replicates how split
equalization works in neovim.
These tests write specific lines into a 10-column-wide test screen. The
"prompt3$ input3\n" writes exceed that column limit, and some of their
characters wrap onto the following line.
These tests' current assertions aren't sensitive to that overflow, but I
spotted the problem while doing some related work, and I thought it
worth making these corrections to avoid any future surprises.
These tests write specific lines into a 10-column-wide test screen. The
"prompt3$ input3\n" writes exceed that column limit, and some of their
characters wrap onto the following line.
These tests' current assertions aren't sensitive to that overflow, but
I spotted the problem while doing some related work, and I thought it
worth making these corrections to avoid any future surprises.
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.
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.
Fixes#4800, supercedes #5995
This is a rewrite of #5995 (though the solution is mostly the same since
this is pretty straightforward). The main difference is the rebase on
the new mouse handling we've had since, and I also continue to update
the selection clipboard on non-left-mouse events.
Fixes#4800, supercedes #5995
This is a rewrite of #5995 (though the solution is mostly the same since
this is pretty straightforward). The main difference is the rebase on
the new mouse handling we've had since, and I also continue to update
the selection clipboard on non-left-mouse events.
Also fixes crashes in both vanilla GTK and Adwaita implementations of
`closeTab`, which erroneously close windows twice when there are no more
tabs left (we probably already handle it somewhere else).
Besides avoiding copying, this allows consumers to choose to allocate
these structs on the stack or to allocate on the heap. It also gives the
apprt.App a stable pointer sooner in the process.
Besides avoiding copying, this allows consumers to choose to allocate
these structs on the stack or to allocate on the heap. It also gives the
apprt.App a stable pointer sooner in the process.
We were using the Rectangle target for simpler addressing, since that
allows for pixel coordinates instead of normalized coordinates, but
there are downsides to rectangle textures, including not supporting
compressed texture formats, and we do probably want to use compressed
formats in the future, so I'm making this change now.
This was applied to the wrong thing by accident, making the custom
shader ping-pong textures compressed, which breaks custom shaders
because compressed texture formats are not color renderable.
Additionally, I've not switched the compressed format to the correct
texture options, because I tried that and it turns out that the default
compression applied by drivers can't be trusted to be good quality and
generally speaking looks terrible. In the future we can explore doing
the compression ourselves CPU-side with something like b7enc_rdo.
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.
Fixes an issue where rectangle selections would appear visually wrong if
their start or end were out of the viewport area, because when cloning
them the restored pins were defaulting to the start and end of the row
instead of the appropriate column.
This issue is shown in discussion #7687.
Replaces #7676
When building as a flatpak, don't install the systemd user services
since flatpaks can't use them. Remove references to the systemd service
from the DBus service.
Also, customize the app metadata depending on the debug mode.
Co-authored-by: Leorize <leorize+oss@disroot.org>
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.
Cleans up the logic, checks for out of bounds using rows instead of
sel.contains because that excludes cases where a rectangle selection
doesn't include the leftmost column.
Also adds test for clipping behavior of rectangular selections.
Fixes an issue where rectangle selections would appear visually wrong if
their start or end were out of the viewport area, because when cloning
them the restored pins were defaulting to the start and end of the row
instead of the appropriate column.
GHOSTTY_VERSION was mistakenly referenced but is never set. Use
TERM_PROGRAM_VERSION which is actually provided by Exec.zig from
build_config.version_string.
- Fix fallback path from full path to "src" since full path is built
later
- Extract duplicate code from listCachedHosts and clearCache into
runCacheCommand helper
- Addresses feedback from @00-kat
- Clarify that +list-ssh-cache shows shell integration cached hosts
- Add note about +clear-ssh-cache command and when to use it
Addresses mitchellh's feedback on action descriptions.
Updates Config.zig documentation to reflect that SSH cache management is
now handled by proper CLI actions (+list-ssh-cache and +clear-ssh-cache)
rather than shell wrapper commands.
Fixes documentation missed in e8c8a51.
- Add +list-ssh-cache and +clear-ssh-cache CLI actions
- Remove ghostty() wrapper functions from all shell integrations
- Improve variable naming in shell scripts for readability
Addresses @00-kat's feedback about CLI discoverability and naming
consistency. The new CLI actions follow established Ghostty patterns
and are discoverable via `ghostty --help`, while maintaining clean
separation of concerns between shell logic and cache management.
Addresses feedback about separation of concerns in shell integration
scripts.
Extracts host caching logic to
`src/shell-integration/shared/ghostty-ssh-cache` and updates all four
shell integrations to use the shared script. The `shared/` subdirectory
preserves the existing organizational pattern where all shell-specific
code lives in subdirectories. This cleanly separates SSH transport logic
from cache management while reducing code duplication by ~25%.
All existing SSH integration behavior remains identical.
Rewrote shell functions to support the two new flags for
shell-integration-features:
- ssh-env: TERM compatibility + best effort environment variable
propagation (anything beyond TERM will depend on what the remote host
allows)
- ssh-terminfo: automatic terminfo installation with control socket
orchestration
- Flags work independently or combined
Implementation optimizations:
- ~65% code reduction through unified execution path
- Eliminated GHOSTTY_SSH_INTEGRATION environment variable system
- Replaced complex function dispatch with direct flag detection
- Consolidated 4 cache helper functions into single _ghst_cache()
utility
- Simplified control socket management (removed multi-step
orchestration)
- Subsequent connections to cached hosts are now directly executed and
more reliable
New additions:
- If ssh-terminfo is enabled, ghostty will be wrapped to provide users
with convenient commands to invoke either of the two utility functions:
`ghostty ssh-cache-list` and `ghostty ssh-cache-clear`
- Add ssh_env and ssh_terminfo flags to ShellIntegrationFeatures
- Remove SSHIntegration enum and ssh-integration config option
- Update setupFeatures to handle new flags via reflection
- Remove setupSSHIntegration function and all references
Integrates SSH functionality into existing shell-integration-features
system for better consistency and user control.
Need a sanity check on this new approach for "full" to help determine if
it's worth additional iteration/refinement.
It solves the double auth issue, successfully propagates env vars, and
avoids output noise for connections that happen after terminfo is
installed. The only issue I don't have time to fix tonight is the fact
that it drops the MOTD for cached (re)connections.
- Cache known hosts with terminfo in
$GHOSTTY_RESOURCES_DIR/terminfo_hosts
- Skip installation step for cached hosts (single connection instead of
two)
- Use secure file permissions (600) and atomic writes
- Extract SSH target safely from command arguments
- Maintains full functionality while improving user experience on
repeated connections
- Fix elvish function name mismatch and use conj for list operations
- Simplify terminfo installation command per ghostty docs (tic -x -)
- Fix conditional structure to ensure error messages always print
- Remove redundant checks and optimize array initialization
- Use consistent patterns across bash, fish, elvish, and zsh
implementations
Add detailed explanations of shell function behavior, TERM compatibility
trade-offs, environment variable propagation, and authentication
requirements per maintainer feedback.
Keeps only functional additions for SSH integration wrapper,
preserving original line breaks and indentation to minimize
diff noise per maintainer feedback.
Keeps only functional additions for SSH integration wrapper,
preserving original line breaks and indentation to minimize
diff noise per maintainer feedback.
- Implements opt-in SSH wrapper following sudo pattern
- Supports term_only, basic, and full integration levels
- Fixes xterm-ghostty TERM compatibility on remote systems
- Propagates shell integration environment variables
- Allows for automatic installation of terminfo if desired
- Addresses GitHub discussions #5892 and #4156
Adds support for background images via the `background-image` config.
Resolves#3645, supersedes PRs #4226 and #5233.
See docs of added config keys for usage details.
> [!NOTE]
> Unlike what is implied by the original issue, because this is
implemented in the renderer it is inherently per-surface not per-window,
meaning a window with a split will have two copies of the background
image.
### Future work
- We should probably introduce code in the apprts that tells surfaces
their position and size relative to the window, which would allow us to
add a `background-image-area` config with options for `surface` and
`window` to control that behavior (and probably default it to `window`).
That apprt code would also allow for window-relative custom shader
locations, which is also a fairly common user request, so I think it's
worth it.
- Currently if you use a high res background image this is fairly
inefficient, since each surface independently loads a copy of the
background image. On systems with limited VRAM this could be an issue
for users who use a lot of surfaces, so it may be worth making a shared
image cache to avoid this problem.
- ~~It's probably worth using compressed texture formats for images,
I'll look in to doing that.~~ (c43702c)
BPTC is required to be available OpenGL >= 4.2 and our minimum is 4.3 so
this is safe in terms of support. I tested briefly in a VM and didn't
encounter any problems so this should just be a complete win.
(Note: texture data is already automatically compressed on Metal)
Adds support for background images via the `background-image` config.
Resolves#3645, supersedes PRs #4226 and #5233.
See docs of added config keys for usage details.
Reverts two commits:
977cd530c7820b7e432b
These break build from source on Linux for two reasons:
1.) The systemd user service needs to be installed in the `share`
prefix, not the `lib` prefix. This lets it get picked up in `~/.local`
but is also correct for just standard FHS paths.
2.) The `ghostty` path in the systemd user service needs to be absolute.
We should interpolate in the build install prefix to form an absolute
path.
Reverts two commits:
977cd530c7820b7e432b
These break build from source on Linux for two reasons:
1.) The systemd user service needs to be installed in the `share`
prefix, not the `lib` prefix. This lets it get picked up in `~/.local`
but is also correct for just standard FHS paths.
2.) The `ghostty` path in the systemd user service needs to be absolute.
We should interpolate in the build install prefix to form an absolute
path.
There are two main improvements being made here. First, we move away
from using autohash and instead use a one-shot strategy similar to the
Style hashing. Since the GlyphKey includes the Metrics struct, which
contains quite a few fields, autohash was performing expensive and
unnecessary repeated updates.
The second improvement is actually just, not hashing Metrics. By
ignoring the Metrics field, we can fit the rest of the GlyphKey into a
64-bit packed struct and just return that as the hash! It ends up being
unique for each GlyphKey in renderGlyph, and is nearly a zero-cost
operation.
This ends up boosting the performance (on my machine at least), from
around 560fps to 590fps on the DOOM-fire benchmark.
Flatpak currently does not export systemd user units. As such, remove
references to it from D-Bus services to prevent D-Bus daemon from trying
to start a non-existent service.
Additionally, make sure that the D-Bus service name is correct for debug
builds.
Follow up to #7433
There are two main improvements being made here. First, we move away from using autohash and instead
use a one-shot strategy similar to the Style hashing. Since the GlyphKey includes the Metrics struct,
which contains quite a few fields, autohash was performing expensive and unnecessary repeated updates.
The second improvement is actually just, not hashing Metrics. By ignoring the Metrics field, we can
fit the rest of the GlyphKey into a 64-bit packed struct and just return that as the hash! It
ends up being unique for each GlyphKey in renderGlyph, and is nearly a zero-cost operation.
This ends up boosting the performance (on my machine at least), from around 560fps to 590fps on the
DOOM-fire benchmark.
Flatpak currently does not export systemd user units. As such, remove
references to it from D-Bus services to prevent D-Bus daemon from trying
to start a non-existent service.
Additionally, make sure that the D-Bus service name is correct for debug
builds.
Without waiting on the xdg-open process on linux/freebsd, we end up with
a defunct (zombie) process after each time we open a URL.
For example, after click on two URLs in a ghostty, here is the output of
`ps ux | grep xdg-open`:
```
pbui 8364 0.0 0.0 0 0 tty7 Z+ 05:03 0:00 [xdg-open] <defunct>
pbui 8453 0.0 0.0 0 0 tty7 Z+ 05:03 0:00 [xdg-open] <defunct>
```
Perhaps we should revisit 695bc30, which removed the wait in the first
place. On my machine running Alpine Linux 3.22, `xdg-open` does not stay
alive and finishes immediately, thus making it safe to call wait (and
not block). This is also the case on my other machine running Ubuntu
24.04: `xdg-open` launches the URL in a browser and terminates
immediately.
Either way, this process must be waited upon eventually. Otherwise, we
will accumulate a collection of defunct processes until the terminal
itself terminates.
Introduces host resources directory as a new concept: A directory
containing application resources that can only be accessed from the host
operating system. This is significant for sandboxed application runtimes
like Flatpak where shells spawned on the host should have access to
application resources to enable integrations.
Alongside this, apprt is now allowed to override the resources lookup
logic.
## Description
Yet another edge case in #2484
When macOS's "Private WiFi address" feature is enabled it'll change the
hostname to a mac address. Mac addresses look like URIs with a hostname
and port component, e.g. `12:34:56:78:90:12` where `:12` looks like port
`12`. However, mac addresses use hex numbers and as such can also
contain letters `a` through `f`. So, a mac address like
`ab💿ef🆎cd:ef` is valid, but will not be parsed as a URI, because
`:ef` is not a valid port.
This commit attempts to fix that by checking if the hostname is a valid
mac address when `std.Uri.parse()` fails and constructing a new
`std.Uri` struct using that information.
It's not perfect, but is equally compliant with the URI spec as
`std.Uri` currently is. Meaning not at all compliant 😅
## Testing instructions
### Unit tests
> [!IMPORTANT]
> I don't know if these tests are run in CI or if they're picked up by
`zig build test`. I get an unrelated crash that mentions `minidump` and
an invalid OSC command when I try to run `zig build test` on my mac.
1. Make sure `zig test src/os/hostname.zig` is passing.
### Manual testing instructions
#### Setup - Enable the "Private WiFi address" setting
> [!IMPORTANT]
> You must be connected to WiFi to be able to test this.
1. Open your mac's "System Settings".
2. Go to Network → Wi-Fi → Details.
<img width="710" alt="image"
src="https://github.com/user-attachments/assets/fe30cfe7-8e77-4421-8b36-2f7aab0918dd"
/>
3. Set the "Private Wi-Fi address" setting to `Rotating`.
<img width="710" alt="image"
src="https://github.com/user-attachments/assets/bd695c20-106c-46bd-8862-cbdce55fed6f"
/>
> [!IMPORTANT]
> Now you wait. The private Wi-Fi address will eventually rotate to a
mac address that ends with a non-digit, e.g. `0a`, `ff`, `e2`, etc.
You'll notice this when your shell integration stops working, e.g. you
open a new tab in Ghostty and the shell is in your home directory
instead of whichever directory you had open in your previous tab.
#### Testing the changes
1. Open Ghostty.
3. `cd` to any directory that isn't the default (usually `$HOME`)
directory, e.g. `cd Documents`.
4. Open a new tab (<kbd>Cmd+T</kbd>) or split (<kbd>Cmd+D</kbd>).
5. Assuming the setup steps have been followed you should:
* On `main`: land in `$HOME` in the new tab or split.
* On this branch: land in the same working directory as the original tab
or split.
Introduces host resources directory as a new concept: A directory
containing application resources that can only be accessed from the host
operating system. This is significant for sandboxed application runtimes
like Flatpak where shells spawned on the host should have access to
application resources to enable integrations.
Alongside this, apprt is now allowed to override the resources lookup
logic.
This adds a new CLI `ghostty +edit-config`. This will open the config
file in the user's specified `$EDITOR`. If Ghostty has never been
configured, this will also create the initial config file with some
default templated contents (the same as that which we introduced back in
Ghostty 1.0.1 or something -- not new behavior here).
This is useful on its own because it will find the correct configuration
path to open. If users are terminal users anyway (not a big stretch
since this is a terminal app), this will allow them to easily edit
config right away.
This is also forward looking: I want to replace our "Open Config" action
to open a Ghostty window executing this command so that users can edit
their config in a terminal editor. This has been heavily requested since
forever (short of a full GUI settings editor, which is not ready yet). I
don't do this in this PR but plan to in a future PR.
Even further forward looking: when we have an API, we can have
`edit-config` auto-reload the config on exit. This isn't possible today
but this is where we'd put that.
This adds a new CLI `ghostty +edit-config`. This will open the config
file in the user's specified `$EDITOR`. If Ghostty has never been
configured, this will also create the initial config file with some
default templated contents (the same as that which we introduced back in
Ghostty 1.0.1 or something -- not new behavior here).
This is useful on its own because it will find the correct configuration
path to open. If users are terminal users anyway (not a big stretch
since this is a terminal app), this will allow them to easily edit
config right away.
This is also forward looking: I want to replace our "Open Config" action
to open a Ghostty window executing this command so that users can edit
their config in a terminal editor. This has been heavily requested since
forever (short of a full GUI settings editor, which is not ready yet). I
don't do this in this PR but plan to in a future PR.
This PR is a collection of small improvements, cleanup, and fixes to the
renderer, as a follow-up to #7620.
### Summary of changes
- The nearly identical-between-backends `cell.zig` and `image.zig` are
now both unified.
- Shader pipeline preparation is now even DRYer in preparation for
future changes that will involve adding new pipelines, where I don't
want to be slowed down by having to add them in multiple places to get
things working. There is now a single source of truth at the top of
`shaders.zig` for what core pipelines are available and how they're
configured.
- Global background color drawn in a separate step before individual
cell background colors, this is required to be able to draw kitty images
between these layers. Can't use the clear color for this because that
would require color space conversions on the CPU-side which we don't yet
have utilities for.
- Fixed a bug with the kitty image z-index logic where not having
foreground images made the background images act as foreground images.
- Moved the custom shader uniform buffer to the frame state so we don't
create a new one every frame like we were before.
- Fixed color glyphs under OpenGL being channel-swapped because the
texture format was RGBA when the data was BGRA (#7670).
Forgot to change these instances when I renamed rgb(a) to bgr(a), which
was breaking test builds. Also went ahead and fixed some code that was
assuming rgba was actually rgba order and added a note to another part.
Also changes color atlas to always use an sRGB internal format so that
the texture reads automatically linearize the colors.
Renames the misleading `rgba` atlas format to `bgra`, since both
FreeType and CoreText are set up to draw color glyphs in bgra.
In this format it will be a lot easier to iterate on this since adding
and removing pipelines only has to be done in a single place.
This commit also separates out the main background color from individual
cell background color drawing, because sometimes kitty images need to be
between the main background and individual cell backgrounds (kitty image
z-index seems to be entirely broken at the moment, I intend to fix it in
future commits).
The code in metal/image.zig and opengl/image.zig was virtually identical
save for the texture options, so I've moved that to the GraphicsAPI and
unified them in to renderer/image.zig
Fixes#7647
See #7647 for context. This commit works by extending the `input` work
introduced in #7652 to libghostty so that the macOS can take advantage
of it. At that point, it's just the macOS utilizing `input` in order to
set the command and `exit` up similar to Terminal and iTerm2.
This applies both to files opened directly by Ghostty as well as the App
Intent to run a command in a new terminal.
Fixes#7647
See #7647 for context. This commit works by extending the `input` work
introduced in #7652 to libghostty so that the macOS can take advantage
of it. At that point, its just the macOS utilizing `input` in order to
set the command and `exit` up similar to Terminal and iTerm2.
This adds a new configuration `input` that allows passing either raw
text or file contents as stdin when starting the terminal.
The input is sent byte-for-byte to the terminal, so control characters
such as `\n` will be interpreted by the shell and can be used to run
programs in the context of the loaded shell. After the input is sent, it
switches to `stdin` as usual.
Example: `ghostty --input="hello, world\n"` will start the your default
shell, run `echo hello, world`, and then show the prompt
The `--input` configuration can be repeated to send multiple data
concatenated directly to together. Values can be also be prefixed with
`file:` to send a file contents, e.g. `file:data.txt`. File contents are
limited to 10MB to prevent excessive memory usage and avoid malicious
use. Beyond that, users should write their own script...
This is all in pursuit of #7647, but we're not there yet.
This adds a new configuration `input` that allows passing either raw
text or file contents as stdin when starting the terminal.
The input is sent byte-for-byte to the terminal, so control characters
such as `\n` will be interpreted by the shell and can be used to run
programs in the context of the loaded shell.
Example: `ghostty --input="hello, world\n"` will start the your default
shell, run `echo hello, world`, and then show the prompt.
Supersedes #6912, implements #6901
Also included in this PR is a fix/cleanup of the custom shader uniform
handling, moved the CPU-side custom shader uniforms struct to the main
renderer struct instead of having it be per-frame, moved the layout
struct to `shadertoy.zig` since it has the `std140` layout for both
backends.
Also, I added the current/previous cursor colors to the uniforms, since
I figured they'd be useful to have and it's a trivial addition.
### Future Work
- This extension to the shadertoy uniforms needs to be documented
somewhere so it's discoverable by users.
- The flipped Y axis on Metal still needs to be fully addressed instead
of just being patched over like it is right now.
This regression may have existed on Sequoia too, but I only saw it on
Tahoe.
The issue is that we should not be setting up titlebar accessory views
when there is no titlebar. This was triggering an AppKit assertion.
To further simplify things, I always use the basic window styling if
window decorations are off, too.
This regression may have existed on Sequoia too, but I only saw it on
Tahoe.
The issue is that we should not be setting up titlebar accessory views
when there is no titlebar. This was triggering an AppKit assertion.
To further simplify things, I always use the basic window styling if
window decorations are off, too.
These should not be independent per-frame, that makes the time
calculations all sorts of jank.
Also moves the uniforms struct layout in to `shadertoy.zig` and cleans
up the handling in general somewhat.
It's here, the long-foretold and long-procrastinated renderer rework!
Hopefully this makes it easier to adapt and modify the renderer in the
future and ensures feature parity between Metal and OpenGL. Despite
having been a lot of work to write initially, with the abstraction layer
in place I feel like working on the renderer will be a much more
pleasant experience going forward.
## Key points
- CPU-side renderer logic is now mostly unified via a generic
`Renderer`.
- A graphics API abstraction layer over OpenGL and Metal has been
introduced.
- Minimum OpenGL version bumped to `4.3`, so can no longer be run on
macOS; I used the nix VM stuff for my testing during development. (Edit
by @mitchellh: Note for readers that Ghostty still works on macOS, but
the OpenGL backend doesn't, only the Metal one)
- The OpenGL backend now supports linear blending! Woohoo! The default
`alpha-blending` has been updated to `linear-corrected` since it's
essentially a strict improvement over `native`. The default on macOS is
still `native` though to match other mac apps in appearance, since macOS
users are more sensitive to text appearance.
- Custom shaders can now be hot reloaded.
- The background color is once again drawn by us, so custom shaders can
interact with it properly. In general, custom shaders should be a little
more robust.
## The abstraction layer
The general hierarchy of the abstraction layer is as such:
```
[ GraphicsAPI ] - Responsible for configuring the runtime surface
| | and providing render `Target`s that draw to it,
| | as well as `Frame`s and `Pipeline`s.
| V
| [ Target ] - Represents an abstract target for rendering, which
| could be a surface directly but is also used as an
| abstraction for off-screen frame buffers.
V
[ Frame ] - Represents the context for drawing a given frame,
| provides `RenderPass`es for issuing draw commands
| to, and reports the frame health when complete.
V
[ RenderPass ] - Represents a render pass in a frame, consisting of
: one or more `Step`s applied to the same target(s),
[ Step ] - - - - each describing the input buffers and textures and
: the vertex/fragment functions and geometry to use.
:_ _ _ _ _ _ _ _ _ _/
v
[ Pipeline ] - Describes a vertex and fragment function to be used
for a `Step`; the `GraphicsAPI` is responsible for
these and they should be constructed and cached
ahead of time.
[ Buffer ] - An abstraction over a GPU buffer.
[ Texture ] - An abstraction over a GPU texture.
```
More specific documentation can be found on the relevant structures.
## Miscellany
Renderers (which effectively just means the generic renderer) are now
expected to only touch GPU resources in `init`, certain lifecycle
functions such as the `displayRealized`/`displayUnrealized` callbacks
from GTK-- and `drawFrame`; and are also expected to be thread-safe.
This allows the renderer thread to build the CPU-side buffers
(`updateFrame`) even if we can only *draw* from the app thread.
Because of this change, we can draw synchronously from the main thread
on macOS when necessary to always have a frame of the correct size
during a resize animation. This was necessary to allow the background to
be drawn by our GPU code (instead of setting a background color on the
layer) while still avoiding holes during resize.
The OpenGL backend now theoretically has access to multi-buffering, but
it's disabled (by setting the buffer count to 1) because it
synchronously waits for frames to complete anyway which means that the
extra buffers were just a waste of memory.
## Validation
To validate that there are no significant or obvious problems, I
exercised both backends with a variety of configurations, and visually
inspected the results. Everything looks to be in order.
The images are available in a gist here:
https://gist.github.com/qwerasd205/c1bd3e4c694d888e41612e53c0560179
## Memory
Here's a comparison of memory usage for ReleaseFast builds on macOS,
between `main` and this branch.
Memory figures given are values from Activity Monitor measuring windows
of the same size, with two tabs with 3 splits each.
||Before|After|
|-:|-|-|
|**Memory**|247.9 MB|224.2 MB|
|**Real Memory**|174.4 MB|172.5 MB|
Happily, the rework has slightly *reduced* the memory footprint- likely
due to removing the overhead of `CAMetalLayer`. (The footprint could be
reduced much further if we got rid of multi-buffering and satisfied
ourselves with blocking for each frame, but that's a discussion for
another day.)
If someone could do a similar comparison for Linux, that'd be much
appreciated!
## Notes / future work
- There are a couple structures that *can* be unified using the
abstraction layer, but I haven't gotten around to unifying yet.
Specifically, in `renderer/(opengl|metal)/`, there's `cell.zig` and
`image.zig`, both of which are substantially identical between the two
backends. `shaders.zig` may also be a candidate for unification, but
that might be *overly* DRY.
- ~~I did not double-check the documentation for config options, which
may mention whether certain options can be hot-reloaded; if it does then
that will need to be updated.~~ Fixed: be5908f
- The `fragCoord` for custom shaders originates at the top left for
Metal, but *bottom* left for OpenGL; fixing this will be a bit annoying,
since the screen texture is likewise vertically flipped between the two.
Some shaders rely on the fragcoord for things like falling particles, so
this does need to be fixed.
- `Target` should be improved to support multiple types of targets right
now it only represents a framebuffer or iosurface, but it should also be
able to represent a texture; right now a kind of messy tagged union is
used so that steps can accept both.
- Custom shader cursor uniforms (#6912) and terminal background images
(#4226, #5233) should be much more straightforward to implement on top
of this rework, and I plan to make follow-up PRs for them once this is
merged.
- I *do* want to do a rework of the pipelines themselves, since the way
we're rendering stuff is a bit messy currently, but this is already a
huge enough PR as it is- so for now the renderer still uses the same
rendering passes that Metal did before.
- We should probably add a system requirements section to the README
where we can note the minimum required OpenGL version of `4.3`, any even
slightly modern Linux system will support this, but it would be good to
document it somewhere user-facing anyway.
# TODO BEFORE MERGE
- [x] Have multiple people test this on both macOS and linux.
- [ ] ~~Have someone with a better dev setup on linux check for memory
leaks and other problems.~~ (Skipped, will merge and let tip users
figure this out, someone should *specifically* look for memory leaks
before the next versioned release though.)
- [x] Address any code review feedback.
This updates the Ghostty icon to be compatible with macOS Tahoe
(supports glass effects, light/dark, tinting, etc.). This icon is made
in the new Apple Icon Composer as the source format, and all other
formats are exported from it.
This commit also updates the icon for non-Apple platforms because the
icon is fundamentally the same and I don't see any reason to maintain
multiple icons of fundamentally the same design and style.
This commit also includes updates to the macOS app so that the About
Window and so on will use the new icon.
This PR integrates Ghostty on macOS with the [App
Intents](https://developer.apple.com/documentation/appintents) system.
The focus of this initial work was on enabling [Apple
Shortcuts](https://support.apple.com/guide/shortcuts/welcome/ios) on
macOS, but App Intents are the same underlying system that powers a
number of other Apple features such as Spotlight, Siri, Widgets, and
more. We don't do much with these latter ones yet, though.
Additionally, this PR begins to refactor and untangle some of our
libghostty API calls from macOS views. Presently, macOS views and view
controllers directly call into the libghostty API and own libghostty
data models. This tight coupling is kind of nasty because it tends to
also couple libghostty API calls to the main GUI thread when they don't
really have to be (they just have to not be concurrently accessed). This
becomes an issue because App Intents run on background threads. This PR
starts to extract out some of this business logic into standalone
classes, but we still force all execution onto the main thread during
the transition.
**Version requirement:** Most of the shortcuts will work on macOS 13,
but there are some that require macOS 14, and some functionality will
require macOS 26. We gracefully degrade in all scenarios (the
capabilities that are unavailable just don't show up on older systems).
> [!IMPORTANT]
>
> This bumps our build requirements on macOS to Xcode 26 and the macOS
26 SDK. **You can still build on macOS 15,** but you must be using the
Xcode 26 beta. This includes a README update about that.
## Why?
Apple Shortcuts is an extremely powerful scripting tool on Apple
platforms. It comes with a number of built-in capabilities such as
moving windows, taking screenshots, fetching secrets, and more. By
integrating with Apple Shortcuts, it allows Ghostty to become scriptable
to a certain extent while also being able to take advantage of this
large ecosystem.
It is a huge downside that Shortcuts is Apple-only and I still would
like to make Ghostty scriptable to some extent on Linux and other
platforms as well. This work doesn't preclude that goal, but gives us an
answer to a large subset of users (macOS users), which is great.
Beyond this, no terminals integrate with Apple Shortcuts except the
built-in Terminal. And even then, the built-in Terminal only exposes two
actions (run script and run script over SSH). I think there's a lot that
can be done by exposing more functionality and I'm excited to see what
people do with this.
Finally, I think Shortcuts is possibly a way we can do some GUI testing
on macOS. That remains to be explored but it seems promising.
## Capability
The initial set of Shortcut actions is shown in the screenshot below:

These can be combined with the built-in shortcuts to do some pretty
interesting things.
### Future
There are more capabilities I'd like to expose, but they require
changing core parts of Ghostty that I didn't want to mix into this PR.
## Security
Scripting Ghostty can be considered a security risk, since it allows
arbitrary command execution, reading terminal output, etc. Therefore,
Ghostty will ask for permission prior to allowing any Shortcut to remote
control it:
<img width="859" alt="image"
src="https://github.com/user-attachments/assets/62344248-9c2c-402d-80f6-3fe3910d23fd"
/>
This can be directly overridden using the new `macos-shortcuts`
configuration, which defaults to `ask` but can also be set to `deny` or
`allow` with self-explanatory behaviors.
We need this to get info about the padding, even if we do derive the
grid and screen size separately.
In the future this should possibly be changed to a message that only
sends the padding info and nothing else.
If this was Swift code, we'd be using a strong reference, which would
retain the layer for us and release it when the object is deallocated,
but this is Zig land so we have to do that manually.
NOTE: We don't *have* to do this, but it fits much better with Zig idiom
and hopefully avoids potential future footguns. We should do this to any
autoreleased objects that we persist a reference to in a Zig struct.
This commit is very large, representing about a month of work with many
interdependent changes that don't separate cleanly in to atomic commits.
The main change here is unifying the renderer logic to a single generic
renderer, implemented on top of an abstraction layer over OpenGL/Metal.
I'll write a more complete summary of the changes in the description of
the PR.
This PR makes the build script use the correct SDK and version flag for
the iOS Simulator.
Without this PR, the terminal fails to initialize on the iOS Simulator:
```
Compiler failed to build request
metal error=Target OS is incompatible: library was not compiled for the simulator
error initializing surface err=error.MetalFailed
```
<details>
<summary>With the PR</summary>

</details>
Fixes#7635.
This wasn't working before but it just requires a restart of the machine
for the changes to take effect. The namespace runners have this prebuilt
so this should work now.
The other workaround was flaky for unknown reasons so I'd prefer to go
back to this.
This fixes an issue where pressing the red close button in a window or
the "x" button on a tab couldn't differentiate and would always close
the tab or close the window (depending on tab counts).
It seems like in both cases, AppKit triggers the `windowShouldClose`
delegate method on the controller, but for the close window case it
triggers this on ALL the windows in the group, not just the one that was
clicked.
I implemented a kind of silly coordinator that debounces
`windowShouldClose` calls over 100ms and uses that to differentiate
between the two cases.
This wasn't working before but it just requires a restart of the machine
for the changes to take effect. The namespace runners have this prebuilt
so this should work now.
The other workaround was flaky for unknown reasons so I'd prefer to go
back to this.
This fixes an issue where pressing the red close button in a window or
the "x" button on a tab couldn't differentiate and would always close
the tab or close the window (depending on tab counts).
It seems like in both cases, AppKit triggers the `windowShouldClose`
delegate method on the controller, but for the close window case it
triggers this on ALL the windows in the group, not just the one
that was clicked.
I implemented a kind of silly coordinator that debounces
`windowShouldClose` calls over 100ms and uses that to differentiate
between the two cases.
Resolves#7591
This moves our CI to build macOS on Sequoia (macOS 15) with Xcode 26,
including the new macOS 26 beta SDK. This includes tip releases. Stable
releases continue to use Xcode 15 and the stable SDK, in case we need to
make a release before macOS Tahoe is stable (though, if its late enough
in the cycle, we may even cut a full release with it).
Importantly, this will make our builds on macOS 26 use the new styling.
I've added a new job that ensures we can continue to build with Xcode 16
and the macOS 15 SDK, as well, although I think that might come to an
end when we switch over to an IconComposer-based icon. I'll verify then.
For now, we continue to support both.
I've also removed our `hasLiquidGlass` check, since this will now always
be true for macOS 26 builds.
Resolves#7591
This moves our CI to build macOS on Sequoia (macOS 15) with Xcode 26,
including the new macOS 26 beta SDK.
Importantly, this will make our builds on macOS 26 use the new styling.
I've added a new job that ensures we can continue to build with Xcode 16 and
the macOS 15 SDK, as well, although I think that might come to an end
when we switch over to an IconComposer-based icon. I'll verify then. For
now, we continue to support both.
I've also removed our `hasLiquidGlass` check, since this will now always
be true for macOS 26 builds.
We were depending on $GHOSTTY_RESOURCES_DIR for two reasons:
1. To locate our script-adjacent bash-preexec.sh script
2. To restrict our script's execution to environments in which
$GHOSTTY_RESOURCES_DIR is available (i.e. Ghostty-only shells)
For (1), we can instead determine our directory using $BASH_SOURCE[0].
This is slightly differently than our previous behavior, where we'd
always load bash-preexec.sh from the $GHOSTTY_RESOURCES_DIR hierarchy,
even if ghostty.bash from source from somewhere else on the file system
... but we never relied on that behavior, even in development.
For (2), there's no harm in source'ing this script outside of Ghostty,
and if that does become a concern, we can restore this condition or use
something more targeted based on those specific cases.
Historically, I believe (2) was in place to enable (1), so addressing
(1) removes the need for (2).
And lastly, none of the other shell integration scripts depend on
$GHOSTTY_RESOURCES_DIR.
We were depending on $GHOSTTY_RESOURCES_DIR for two reasons:
1. To locate our script-adjacent bash-preexec.sh script
2. To restrict our script's execution to environments in which
$GHOSTTY_RESOURCES_DIR is available (i.e. Ghostty-only shells)
For (1), we can instead determine our directory using $BASH_SOURCE[0].
This is slightly differently than our previous behavior, where we'd
always load bash-preexec.sh from the $GHOSTTY_RESOURCES_DIR hierarchy,
even if ghostty.bash from source from somewhere else on the file system
... but we never relied on that behavior, even in development.
For (2), there's no harm in source'ing this script outside of Ghostty,
and if that does become a concern, we can restore this condition or use
something more targeted based on those specific cases.
Historically, I believe (2) was in place to enable (1), so addressing
(1) removes the need for (2).
And lastly, none of the other shell integration scripts depend on
$GHOSTTY_RESOURCES_DIR.
This integrates with macOS accessibility APIs to expose Ghostty terminal
structure and content.
This is a very, very bare implementation and the terminal contents
currently reported are the _full screen and scrollback_ which is way too
much for realistic human accessibility use. The target use case for this
PR is to enable automated tooling (namely, AI screen readers). However,
this is all groundwork we'll need to iterate and improve the
accessibility work anyways.
To make this work, I also replatformed some of our hacky C APIs onto a
more robust `ghostty_surface_read_text` API that can now read arbitrary
ranges of the screen into C strings for consumers to use. This will be
useful in more places going forward (hint hint).
## Before
Accessibility tooling can't read anything, Ghostty has no attributes, no
contents, just shows up as a square.

## After
A lot of metadata, including the screen contents as text.

Also, split hierarchies are navigable:
https://github.com/user-attachments/assets/a7b2ffb7-dbeb-41b2-8705-9c3200812c4d
This fixes a regression from our Tahoe window styling changes on
earlier, stable versions of macOS. We need to set
"titlebarAppearsTransparent" to true in order to hide the bottom border.
This fixes a regression from our Tahoe window styling changes on
earlier, stable versions of macOS. We need to set
"titlebarAppearsTransparent" to true in order to hide the bottom
border.
This is recommended for macOS Tahoe and all standard menu items now have
associated images. This makes our app look more polished and native for
macOS Tahoe.
On earlier versions of macOS (macOS 15 and earlier), we _do not_ set the
menu item image. Cocoa has supported menu item images for a long time
but it isn't idiomatic to show them in earlier versions, so we only do
this for later macOS versions.
For icon choice, I tried to copy other native macOS apps as much as
possible, mostly from Xcode. It looks like a lot of apps aren't updated
yet. I'm absolutely open to suggestions for better icons but I think
these are a good starting point.
One menu change is I moved "reset font size" above "increase font size"
which better matches other apps (e.g. Terminal.app).
https://github.com/user-attachments/assets/50a68326-221f-454f-9a9c-078878010a63
This is recommended for macOS Tahoe and all standard menu items now have
associated images. This makes our app look more polished and native for
macOS Tahoe.
For icon choice, I tried to copy other native macOS apps as much as
possible, mostly from Xcode. It looks like a lot of apps aren't updated
yet. I'm absolutely open to suggestions for better icons but I think
these are a good starting point.
One menu change is I moved "reset font size" above "increase font size"
which better matches other apps (e.g. Terminal.app).
We have been building on macOS 14 and Xcode 16.0 for a longggg time now.
This gets us to a version that will be running Xcode 26 eventually so we
can ultimately build for Tahoe on a stable OS.
This should change nothing in the interim.
We have been building on macOS 14 and Xcode 16.0 for a longggg time now.
This gets us to a version that will be running Xcode 26 eventually so
we can ultimately build for Tahoe on a stable OS.
This should change nothing in the interim.
Fixes#7568Fixes#7563
This PR incorporates all the fixes necessary to make all of our
`macos-titlebar-style` values work on macOS Tahoe.
All titlebar styles are significantly more efficient on macOS Tahoe. I
was able to refactor a lot of our complicated logic out of `update`
ticks (which are called on _every event loop tick_) to being eventful,
so the CPU required to render any titlebar style on Tahoe is
dramatically lowered. This fix _might_ be able to be back ported but I
tried my best to not modify Sequoia and earlier codebases to keep them
stable.
The major change here is `macos-titlebar-style = tabs`, which takes on a
whole new look with Tahoe. The ultimate goal of this setting is that the
_native tab bar_ goes into the titlebar, and we've respected that here.
For more background, see #7563 on why we can't easily mimic the Sequoia
style. I think longer term, for people who don't want capsule-tabs in
their titlebar, we'll need a custom tab bar.
## Future Improvements
There are obviously future improvements that can be made. I think for
example the readability in some cases of the `tabs` styling is pretty
bad. But I wonder if Apple themselves will do something about this with
the tab bar, so I'm going to hold out on caring too much.
The main goal of this PR was to make sure Ghostty was usable when
recompiled with the v26 SDKs.
## Screenshots
> [!NOTE]
>
> Ignore the weird background color glitches in the titlebar. That's a
screenshot artifact (bug probably in macOS) and not actually visible
when running.
### `macos-titlebar-style = tabs`


### `macos-titlebar-style = transparent`


### `macos-titlebar-style = native`


## TODO
- [x] A really weird bug where if I resize vertically to create
scrollback then create a new tab my font size is... tiny?
- [x] Verify no major regressions on Sequoia
- [x] Sequoia regression: non-native fullscreen with titlebar tabs
doesn't work
- [x] Sequoia regression: reset split zoom button doesn't restore for
tabs titlebar going to zero tabs
- [x] Sequoia regression: transparent isn't working
- [x] Check macOS 26 gates on Solarium instead of version?
- [ ] Merge all windows doesn't create titlebar tabs on Tahoe
Windows with `macos-titlebar-style = hidden` create new windows when the
new tab binding is pressed. This behavior has existed for a long time.
However, these windows did not cascade, meaning they'd appear overlapped
directly on top of the previous window, which is kind of nasty.
This commit changes it so that new windows created via new tab from a
hidden titlebar window will cascade.
Windows with `macos-titlebar-style = hidden` create new windows when the
new tab binding is pressed. This behavior has existed for a long time.
However, these windows did not cascade, meaning they'd appear overlapped
directly on top of the previous window, which is kind of nasty.
This commit changes it so that new windows created via new tab from a
hidden titlebar window will cascade.
Updates all copyright notices to include "Ghostty contributors" to
reflect the fact that Mitchell is not the sole copyright owner.
Also adds "Ghostty contributors" to the author section in the manpages,
linking https://github.com/ghostty-org/ghostty/graphs/contributors for
proper credit.
This change was discussed in the Discord.
Updates all copyright notices to include "Ghostty contributors" to
reflect the fact that Mitchell is not the sole copyright owner.
Also adds "Ghostty contributors" to the author section in the manpages,
linking https://github.com/ghostty-org/ghostty/graphs/contributors for
proper credit.
This updates our build script and CI to support Xcode 26 and macOS
Tahoe. **This doesn't update the Ghostty app to resolve any Tahoe
issues.**
For CI, we've added a new build job that runs on macOS Tahoe with Xcode
26. I've stopped short of updating our tip release job, since I think I
want to wait until I verify a bit more about Tahoe before we flip that
bit. Also, ideally, we'd run Xcode 26 on Sequoia (macOS 15) for
stability reasons and Namespace doesn't have Xcode 26 on 15 yet.
For builds, this updates our build script to find Metal binaries using
`xcodebuild -find-executable` instead of `xcrun`. The latter doesn't
work with Xcode 26, but the former does and also still works with older
Xcodes. I'm not sure if this is a bug but I did report it: FB17874042.
This updates our build script and CI to support Xcode 26 and macOS
Tahoe. **This doesn't update the Ghostty app to resolve any Tahoe
issues.**
For CI, we've added a new build job that runs on macOS Tahoe with Xcode
26. I've stopped short of updating our tip release job, since I think I
want to wait until I verify a bit more about Tahoe before we flip that
bit. Also, ideally, we'd run Xcode 26 on Sequoia (macOS 15) for
stability reasons and Namespace doesn't have Xcode 26 on 15 yet.
For builds, this updates our build script to find Metal binaries using
`xcodebuild -find-executable` instead of `xcrun`. The latter doesn't
work with Xcode 26, but the former does and also still works with older
Xcodes. I'm not sure if this is a bug but I did report it: FB17874042.
Implements mode 1048 for saving/restoring cursor position.
This is the same as mode 1049 but only saves cursor position without
touching the alternate screen.
**save/restore cursor position:**
- `ESC[?1048h` - save cursor position
- `ESC[?1048l` - restore cursor position
**Quick test:**
```bash
printf '\e[5;10H[SAVED HERE]'
printf '\e[?1048h' # save position
printf '\e[15;1HMoved somewhere else...'
printf '\e[?1048l' # restore
printf ' RESTORED!' # should appear next to [SAVED HERE]
```
Fixes#7473
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.
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.
Fixes#7546
SwiftUI uses type and structure to identify views, which can lead
to issues with tree like structures where the shape and type is the same
but the content changes. This was causing #7546.
To fix this, we need to add explicit identity to the split tree view
so that SwiftUI can differentiate when it needs to redraw the view.
We don't want to blindly add Hashable to SplitTree because we don't want
to take into account all the fields. Instead, we add an explicit
"structural identity" to the SplitTreeView that can be used by SwiftUI.
This PR implements the ability to undo/redo new and closed windows,
tabs, and splits.
## Demo
https://github.com/user-attachments/assets/98601810-71b8-4adb-bfa4-bdfaa2526dc6
## Details
### Undo Timeout
Running terminal sessions _remain running_ for a configurable period of
time after close, during which time they're undoable. This is similar to
"email unsend" (since email in the traditional sense can't be unsent,
clients simply hold onto it for a period of time before sending).
This behavior is not unique to Ghostty. The first and only place I've
seen it is in iTerm2. And iTerm2 behaves similarly, although details of
our behavior and our implementation vary greatly.
The configurable period of time is done via the `undo-timeout`
configuration. The default value is 5 seconds. This feels reasonable to
be and is grounded in being the default for iTerm2 as well, so it's
probably a safe choice.
Undo can be disabled by setting `undo-timeout = 0`.
### Future
The actions that can be potentially undone/redone can be easily expanded
in the future. Some thoughts on things that make sense to me:
- Any sort of split resizing, including equalization
- Moving tabs or splits
#### What about Linux?
I'd love to support this on Linux. I don't think any other terminal on
Linux has this kind of feature (definitely might be wrong, but I've
never seen it and I've looked at a lot of terminal emulators 😄 ). But
there's some work to be done to get there.
## TODO for the Draft PR
This is still a draft. There are some items remaining (list will update
as I go):
- [x] Undoing a closed window is sometimes buggy still and I'm not sure
why, I have to dig into this.
- [x] New window should be undoable
- [x] New tab should be undoable
- [x] Close All Windows should be undoable
- [x] I think I have to get rid of TerminalManager. Undone windows won't
be in TerminalManager's list of controllers and I think that's going to
break a lot of things.
- [x] I haven't tested this with QuickTerminal at all. I expect bugs
there but I want undo to work with splits there.
- [x] Close window with the red traffic light button doesn't trigger
undo
- [x] Closing window with multiple tabs undoes them as separate windows
When closing a window that contains multiple tabs, the undo operation
now properly restores all tabs as a single tabbed window rather than
just restoring the active tab.
The implementation:
- Collects undo states from all windows in the tab group before closing
- Sorts them by their original tab index to preserve order
- Clears tab group references to avoid referencing garbage collected objects
- Restores all windows and re-adds them as tabs to the first window
- Tracks and restores which tab was focused (or focuses the last tab if none were)
AI prompts that generated this commit are below.
Each separate prompt is separated by a blank line, so this session was
made up with many prompts in a back-and-forth conversation.
> We need to update the undo/redo implementation in
> @macos/Sources/Features/Terminal/TerminalController.swift `closeWindowImmediately`
> to handle the case that multiple windows in a tab group are closed all at once,
> and to restore them as a tabbed window. To do this, I think we should collect
> all the `undoStates`, sort them by `tabIndex` (null at the end), and then on j
> restore, restore them one at a time but add them back to the same tabGroup. We
> can't use the tab group in the `undoState` because it will be garbage collected
> by then. To be sure, we should just set it to nil.
I should note at this point that the feature already worked, but the
code quality and organization wasn't up to my standards. If someone
using AI were just trying to make something work, they might be done at
this point.
I do think this is the biggest gap I worry about with AI-assisted
development: bridging between the "it works" stage at a junior quality
and the "it works and is maintainable" stage at a senior quality. I
suspect this will be a balance of LLMs getting better but also senior
code reviewers remaining highly involved in the process.
> Let's extract all the work you just did into a dedicated private method
> called `registerUndoForCloseWindow`
Manual: made some tweaks to comments, moved some lines around, didn’t change
any logic.
> I think we can pull the tabIndex directly from the undoState instead of
> storing it in a tuple.
> Instead of `var undoStates`, I think we can create a `let undoStates` and
> build and filter and sort them all in a chain of functional mappings.
> Okay, looking at your logic for restoration, the `var firstController` and
> conditionals are littly messy. Can you make your own pass at cleaning those
> up and I'll review and provide more specific guidance after.
> Excellent. Perfect. The last thing we're missing is restoring the proper
> focused window of the tab group. We should store that and make sure the
> proper window is made key. If no windows were key, then we should make the
> last one key.
> Excellent. Any more cleanups or comments you'd recommend in the places you
> changed?
Notes on the last one: it gave me a bunch of suggestions, I rejected most but
did accept some.
> Can you write me a commit message summarizing the changes?
It wrote me a part of the commit message you're reading now, but I
always manually tweak the commit message and add my own flair.
When macOS's "Private WiFi address" feature is enabled it'll change the
hostname to a mac address. Mac addresses look like URIs with a hostname
and port component, e.g. 12:34:56:78:90:12 where `:12` looks like port
12. However, mac addresses can also contain letters a through f, so a
valid mac address like ab💿ef🆎cd:ef is valid, but will not be parsed
as a URI, because `:ef` is not a valid port.
This commit attempts to fix that by checking if the hostname is a valid
mac address when `std.Uri.parse()` fails and constructing a new std.Uri
struct using that information.
It's not perfect, but is equally compliant with the URI spec as std.Uri
currently is.
Since the W3C rewrite we're able to specify codepoints like `+` directly
in the config format who otherwise have special meanings. Turns out we
forgot to do the same for `=`.
Since the W3C rewrite we're able to specify codepoints like `+` directly
in the config format who otherwise have special meanings. Turns out we
forgot to do the same for `=`.
This PR solves a few resource leaks in `FlatpakHostCommand`:
- Threads created by `FlatpakHostCommand.spawn` are now detached,
allowing its resources to be released.
- Errors created by various `glib`/`gio` APIs are now freed after use.
This fixes a regression from the new split work last week, but it was
also probably an issue before that in a slightly different way.
With the new split work, the quick terminal was becoming unusable when
the final surface explicitly `exit`-ed, because AppKit/SwiftUI would
resize the window to a very small size and you couldn't see the new
terminal on the next toggle.
Prior to this, I think the quick terminal would've reverted to its
original size but I'm not sure (even if the user resized it manually).
This commit saves the size of the quick terminal at the point all
surfaces are exited and restores it when the quick terminal is shown the
next time with a new initial surface.
This fixes a regression from the new split work last week, but it was
also probably an issue before that in a slightly different way.
With the new split work, the quick terminal was becoming unusable when
the final surface explicitly `exit`-ed, because AppKit/SwiftUI would
resize the window to a very small size and you couldn't see the new
terminal on the next toggle.
Prior to this, I think the quick terminal would've reverted to its
original size but I'm not sure (even if the user resized it manually).
This commit saves the size of the quick terminal at the point all
surfaces are exited and restores it when the quick terminal is shown the
next time with a new initial surface.
This makes sure the underlying thread implementation know to free
resources the moment the thread is no longer necessary, preventing leaks
from not manually collecting the thread.
Fixes#7536
When we're reflowing a row and we need to insert a spacer head, we must
move to the next row to insert it. Previously, we were setting a spacer
head and then copying data into that spacer head, which could lead to
corrupt data and an eventual crash.
In debug builds this triggers assertion failures but in release builds
this would lead to silent corruption and a crash later on.
The unit test shows the issue clearly but effectively you need a
multi-codepoint grapheme such as `👨👨👦👦` to wrap across a row by
changing the columns.
Fixes#7536
When we're reflowing a row and we need to insert a spacer head, we must
move to the next row to insert it. Previously, we were setting a spacer
head and then copying data into that spacer head, which could lead to
corrupt data and an eventual crash.
In debug builds this triggers assertion failures but in release builds
this would lead to silent corruption and a crash later on.
The unit test shows the issue clearly but effectively you need a
multi-codepoint grapheme such as `👨👨👦👦` to wrap across a row by changing
the columns.
I've only recently been using programs that use user notifications
heavily and this commit addresses a number of annoyances I've
encountered.
1. Notifications dispatched while the source terminal surface is focused
are now only shown for a short time (3 seconds hardcoded) and then
automatically dismiss.
2. Notifications are dismissed when the target surface becomes focused
from an unfocused state. This dismissal happens immediately (no delay).
3. Notifications are dismissed when the application exits.
4. This fixes a bug where notification callbacks were modifying view
state, but the notification center doesn't guarantee that the
callback is called on the main thread. We now ensure that
the callback is always called on the main thread.
I've only recently been using programs that use user notifications heavily
and this commit addresses a number of annoyances I've encountered.
1. Notifications dispatched while the source terminal surface is
focused are now only shown for a short time (3 seconds hardcoded)
and then automatically dismiss.
2. Notifications are dismissed when the target surface becomes focused
from an unfocused state. This dismissal happens immediately (no
delay).
3. Notifications are dismissed when the application exits.
4. This fixes a bug where notification callbacks were modifying view
state, but the notification center doesn't guarantee that the
callback is called on the main thread. We now ensure that
the callback is always called on the main thread.
Took another look through #7504 after the merge and realized that the
logic behind the `hasWindowButtons` property wasn't quite sound. It
would return `false` if either _at least one_ button were missing in the
`standardWindowButton(.theButton) == nil` sense, or if _all_ buttons
were hidden in the `isHidden` sense.
With this PR, the logic is rectified: `false` if _all_ buttons are
missing or hidden in any sense, otherwise `true`.
In practice, I suppose Ghostty won't ever instantiate a `TerminalWindow`
where `standardWindowButton(.theButton) == nil`, but might as well get
it right and sleep better at night.
Fixes regression from #7523
I messed two things up around spatial navigation in the split tree that
this commit fixes:
1. The distance in the spatial tree only used a single dimension that we
were navigating. This commit now uses 2D euclidean distance from the
top-left corners of nodes. This handles the case where the nodes are
directly above or below each other better.
2. The spatial slots include split containers because they are layout
elements. But we should only navigate to leaf nodes. This was causing
the wrong navigatin to happen in some scenarios.
Fixes regression from #7523
I messed two things up around spatial navigation in the split tree
that this commit fixes:
1. The distance in the spatial tree only used a single dimension
that we were navigating. This commit now uses 2D euclidean
distance from the top-left corners of nodes. This handles the case
where the nodes are directly above or below each other better.
2. The spatial slots include split containers because they are layout
elements. But we should only navigate to leaf nodes. This was
causing the wrong navigatin to happen in some scenarios.
This is a major rework of how we represent, handle, and render splits in
the macOS app.
This new PR moves the split structure into a dedicated, generic
(non-Ghostty-specific) value-type called `SplitTree<V>`. All logic
associated with splits (new split, close split, move split, etc.) is now
handled by notifications on `BaseTerminalController`. The view hierarchy
is still SwiftUI but it has no logic associated with it anymore and
purely renders a static tree of splits.
Previously, the split hierarchy was owned by AppKit in a type called
`SplitNode` (a recursive class that contained the tree structure). All
logic around creating, zooming, etc. splits was handled by notification
listeners directly within the SwiftUI hierarchy. SwiftUI managed a
significant amount of state and we heavily used bindings, publishers,
and more. The reasoning for this is mostly historical: splits date back
to when Ghostty tried to go all-in on SwiftUI. Since then, we've taken a
more balanced approach of SwiftUI for views and AppKit for data and
business logic, and this has proven a lot more maintainable.
## Spatial Navigation
Previously, focus moving was handled by traversing the tree structure.
This led to some awkward behaviors. See:
https://github.com/ghostty-org/ghostty/issues/524#issuecomment-2668396095
In this PR, we now handle focus moving spatially. This means that move
"left" means moving to the visually left split (from the top-left
corner, a future improvement would be to do it from the cursor
position).
Concretely, given the following split structure:
```
+----------+-----+
| | b |
| | |
| a +-----+
| | |
| | |
| | |
| | |
|----------| d |
| c | |
| | |
+----------+-----+
```
Moving "right" from `c` now moves to `d`. Previously, it would go to
`b`. On Linux, it still goes to `b`.
## Value Types
One of the major architectural shifts is moving **purely to immutable
value types.** Whenever a split property changes such as a new split,
the ratio between splits, zoomed state, etc. we _create an entirely new
`SplitTree` value_ and replace it along the entire view hierarchy. This
is in some ways wasteful, but split hierarchies are relatively small
(even the largest I've seen in practical use are dozens of splits, which
is small for a computer). And using value types lets us get rid of a ton
of change notification soup around the SwiftUI hierarchy. We can rely on
reference counting to properly clean up our closed views.
> [!NOTE]
>
> As an aside, I think value types are going to make it a lot easier in
the future to implement features like "undo close." We can just keep a
trailing list of surface tree states and just restore them. This PR
doesn't do anything like that, but it's now possible.
## SwiftUI Simplicity
Our SwiftUI view hierarchy is dramatically simplified. See the
difference in `TerminalSplitTreeView` (new) vs `TerminalSplit` (old).
There's so much less logic in our new views (almost none!). All of it is
in the AppKit layer which is just way nicer.
## AI Notes
This PR was heavily written by AI. I reviewed every line of code that
was rewritten, and I did manually rewrite at every step of the way in
minor ways. But it was very much written in concert. Each commit usually
started as an AI agent writing the whole commit, then nudging to get
cleaned up in the right way.
One thing I found in this task was that until the last commit, I kept
the entire previous implementation around and compiling. The agent
having access to a previous working version of code during a refactor
made the code it produced as follow up in the new architecture
significantly better, despite the new architecture having major
fundamental differences in how it works!
Conversion of #7497 to a PR. This implements a feature requested in
#7331: an option to hide the default window buttons on macOS for a
cleaner aesthetic.
~~Builds on #7502 as it requires the same change to avoid the main
toolbar title showing on top of the tab bar.~~ EDIT: rebased on main now
that #7502 was merged.
I aligned the scope of the new option with `macos-titlebar-style`, since
they both customize titlebar elements. This means it has the same edge
case quirks: For example, if you change the setting, reload the config,
and then open a new tab, the appearance of the current window will
depend on which tab is in the foreground. I did it this way because
`macos-titlebar-style` provided an easy template for which derived
configs and functions to modify. Let me know if you want me to try
adjusting this so that a change in the setting also takes effect for
current windows/tabs, which I _think_ should be possible.
Screenshots:
* `macos-titlebar-style = transparent` (default)


* `macos-titlebar-style = tabs`


`TabView` assumes to be the sole owner of all `Tab`s within a Window. As
such, it could close the managed `Window` once all tabs are removed from
its widget.
However, during `AdwTabView::close-page` signal triggered by libadwaita,
the `Tab` to be closed will gain an another reference for the duration
of the signal, breaking `TabView.closeTab` (called via
`Tab.closeWithConfirmation`) assumptions that having no tabs meant they
are all destroyed.
This commit solves the issue by scheduling `Tab.closeWithConfirmation`
to be run after `AdwTabView::close-page` signal has finished processing.
Fixes#7426
First, remove the always-inlined openTerminalFromPasteboard code and
combine it with openTerminal. Now that we're doing a bit of work inside
openTerminal, there's little better to having an intermediate, inlined
function.
Second, combine some type-casting operations (saving a .map() call).
Lastly, adjust some variable names because a generic `objs` or `urls`
was a little ambiguous now that we're all in one function scope.
Bumps
[namespacelabs/nscloud-cache-action](https://github.com/namespacelabs/nscloud-cache-action)
from 1.2.7 to 1.2.8.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="449c929cd5"><code>449c929</code></a>
Merge pull request <a
href="https://redirect.github.com/namespacelabs/nscloud-cache-action/issues/20">#20</a>
from namespacelabs/niklas-timeout</li>
<li><a
href="a63596ed5b"><code>a63596e</code></a>
Wrap action with a timeout.</li>
<li>See full diff in <a
href="https://github.com/namespacelabs/nscloud-cache-action/compare/v1.2.7...v1.2.8">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
The current documentation for actions are very sparse and would leave
someone (even including contributors) as to what exactly they do. On top
of that there are many stylistic and grammatical problems that are
simply no longer in line with our current standards, and certainly not
on par with our configuration options reference.
Hence, I've taken it upon myself to add, clarify, supplement, edit and
even rewrite the documentation for most of these actions, in a wider
effort of trying to offer better, clearer documentation for our users.
The current documentation for actions are very sparse and would leave
someone (even including contributors) as to what exactly they do.
On top of that there are many stylistic and grammatical problems that are
simply no longer in line with our current standards, and certainly not
on par with our configuration options reference.
Hence, I've taken it upon myself to add, clarify, supplement, edit and
even rewrite the documentation for most of these actions, in a wider
effort of trying to offer better, clearer documentation for our users.
`TabView` assumes to be the sole owner of all `Tab`s within a Window.
As such, it could close the managed `Window` once all tabs are removed
from its widget.
However, during `AdwTabView::close-page` signal triggered by libadwaita,
the `Tab` to be closed will gain an another reference for the duration
of the signal, breaking `TabView.closeTab` (called via
`Tab.closeWithConfirmation`) assumptions that having no tabs meant they
are all destroyed.
This commit solves the issue by scheduling `Tab.closeWithConfirmation`
to be run after `AdwTabView::close-page` signal has finished processing.
This is done to match against the default application id when Ghostty is
built using debug configuration, preparing the Flatpak version for D-Bus
activation support (#7433).
This is done to match against the default application id when Ghostty is
built using debug configuration, done to prepare the Flatpak version for
D-Bus activation support.
First, remove the always-inlined openTerminalFromPasteboard code and
combine it with openTerminal. Now that we're doing a bit of work inside
openTerminal, there's little better to having an intermediate, inlined
function.
Second, combine some type-casting operations (saving a .map() call).
Lastly, adjust some variable names because a generic `objs` or `urls`
was a little ambiguous now that we're all in one function scope.
This fixes a small memory leak I found where the `SplitNode.Leaf` was
not being deinitialized properly when closing a split. It would get
deinitialized the next time a split was made or the window was closed,
so the leak wasn't big. The surface view underneath the split was also
properly deinitialized because we forced it, so again, the leak was
quite small.
But conceptually this is a big problem, because when we change the
surface tree we expect the deinit chain to propagate properly through
the whole thing, _including_ to the SurfaceView.
This fixes that by removing the `id(node)` call. I don't find this to be
necessary anymore. I don't know when that happened but we've changed
quite a lot in our split system since it was introduced. I'm also not
100% sure why the `id(node)` was causing a strong reference to begin
with... which bothers me a bit.
AI note: While I manually hunted this down, I started up Claude Code and
Codex in separate tabs to also hunt for the memory leak. They both
failed to find it and offered solutions that didn't work.
This fixes a small memory leak I found where the `SplitNode.Leaf` was
not being deinitialized properly when closing a split. It would get
deinitialized the next time a split was made or the window was closed,
so the leak wasn't big. The surface view underneath the split was also
properly deinitialized because we forced it, so again, the leak was
quite small.
But conceptually this is a big problem, because when we change the
surface tree we expect the deinit chain to propagate properly through
the whole thing, _including_ to the SurfaceView.
This fixes that by removing the `id(node)` call. I don't find this to be
necessary anymore. I don't know when that happened but we've changed
quite a lot in our split system since it was introduced. I'm also not
100% sure why the `id(node)` was causing a strong reference to begin
with... which bothers me a bit.
AI note: While I manually hunted this down, I started up Claude Code and
Codex in separate tabs to also hunt for the memory leak. They both
failed to find it and offered solutions that didn't work.
Just a quick follow-up to #7462: I noticed Swift was admonishing me
about a `var` that could be a `let`, and realized that it was I who had
failed to make that change. Apologies for the noise.
This is the conversion of #7496 into a PR. The problem was discussed in
#3288: When `macos-titlebar-style = tabs`, the reset zoom button doesn't
appear with only a single tab, i.e., no tab bar.
This fix was simply to remove an unnecessary branch, as the titlebar
style doesn't make any difference for the required logic. If the tab bar
is visible, you should unconditionally hide the titlebar's button and
rely on the per-tab buttons. If the tab bar is not visible, you should
hide or show the titlebar button depending on `surfaceIsZoomed`.
One wrinkle was that the main toolbar title started showing on top of
the tabs. Turns out it was previously not showing only because it would
always be pushed into the hidden overflow menu. With the reset zoom
button present (either hidden or visible, but always there), this no
longer happens reliably, presumably due to some cascading change in the
overflow calculations. In any case, it seems brittle to rely on this way
of concealing the main title, so I added a few lines of code to properly
hide it when the tab bar is visible.
Screenshots:



Related to #7433
This extracts our "launched from desktop" logic into a config option.
The default value is detection using the same logic as before, but now
this can be overridden by the user.
This also adds the systemd and dbus activation sources from #7433.
There are a number of reasons why we decided to do this:
1. It automatically gets us caching since the configuration is only
loaded once (per reload, a rare occurrence).
2. It allows us to override the logic when testing. Previously, we had
to do more complex environment faking to get the same behavior.
3. It forces exhaustive switches in any desktop handling code, which
will make it easier to ensure valid behaviors if we introduce new launch
sources (as we are in #7433).
4. It lowers code complexity since callsites don't need to have N
`launchedFromX()` checks and can use a single value.
Related to #7433
This extracts our "launched from desktop" logic into a config option.
The default value is detection using the same logic as before, but now
this can be overridden by the user.
This also adds the systemd and dbus activation sources from #7433.
There are a number of reasons why we decided to do this:
1. It automatically gets us caching since the configuration is only
loaded once (per reload, a rare occurrence).
2. It allows us to override the logic when testing. Previously, we
had to do more complex environment faking to get the same
behavior.
3. It forces exhaustive switches in any desktop handling code, which
will make it easier to ensure valid behaviors if we introduce new
launch sources (as we are in #7433).
4. It lowers code complexity since callsites don't need to have N
`launchedFromX()` checks and can use a single value.
Fixes#6766
This ensures that during surface deinit the cgroup is removed. By the
time the surface is deinitialized, the subprocess should already be dead
so the cgroup can be safely removed. If the cgroup cannot be removed for
any reason we log a warning.
I'm still investigating whether we also need to remove the transient
cgroup we create per app but that's a lot less noisy since app startup
and shutdown is a lot more rare.
Fixes#6766
This ensures that during surface deinit the cgroup is removed. By the
time the surface is deinitialized, the subprocess should already be
dead so the cgroup can be safely removed. If the cgroup cannot be
removed for any reason we log a warning.
This should make the sorting more robust to fonts with questionable
metadata or atypical style names.
I was originally just going to change the scoring slightly to account
for fonts whose regular italic style is named "Regular Italic" - which
previously resulted in the Bold Italic or Thin Italic style being chosen
instead because they're shorter names, but I decided to do some better
inspection of the metadata and looser style name matching while I was
changing code here anyway.
Also adds a unit test to verify the sorting works correctly, though a
more comprehensive set of tests may be desirable in the future.
(Feel free to make any changes to the PR you feel necessary before
merging while I'm gone over the weekend.)
This improves "outer edge" alignment of octants and other elements drawn
using `yQuads` and friends with blocks drawn with `draw_block` -- this
should guarantee alignment along a continuous edge, but may result in a
1px overlap of opposing edges (such as a top half block followed by a
bottom half block with an odd cell height, they will both have the
center row filled).
This is very necessary since several block elements are needed to
complete the set of octants, since dedicated octant characters aren't
included when they would be redundant.
Fixes#7479
<details>
<summary><b><code>Box.ppm</code> diff</b></summary>

</details>
> [!NOTE]
> In the future I think we should have a unified single source of truth
for grid positions (divisions of the cell) to ensure this type of thing
can't happen with other characters, and also it would make a lot of the
code cleaner. For now this works though.
This improves "outer edge" alignment of octants and other elements drawn
using `yQuads` and friends with blocks drawn with `draw_block` -- this
should guarantee alignment along a continuous edge, but may result in a
1px overlap of opposing edges (such as a top half block followed by a
bottom half block with an odd cell height, they will both have the
center row filled).
This is very necessary since several block elements are needed to
complete the set of octants, since dedicated octant characters aren't
included when they would be redundant.
Fixes#7480
I regret to inform the haters that an AI agent did this while I was out
getting a coffee. I only had to follow up once to ask it rename the
method to be more idiomatic. I absolutely didn't need AI to do this, but
I was leaving the office _anyways_ and figured why not. Got back and it
was done. What a time to be alive.
I'm not sure if this should be enabled by default like the tab
animation, but on KDE at least this is unintrusive enough for me to
always enable by default. Alacritty appears to agree with me as well.
Fixes#7124
This should make the sorting more robust to fonts with questionable
metadata or atypical style names.
I was originally just going to change the scoring slightly to account
for fonts whose regular italic style is named "Regular Italic" - which
previously resulted in the Bold Italic or Thin Italic style being chosen
instead because they're shorter names, but I decided to do some better
inspection of the metadata and looser style name matching while I was
changing code here anyway.
Also adds a unit test to verify the sorting works correctly, though a
more comprehensive set of tests may be desirable in the future.
Fixes#7481
We unfortunately don't have a great way to unit test this since our
logic relies on argv and I'm too lazy to extract it out right now.
The core issue is that we previously detected if font-families changed
by comparing lengths. This doesn't work because the CLI args can reset
and add families back to a lesser length. This caused an integer
overflow.
We can fix this by not being clever and introducing the overwrite logic
directly into the config type. I unit tested that.
Fixes#7481
We unfortunately don't have a great way to unit test this since our
logic relies on argv and I'm too lazy to extract it out right now.
The core issue is that we previously detected if font-families changed
by comparing lengths. This doesn't work because the CLI args can reset
and add families back to a lesser length. This caused an integer
overflow.
We can fix this by not being clever and introducing the overwrite logic
directly into the config type. I unit tested that.
I'm not sure if this should be enabled by default like the tab
animation, but on KDE at least this is unintrusive enough for me to
always enable by default. Alacritty appears to agree with me as well.
Even though gtk4-layer-shell's documentation claims that "nobody quite
knows what it's for", some compositors (like Niri) can define custom
rules based on the layer name and it's beneficial in those cases to
define a distinct name just for our quick terminals.
The default keybinds for showing the GTK inspector (`ctrl+shift+i` and
`ctrl+shift+d`) don't work reliably in Ghostty due to the way Ghostty
handles input. You can show the GTK inspector by setting the environment
variable `GTK_DEBUG` to `interactive` before starting Ghostty but that's
not always convenient.
This adds a keybind action that will show the GTK inspector. Due to API
limitations toggling the GTK inspector using the keybind action is
impractical because GTK does not provide a convenient API to determine
if the GTK inspector is already showing. Thus we limit ourselves to
strictly showing the GTK inspector. To close the GTK inspector the user
must click the close button on the GTK inspector window. If the GTK
inspector window is already visible but is hidden, calling the keybind
action will not bring the GTK inspector window to the front.
This brings the behavior of mode 47, 1047, and 1049 much closer to
xterm's behavior. I found that our prior implementation had many
deficiencies.
For example, we weren't properly copying the cursor state back to the
primary screen from the alternate screen for modes 47 and 1047. And we
weren't saving/restoring cursor state unconditionally for mode 1049 even
if we were already in the alternate screen.
I also found that we were dangling hyperlink state on the primary screen
when we switched to alternate. xterm doesn't support hyperlinks but we
did the opposite behavior for going from alternate to primary. So one
way or the other its a bug. I'm worried this one could've maybe led to
memory corruption under the right circumstances but I wasn't able to
prove it.
These are weird, edgy behaviors that I don't think anyone expected
(evidence by there being no bug reports about them), but they are bugs
nontheless.
Many tests added.
(Btw: this flew under the radar of our "xterm audit" because that only
included sequences and not modes. I noted this in the audit issue itself
but just an FYI.)
Related to #7468
This changes the behavior of "ignore". Previously, Ghostty would
consider "ignore" actions consumed but do nothing. They were like a
black hole. Now, Ghostty returns `ignored` which lets the apprt forward
the event to the OS/GUI.
This enables keys that would otherwise be pty-encoded to be processed
later, such as for GTK to show the GTK inspector.
Related to #7468
This changes the behavior of "ignore". Previously, Ghostty would
consider "ignore" actions consumed but do nothing. They were like a
black hole. Now, Ghostty returns `ignored` which lets the apprt forward
the event to the OS/GUI.
This enables keys that would otherwise be pty-encoded to be processed
later, such as for GTK to show the GTK inspector.
This brings the behavior of mode 47, 1047, and 1049 much closer to
xterm's behavior. I found that our prior implementation had many
deficiencies.
For example, we weren't properly copying the cursor state back to the
primary screen from the alternate screen for modes 47 and 1047. And we
weren't saving/restoring cursor state unconditionally for mode 1049 even
if we were already in the alternate screen.
These are weird, edgy behaviors that I don't think anyone expected
(evidence by there being no bug reports about them), but they are bugs
nontheless.
Many tests added.
This fixes an issue where Ghostty would not build against the macOS 15.5
SDK.
What was happening was that Zig was adding its embedded libc paths to
the clang command line, which included old headers that were
incompatible with the latest (macOS 15.5) SDK. Ghostty was adding the
newer paths but they were being overridden by the embedded libc paths.
The reason this was happening is because Zig was using its own logic to
find the libc paths and this was colliding with the paths we were
setting manually. To fix this, we now use a `libc.txt` file that
explicitly tells Zig where to find libc, and we base this on our own SDK
search logic.
This fixes an issue where Ghostty would not build against the macOS 15.5 SDK.
What was happening was that Zig was adding its embedded libc paths to
the clang command line, which included old headers that were
incompatible with the latest (macOS 15.5) SDK. Ghostty was adding the
newer paths but they were being overridden by the embedded libc paths.
The reason this was happening is because Zig was using its own logic to
find the libc paths and this was colliding with the paths we were
setting manually. To fix this, we now use a `libc.txt` file that
explicitly tells Zig where to find libc, and we base this on our own SDK
search logic.
The default keybinds for showing the GTK inspector (`ctrl+shift+i` and
`ctrl+shift+d`) don't work reliably in Ghostty due to the way Ghostty
handles input. You can show the GTK inspector by setting the environment
variable `GTK_DEBUG` to `interactive` before starting Ghostty but that's
not always convenient.
This adds a keybind action that will show the GTK inspector. Due to
API limitations toggling the GTK inspector using the keybind action is
impractical because GTK does not provide a convenient API to determine
if the GTK inspector is already showing. Thus we limit ourselves to
strictly showing the GTK inspector. To close the GTK inspector the user
must click the close button on the GTK inspector window. If the GTK
inspector window is already visible but is hidden, calling the keybind
action will not bring the GTK inspector window to the front.
Fixes#7215. The issue was that `GlobalEventTap` didn't fully initialize
the key event before passing it to `ghostty_app_key`. In particular, it
didn't set the `unshifted_codepoint` field, which is needed to find a
match when calling `keybind.set.getEvent`, because `keybind.set` stores
bindings by codepoint, not physical key (i.e., the trigger to match is
`ctrl+p`, not `ctrl+key_p`, et cetera).
The fix was to make `GlobalEventTap` initialize the key event using
`event.ghosttyKeyEvent`, like it's done in `SurfaceView.keyAction` and
`AppDelegate.localEventKeyDown`.
This is my first time touching either Zig or Swift, so I'm a bit green.
In particular, I don't know if it's possible to write a test covering
this, and if so, how and where to put it. Please edit or request changes
as you see fit.
As of Zig 0.14.0, `@splat` can be used for array types, which eliminates
a lot of redundant syntax and makes things generally cleaner.
I've explicitly avoided applying this change in the renderer files for
now since it would just create rebasing conflicts in my renderer rework
branch which I'll be PR-ing pretty soon.
As of Zig 0.14.0, `@splat` can be used for array types, which eliminates
a lot of redundant syntax and makes things generally cleaner.
I've explicitly avoided applying this change in the renderer files for
now since it would just create rebasing conflicts in my renderer rework
branch which I'll be PR-ing pretty soon.
This PR adds support for handling the escape sequences CSI ? 47 h/l,
which are related to alternate screen buffer switching. This is in
response to the issue reported in
[ghostty-org/ghostty#7386](https://github.com/ghostty-org/ghostty/issues/7386).
I'm unsure where to add tests for this change. Would it make sense to
add visual tests for this behavior, or is there a preferred approach or
location for testing such functionality? I tested it locally using the
following commands:
```bash
echo -e "\e[?47h"
echo "Printed on the alt screen"
echo -e "\e[?47l"
```
This PR fixes the problem discussed in #5058 and #7434 by reworking the
selection logic in a way that better handles edge cases as well as being
generally cleaner.
This rework does change how selection behaves slightly, especially
rectangular selection, but the new behavior of rectangular selection is
more in line with other terminals I tested (Terminal.app, Kitty).
There are some TODO comments for adding unit tests- I ran out of steam
tonight, but if this PR is still open tomorrow I'll go ahead and add
them.
Adds many test cases for expected behavior of the selection logic, this
will allow changes to be made more confidently in the future without
fear of regressions.
This logic is cleaner and produces better behavior when selecting by
dragging the mouse outside the bounds of the surface, previously when
doing this on the left side of the surface selections would include the
first cell of the next row, this is no longer the case.
This introduces methods on PageList.Pin which move a pin left or right
while wrapping to the prev/next row, or clamping to the ends of the row.
These need unit tests.
See commit messages for details.
If some of the commits in this PR have problems with them I'm perfectly
fine with the others being cherry-picked out while the problems are
addressed.
The biggest/broadest reaching changes in this PR come from converting a
lot of code to use decl literals where possible, so there are a lot of
files where only a handful of lines are modified very slightly.
Update to Nix 25.05 which gets us GTK 4.18, libadwaita 1.7, and Zig
0.14.1.
Since Nix updated to Zig 0.14.1, the devshell has been switched to Zig
0.14.1 from zig-overlay as well.
Fixes#7305
Ghostty doesn't support `$XDG_CONFIG_DIR`, it only supports
`$XDG_CONFIG_HOME`, so the documentation was incorrect. Not to be
confused with `$XDG_CONFIG_DIRS` nor `$XDG_RUNTIME_DIR` (very
consistent…).
Furthermore, `$XDG_CONFIG_HOME` is the correct XDG path:
> <ins>$XDG_CONFIG_HOME</ins> defines the base directory relative to
which user-specific configuration files should be stored. If
<ins>$XDG_CONFIG_HOME</ins> is either not set or empty, a default equal
to <ins>$HOME</ins>/.config should be used.
> […]
> <ins>$XDG_CONFIG_DIRS</ins> defines the preference-ordered set of base
directories to search for configuration files in addition to the
<ins>$XDG_CONFIG_HOME</ins> base directory. The directories in
<ins>$XDG_CONFIG_DIRS</ins> should be separated with a colon ':'.
— [XDG Base Directory Specification § Environment variables][xdgvars]
Cross-reference:
https://github.com/ghostty-org/ghostty/discussions/7431#discussioncomment-13283139.
[xdgvars]:
https://specifications.freedesktop.org/basedir-spec/latest#variables
This commit changes a LOT of areas of the code to use decl literals
instead of redundantly referring to the type.
These changes were mostly driven by some regex searches and then manual
adjustment on a case-by-case basis.
I almost certainly missed quite a few places where decl literals could
be used, but this is a good first step in converting things, and other
instances can be addressed when they're discovered.
I tested GLFW+Metal and building the framework on macOS and tested a GTK
build on Linux, so I'm 99% sure I didn't introduce any syntax errors or
other problems with this. (fingers crossed)
Cleaner and less visual noise, easy change to make, there are many other
areas in the code which would benefit from decl literals as well, but
this is an area that benefits a lot from them and is self-contained.
Update to Nix 25.05 which gets us GTK 4.18, libadwaita 1.7, and Zig 0.14.1.
Since Nix updated to Zig 0.14.1, the devshell has been switched to Zig 0.14.1
from zig-overlay as well.
Fixes#7305
'g_application_id_is_valid' doesn't allow empty elements or elements
that start with digits.
This commit updates 'isValidAppId' to be more consistant with
'g_application_id_is_valid' avoiding the app id defaulting to 'GTK
Application' for app ids like '0foo.bar' or 'foo..bar'.
This change changes the wording on the split pane functionality. The new
wording is taken from the macOS terminal app when the whole system is
translated to Norwegian.
macOS uses "Del opp vindu" and "Lukk delt vindu" for "Split Pane" and
"Close Split Pane". So instead of using "split" the verb in question is
always "del". Personally I find this translation to be better rooted in
Norwegian.
When looking at the German translation, which is often a good indicator
for Norwegian as well, one can see the same wording being used.
'g_application_id_is_valid' doesn't allow empty elements or elements
that start with digits.
This commit updates 'isValidAppId' to be more consistant with
'g_application_id_is_valid' avoiding the app id defaulting to 'GTK
Application' for app ids like '0foo.bar' or 'foo..bar'.
The origin of these keys are old sun keyboards.
They are getting picked up by the custom (progammable) keyboard scene
(see https://github.com/manna-harbour/miryoku for a popular layout).
Support in ghosty is quite handy because it allows to bind copy/paste in
a way that doesn't overlap with ctrl-c/ctrl-v, which can have special
bindings in some terminal applications.
The origin of these keys are old sun keyboards.
They are getting picked up by the custom (progammable) keyboard scene
(see https://github.com/manna-harbour/miryoku for a popular layout).
Support in ghosty is quite handy because it allows to bind copy/paste in
a way that doesn't overlap with ctrl-c/ctrl-v, which can have special
bindings in some terminal applications.
This introduces a new package `src/synthetic` for generating synthetic
data, currently primarily for benchmarking but other use cases can
emerge.
The synthetic package exports a runtime-dispatched type `Generator` that
can generate data of various types. To start, we have a bytes, utf8, and
OSC generator. The goal of each generator is to expose knobs to tune the
probabilities of various outcomes. For example, the UTF-8 generator has
a knob to tune the probability of generating 1, 2, 3, or 4-byte UTF-8
sequences.
Ultimately, the goal is to be able to collect probability data
empirically that we can then use for benchmarks so we can optimize
various parts of the codebase on real-world data shape distributions.
This change changes the wording on the split pane functionality. The new
wording is taken from the macOS terminal app when the whole system is
translated to Norwegian.
macOS uses "Del opp vindu" and "Lukk delt vindu" for "Split Pane" and
"Close Split Pane". So instead of using "split" the verb in question is
always "del". Personally I find this translation to be better rooted in
Norwegian.
When looking at the German translation, which is often a good indicator
for Norwegian as well, one can see the same wording being used.
Bumps [cachix/cachix-action](https://github.com/cachix/cachix-action)
from 15 to 16.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cachix/cachix-action/releases">cachix/cachix-action's
releases</a>.</em></p>
<blockquote>
<h2>v16</h2>
<h2>What's Changed</h2>
<ul>
<li>Add a small delay to allow post-build hooks to flush through by <a
href="https://github.com/sandydoo"><code>@sandydoo</code></a> in <a
href="https://redirect.github.com/cachix/cachix-action/pull/196">cachix/cachix-action#196</a></li>
<li>Upgraded dependencies</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/cachix/cachix-action/compare/v15...v16">https://github.com/cachix/cachix-action/compare/v15...v16</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="0fc020193b"><code>0fc0201</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/cachix-action/issues/201">#201</a>
from cachix/bump-deps</li>
<li><a
href="9ff160dfdf"><code>9ff160d</code></a>
dist: build</li>
<li><a
href="43208f1165"><code>43208f1</code></a>
deps: pnpm update</li>
<li><a
href="177fc8ba87"><code>177fc8b</code></a>
ci: run private cache tests when secrets are available</li>
<li><a
href="9bb3a15fdb"><code>9bb3a15</code></a>
deps: devenv update</li>
<li><a
href="be5295a636"><code>be5295a</code></a>
Merge pull request <a
href="https://redirect.github.com/cachix/cachix-action/issues/197">#197</a>
from cachix/dev-restructure</li>
<li><a
href="dbbedb8579"><code>dbbedb8</code></a>
ci: split build and test scripts</li>
<li><a
href="a3f805d988"><code>a3f805d</code></a>
ci: build once with devenv</li>
<li><a
href="c48cfdb2ec"><code>c48cfdb</code></a>
ci: switch out yarn</li>
<li><a
href="9af815036c"><code>9af8150</code></a>
dist: rebuild</li>
<li>Additional commits viewable in <a
href="https://github.com/cachix/cachix-action/compare/v15...v16">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
This introduces a new package `src/synthetic` for generating synthetic
data, currently primarily for benchmarking but other use cases can
emerge.
The synthetic package exports a runtime-dispatched type `Generator` that
can generate data of various types. To start, we have a bytes, utf8,
and OSC generator. The goal of each generator is to expose knobs to tune the
probabilities of various outcomes. For example, the UTF-8 generator has
a knob to tune the probability of generating 1, 2, 3, or 4-byte UTF-8
sequences.
Ultimately, the goal is to be able to collect probability data
empirically that we can then use for benchmarks so we can optimize
various parts of the codebase on real-world data shape distributions.
Without --device=all, the sandbox gets a dedicated PTY namespace.
Commands run on the host via the HostCommand D-Bus interface receive the
file descriptors from the namespaced PTY but cannot determine its path
via ttyname(3). This breaks commands like tty(1), ps(1) and
emacsclient(1).
Add --device=all so the host PTY namespace is used when allocating TTYs.
Applications with access to org.freedesktop.Flatpak can already give
themselves arbitrary permissions, so the sandboxing benefits of
restricted device access are limited. For terminal emulators, the
consistency provided by a cross-distribution runtime and ability to
distribute directly to users is the primary benefit of shipping as a
Flatpak rather than sandboxing.
Without --device=all, the sandbox gets a dedicated PTY namespace.
Commands run on the host via the HostCommand D-Bus interface receive the
file descriptors from the namespaced PTY but cannot determine its path
via ttyname(3). This breaks commands like tty(1), ps(1) and
emacsclient(1).
Add --device=all so the host PTY namespace is used when allocating TTYs.
Applications with access to org.freedesktop.Flatpak can already give
themselves arbitrary permissions, so the sandboxing benefits of
restricted device access are limited. For terminal emulators, the
primary benefit of Flatpak is the predictability of the
distro-independent target runtime rather than sandboxing.
This fixes an issue where stack traces were unreliable on some platforms
(namely aarch64-linux in a MacOS VM). I'm unsure if this is a bug in Zig
(defaults should be changed?) or what, because this isn't necessary on
other platforms, but this works around the issue.
I've unconditionally enabled this for all platforms, depending on build
mode (debug/test) and not the target. This is because I don't think
there is a downside for other platforms but if thats wrong we can fix
that quickly.
Some binaries have this unconditionally enabled regardless of build mode
(e.g. the Unicode tables generator) because having symbols in those
cases is always useful.
Some unrelated GTK test fix is also included here. I'm not sure why CI
didn't catch this (perhaps we only run tests for none-runtime) but all
tests pass locally and we can look into that elsewhere.
I also updated all our build API calls to be non-deprecated fields.
This fixes an issue where stack traces were unreliable on some platforms
(namely aarch64-linux in a MacOS VM). I'm unsure if this is a bug in Zig
(defaults should be changed?) or what, because this isn't necessary on
other platforms, but this works around the issue.
I've unconditionally enabled this for all platforms, depending on build
mode (debug/test) and not the target. This is because I don't think
there is a downside for other platforms but if thats wrong we can fix
that quickly.
Some binaries have this unconditionally enabled regardless of build mode
(e.g. the Unicode tables generator) because having symbols in those
cases is always useful.
Some unrelated GTK test fix is also included here. I'm not sure why CI
didn't catch this (perhaps we only run tests for none-runtime) but all
tests pass locally and we can look into that elsewhere.
Fixes#7392
Docs:
> Whether to clear selected text when typing. This defaults to `true`.
> This is typical behavior for most terminal emulators as well as
> text input fields. If you set this to `false`, then the selected text
> will not be cleared when typing.
>
> "Typing" is specifically defined as any non-modifier (shift, control,
> alt, etc.) keypress that produces data to be sent to the application
> running within the terminal (e.g. the shell). Additionally, selection
> is cleared when any preedit or composition state is started (e.g.
> when typing languages such as Japanese).
>
> If this is `false`, then the selection can still be manually
> cleared by clicking once or by pressing `escape`.
Fixes#7392
Docs:
> Whether to clear selected text when typing. This defaults to `true`.
> This is typical behavior for most terminal emulators as well as
> text input fields. If you set this to `false`, then the selected text
> will not be cleared when typing.
>
> "Typing" is specifically defined as any non-modifier (shift, control,
> alt, etc.) keypress that produces data to be sent to the application
> running within the terminal (e.g. the shell). Additionally, selection
> is cleared when any preedit or composition state is started (e.g.
> when typing languages such as Japanese).
>
> If this is `false`, then the selection can still be manually
> cleared by clicking once or by pressing `escape`.
It's been a lot of D-Bus related pain and suffering, but here it is.
I'm not sure about how well this is integrated inside App, but I'm fairly
proud of the standalone logic.
Fixes#7286
Previously, when using the "New Ghostty Window/Tab Here" macOS service
on a file, the new terminal window/tab would incorrectly open in the
user's home directory. This was because the service handler only
expected directory paths.
This commit updates the service handler to check if the provided path is
a file. If it is, the handler now uses the file's parent
directory as the working directory for the new Ghostty window or tab,
aligning with user expectations. If the path is a directory, it's used
directly as before.
cc @qwerasd205
This commit adds a few new mode flags to the `bench-stream` program to
generator synthetic OSC sequences. The new modes are `gen-osc`,
`gen-osc-valid`, and `gen-osc-invalid`. The `gen-osc` mode generates
equal parts valid and invalid OSC sequences, while the suffixed variants
are for generating only valid or invalid sequences, respectively.
This commit also fixes our build system to actually be able to build the
benchmarks. It turns out we were just rebuilding the main Ghostty binary
for `-Demit-bench`. And, our benchmarks didn't run under Zig 0.14, which
is now fixed.
An important new design I'm working towards in this commit is to split
out synthetic data generation to a dedicated package in
`src/bench/synth` although I'm tempted to move it to `src/synth` since
it may be useful outside of benchmarks.
The synth package is a work-in-progress, but it contains a hint of
what's to come. I ultimately want to able to generate all kinds of
synthetic data with a lot of knobs to control dimensionality (e.g. in
the case of OSC sequences: valid/invalid, length, operation types,
etc.).
Fixes the gamma error noted in #7367 for both Metal and OpenGL by using
sRGB image formats for the textures.
This branch also includes the commits from #7367, so it'd probably be
best to review and merge that first.
This problem was introduced by f091a69 (PR #6675).
I've gone ahead and overhauled the placement positioning logic as well;
it was doing a lot of expensive calls before, I've significantly reduced
that.
Clipping partially off-screen images is now handled entirely by the
renderer, rather than while preparing the placement, and as such the
grid position passed to the image shader is now signed.
Also fixes crashes in both vanilla GTK and Adwaita implementations of
`closeTab`, which erroneously close windows twice when there are no
more tabs left (we probably already handle it somewhere else).
This commit adds a few new mode flags to the `bench-stream` program
to generator synthetic OSC sequences. The new modes are `gen-osc`,
`gen-osc-valid`, and `gen-osc-invalid`. The `gen-osc` mode generates
equal parts valid and invalid OSC sequences, while the suffixed variants
are for generating only valid or invalid sequences, respectively.
This commit also fixes our build system to actually be able to build the
benchmarks. It turns out we were just rebuilding the main Ghostty binary
for `-Demit-bench`. And, our benchmarks didn't run under Zig 0.14, which
is now fixed.
An important new design I'm working towards in this commit is to split
out synthetic data generation to a dedicated package in
`src/bench/synth` although I'm tempted to move it to `src/synth` since
it may be useful outside of benchmarks.
The synth package is a work-in-progress, but it contains a hint of
what's to come. I ultimately want to able to generate all kinds of
synthetic data with a lot of knobs to control dimensionality (e.g. in
the case of OSC sequences: valid/invalid, length, operation types,
etc.).
Fixes#7337
AppKit encodes functional keys as PUA codepoints. We don't want to send
that down as valid text encoding for a key event because KKP uses that
in particular to change the encoding with associated text.
I think there may be a more specific solution to this by only doing this
within the KKP encoding part of KeyEncoder but that was filled with edge
cases and I didn't want to risk breaking anything else.
Fixes#7337
AppKit encodes functional keys as PUA codepoints. We don't want to send
that down as valid text encoding for a key event because KKP uses that
in particular to change the encoding with associated text.
I think there may be a more specific solution to this by only doing this
within the KKP encoding part of KeyEncoder but that was filled with edge
cases and I didn't want to risk breaking anything else.
Fixes#7315Fixes#7314Fixes#7310Fixes#7309
**This PR has breaking changes, noted later.**
This changes our internal key events to match the [W3C key event code
specification](https://www.w3.org/TR/uievents-code/). With these
changes, apprts are expected to produce key codes within that spec and
everything follows from there. This gives us a standard cross-platform
keycode behavior.
Previously, due to our history, we based our key codes on GLFW and Linux
more generally (since Ghostty was on Linux first). This served us
surprisingly well through today, but has been riddled with various
issues (many fixed, but note how many bugs this PR is fixing). From a
user experience perspective, it also caused confusion about how
Ghostty's keybinds interact with keyboard layouts which led to various
complicated features like `physical:` (which is removed in this PR).
## Overview of Changes
* `physical:` is now gone. Physical keys are now specified by the W3C
key codes. Example: `ctrl+KeyA` will always match the "a" key on a US
physical layout (the name `KeyA` lining up with US keyboards is mandated
by the spec, not us). Note when we say "physical" here we mean the
keycode sent by the OS or GUI framework; these can often be overridden
using programs to remap keys at the "hardware" level but software
layouts do not do this.
* All single codepoint characters match the character produced by the
keyboard layout (i.e. are layout-dependent). So `ctrl+c` matches the
physical "c" key on a US standard keyboard with a US layout, but matches
the "i" key on a Dvorak layout. This also works for international
characters. Codepoints are case-insensitive and match via Unicode case
folding (this is how both Windows and macOS treat keyboard shortcuts).
* W3C key names replace all previous non-character names and always
match _physical keys_. For example, `ctrl+grave_accent` (Ghostty 1.1x)
is now `ctrl+backquote` to match the W3C spec. This PR maintains
backwards compatibility so the old values are aliased to the new values.
* W3C key names can be both snake case and match the spec exactly. e.g.
`KeyA` and `key_a` are both valid. This is an aesthetic choice, because
I think the capitalization is really ugly. We have tests to verify this
mapping so its officially supported.
* Because we support W3C keys, we now support significantly more media
keys such as `context_menu`, `browser_home` and so many more. These work
as long as your OS and keyboard can produce the valid key codes.
## Breaking Changes
* Key names have changed, e.g. `grave_accent` is now `backquote`. The
new names are all W3C standard. There are backwards compatible aliases,
so **old configurations will still work.** But the `+list-keybinds` CLI
will always output the new versions.
* `physical:` is gone, all W3C names are always physical and all code
points are always logical. There are backwards compatible aliases for
unambiguous keys, so **most old configurations will still work.** If
there is ambiguity, old configurations will error, but I don't expect
this to be common at all.
* Physical keybinds always take priority over logical keybinds. This was
_inconsistent_ before. Physical keybinds are relatively rare so I don't
think this will impact people but just noting this is not an explicit,
tested, documented behavior.
Related to #7316
I'm not going to close the issue yet because I want to also update the
website, but this should help clarify things more. I noticed the README
already makes a distinction, thankfully.
This allows for `keybind = super+d=new_split` to now work (defaults to
"auto"). This will also let us convert void types to union/enum/struct
types in the future without breaking existing bindings.
This allows for `keybind = super+d=new_split` to now work (defaults to
"auto"). This will also let us convert void types to union/enum/struct
types in the future without breaking existing bindings.
Fixes#7236
Supersedes #7249
This removes all of our `focusedValue`-based tracking of the surface
title and moves it completely to the window controller. The window
controller now sets up event listeners (via Combine) when the focused
surface changes and updates the window title accordingly.
There is some complicated logic here to handle when we lose focus to
something other than a surface. In this case, we want our title to be
the last focused surface so long as it exists.
The prior `focusedValue`-based code was a relic of when Ghostty was a
full SwiftUI app. Now that we have AppKit in the mix (and have for a
couple years), we shouldn't bolt on fixes to that cruft if we can help
it.
Fixes#7236
Supersedes #7249
This removes all of our `focusedValue`-based tracking of the surface
title and moves it completely to the window controller. The window
controller now sets up event listeners (via Combine) when the focused
surface changes and updates the window title accordingly.
There is some complicated logic here to handle when we lose focus to
something other than a surface. In this case, we want our title to be
the last focused surface so long as it exists.
Changes:
1. Require `blueprint-compiler` 0.16.0 (or newer) for building from a
git checkout. With #6822 distributions that can't meet that requirement
can use generated source tarballs to build.
2. Remove all `.ui` files as they are unnecessary.
3. Simplify the `Builder` interface since raw `.ui` files are no longer
used.
4. Removed build-time check of raw `.ui` files.
Implements path access testing for Flatpak via test spawning. This is
required since Flatpak reserves certain paths from being accessible
regardless of permissions.
Opened mostly as an RFC, this approach seems to work without any
noticeable performance impact.
Ref:
https://docs.flatpak.org/en/latest/sandbox-permissions.html#reserved-paths
Fixes#6999
Supersedes #7201
It appears that at some point one of the operations causes focus to move
away for non-native fullscreen. We previously relied on the delegate
method to restore this but a better approach appears to handle this
directly in the fullscreen implementations. This fixes the linked issue.
I still think long term all the `Ghostty.moveFocus` stuff is a code
smell and we should be auditing all that code to see if we can eliminate
it. But this is a step in the right direction, and removes one of those
uses.
Fixes#6999
It appears that at some point one of the operations causes focus to move
away for non-native fullscreen. We previously relied on the delegate
method to restore this but a better approach appears to handle this
directly in the fullscreen implementations. This fixes the linked issue.
I still think long term all the `Ghostty.moveFocus` stuff is a code
smell and we should be auditing all that code to see if we can
eliminate it. But this is a step in the right direction, and removes one
of those uses.
Fixes#7114
Supercedes #7271
This fixes a crash that could occur with non-native fullscreen and
`fullscreen = true` set at once.
The "windowNumber" can be `<= 0` if the window "doesn't have a window
device." I don't fully know all the scenarios this is true but it is
true when the window is not visible, at least.
Fixes#7114
Supercedes #7271
This fixes a crash that could occur with non-native fullscreen and
`fullscreen = true` set at once.
The "windowNumber" can be `<= 0` if the window "doesn't have a
window device." I don't fully know all the scenarios this is true but it
is true when the window is not visible, at least.
tl;dr: FT_New_Face and FT_Done_Face require the Library to be locked for
thread safety, and FT_Load_Glyph and FT_Render_Glyph and friends need
the face to be locked for thread safety, since we're sharing faces
across threads.
For details see comments and FreeType docs @
-
https://freetype.org/freetype2/docs/reference/ft2-library_setup.html#ft_library
-
https://freetype.org/freetype2/docs/reference/ft2-face_creation.html#ft_face
---
This might resolve the issue discussed in #7016 but I wasn't able to
reliably reproduce it in a way I could debug, so someone who was
experiencing it should probably test this PR.
Additionally I can still semi-reliably produce a crash with the GTK
apprt by setting an `all:` keybind to adjust font sizes and changing the
font size rapidly with many surfaces open with emojis on them.
Unfortunately I can't really tell what the root cause is because the
debug info is broken in QEMU.
However, I do think this is a good idea for us to be thread safe with
this stuff even if it isn't related to that problem.
Prior to #7044, on macOS, our shell integrated command line would be
executed under `exec -l`, which caused bash to be started as a login
shell. Now that we're using direct command execution, add `--login` to
our bash command's arguments on macOS to get that same behavior.
After some time with the initial template and Discussions being created,
there are some improvements we should make to the Issue Triage template.
Most of these changes were discussed in #7012 and among helpers in the
Discord.
~~I've marked this as a Draft for now as I work with the helpers to
confirm how we want to approach a few of the outstanding changes, mostly
minor updates.~~
As always, you can test these out by [opening an Issue Triage Discussion
post on my
fork](https://github.com/taylrfnt/ghostty/discussions/new?category=issue-triage).
Prior to #7044, on macOS, our shell integrated command line would be
executed under `exec -l`, which caused bash to be started as a login
shell. Now that we're using direct command execution, add `--login` to
our bash command's arguments on macOS to get that same behavior.
Recently when answering [a Discussion], I was reminded of when Tristan
once linked the GTK CSS documentation and said “Perhaps good to add this
to the docs of the GTK custom CSS config option”, so I decided to do
that now. I also added a few random things that I found helpful when
attempting to modify Ghostty's CSS; this information was mostly stolen
from random people on Discord or accidentally discovered.
I really do not care if this is merged or not, nor do I care if the
strings are changed considerably[^1], so I am going straight for a Pull
Request without bothering to open a Discussion, get that converted to an
Issue after a few years, then finally remember to open a Pull Request.
I only tested what this looks like in `ghostty +show-config --default
--docs`, the manpage and the HTML output; I notably did not try seeing
how it renders on the website. The links have to be in angle brackets
for the HTML output to have it rendered as URLs, but it looks odd
everywhere else; manpages have them with mathematical angle brackets,
⟨like this⟩. I also refrained from using an em (—) or en (–) dash
instead of a normal dash (-) as that does not seem to be common in the
rest of the documentation.
[a Discussion]: https://github.com/ghostty-org/ghostty/discussions/7189
[^1]: I didn't see any guidelines or standards for these strings, so
presumably these would be contested as I didn't know what to adhere to
when writing them.
XTGETTCAP queries are a semicolon-delimited list of hex encoded terminfo
capability names. Ghostty encodes a map using upper case hex encodings,
meaning when an application uses a lower case encoding the capability is
not found. To fix, we convert the entire list we receive in the query to
upper case prior to processing further.
Fixes: #7229
XTGETTCAP queries are a semicolon-delimited list of hex encoded terminfo
capability names. Ghostty encodes a map using upper case hex encodings,
meaning when an application uses a lower case encoding the capability is
not found. To fix, we convert the entire list we receive in the query to
upper case prior to processing further.
Fixes: #7229
It may not be immediately obvious how to style Ghostty despite knowing
of the existence of that configuration option; one who is more
accustomed to web development would likely be very reliant on their
browser's inspector for modifying and debugging the style of their
application. GTK CSS also differs in some important ways from the CSS
found in browsers, and hence linking to the GTK CSS documentation would
save time for anyone new to styling GTK applications.
C bindings to expose the color palette to Swift for macOS. Returns the
full 256 colors from the current color scheme. After fetching the
palette with `ghostty_config_get`, you can access the desired color by
its index in the list.
### Usage
Here is one way to get the palette in Swift.
```swift
import GhosttyKit
private(set) var config: ghostty_config_t? = nil {
didSet {
// Free the old value whenever we change
guard let old = oldValue else { return }
ghostty_config_free(old)
}
}
var paletteColors: [Color] {
var paletteList: ghostty_config_palette_s = .init()
let key = "palette"
if (!ghostty_config_get(config, &paletteList, key, UInt(key.count))) {
return []
}
var colors: [Color] = []
let mirror = Mirror(reflecting: paletteList.colors)
for (_, element) in mirror.children {
if let color = element as? ghostty_config_color_s {
colors.append(Color(
red: Double(color.r) / 255,
green: Double(color.g) / 255,
blue: Double(color.b) / 255
))
}
}
print("Palette Colors: ", colors)
return colors
}
```
Result (GruvboxDarkHard theme)

version 0.6.0 of z2d has a security vulnerability and the author
suggests upgrading (randomly saw this in the zig discord)
https://github.com/vancluever/z2d/security/advisories/GHSA-mm4c-p35v-7hx3
I don't know enough about how ghostty uses to it to say if it could be
affected
also updates that Context no longer returns an error type this says it
was in the 0.6.0 changes which ghostty was already pinned to (it seems
like that the tag might have been updated or something im not sure) you
can see it listed as a change in 0.6.0 below
https://github.com/vancluever/z2d/blob/main/CHANGELOG.md
I'm not sure if any other changes are required I ran a `zig build` and
`zig build test` and things seem to be fine on my end
~~I'm not sure I updated the zon nix stuff correctly I just ran the
command in jcollie readme
`nix run github:jcollie/zon2nix#zon2nix -- --nix=build.zig.zon.nix
build.zig.zon`~~ looks like I should have just ran the script
ps: sorry for the direct ping mitchell (I thought this might be
important that you look at it)
Resolve 1. of #7173
<img width="1126" alt="image"
src="https://github.com/user-attachments/assets/8904b09f-42f6-4f26-a722-c92dad8e2933"
/>
Changes made:
1. Change shortcut from `String` to `[String]` so its easier to iterate
over it.
2. Overlay background color on top of an `ultraThinMaterial` for better
aesthetic.
3. Reorganize and beautify the spacings and paddings.
4. Unhide the scrollbar.
5. Reorder the modifier keys to Control, Option, Shift and then Command.
<https://leancrew.com/all-this/2017/11/modifier-key-order/>
6. Style shortcut keys to resemble macOS menu bar items, using
corresponding symbols and fixed-width for alignment.
#7173
(1) The command palette no longer has any selection by default. If and
when we introduce most recently used commands, defaulting to that would
make sense. A selection only appears when the arrow keys are used or the
user starts typing.
(2) The selection with arrow keys now wraps, so if you press "down" on
the last option, it will wrap to the first option, and if you press "up"
on the first option, it will wrap to the last option. This matches both
VSCode and Zed.
#7173
(1) The command palette no longer has any selection by default.
If and when we introduce most recently used commands, defaulting to that
would make sense. A selection only appears when the arrow keys are used
or the user starts typing.
(2) The selection with arrow keys now wraps, so if you press "down" on
the last option, it will wrap to the first option, and if you press "up"
on the first option, it will wrap to the last option. This matches both
VSCode and Zed.
As discussed in #7165, this PR resolves two issues with the command
pallete on macOS.
1. The flashing of a command passing out of view was caused by the
`withAnimation` function. Removing this closure resolved this issue.
2. The current implementation kept the selected command anchored to the
middle of the pallete. However, most command pallates allow you to key
through the visible options, only scrolling when your selection hits the
boundary. I resolved this by removing the `anchor: .center` parameter
for the `scrollTo` command.
Changes:
1. Require `blueprint-compiler` 0.16.0 (or newer) for building from
a git checkout. With #6822 distributions that can't meet that
requirement can use generated source tarballs to build.
2. Remove all `.ui` files as they are unnecessary.
3. Simplify the `Builder` interface since raw `.ui` files are no
longer used.
4. Removed build-time check of raw `.ui` files.
Removes the withAnimation closure which caused flashing when scrolling
up or down with arrow keys. Also removes the center anchor to behave
more like other command palletes (e.g., Zed, Raycast).
This should make testing Flatpak builds a lot easier.
To build, enter `flatpak/` directory and run:
flatpak-builder --repo=repo builddir com.mitchellh.ghostty.yml
alternatively, using org.flatpak.Builder flatpak:
flatpak run -p org.flatpak.Builder \
--repo=repo \
builddir \
com.mitchellh.ghostty.yml
The resulting flatpak can be installed using
flatpak install ./repo com.mitchellh.ghostty
Credit of AppStream metadata goes to @yorickpeterse.
This should make testing Flatpak builds a lot easier.
To build, enter `flatpak/` directory and run:
flatpak-builder --repo=repo builddir com.mitchellh.ghostty.yml
alternatively, using org.flatpak.Builder flatpak:
flatpak run -p org.flatpak.Builder \
--repo=repo \
builddir \
com.mitchellh.ghostty.yml
The resulting flatpak can be installed using
flatpak install ./repo com.mitchellh.ghostty
Credit of AppStream metadata goes to @yorickpeterse.
This introduces a command palette (inspired by @pluiedev's work in
#5681, but not using it as a base) for macOS.
The command palette is available in the `View` menu and also bindable
via `toggle_command_palette`, default binding is `cmd+shift+p` to match
VSCode.
The commands in the command palette must map to a _bindable_ action,
though they may not have an associated keybinding. This means that any
new binding actions we add in the future can be represented here and
also makes it easy in the future to add configuration to add new custom
entries to the command palette. For this initial PR, the available
commands are hardcoded (`src/input/commands.zig`).
I've noticed in other programs (VSCode, Zed), the command palette
contains pretty much _all available actions_ even if they're basically
useless in the context of a command palette. For example, Zed has the
"toggle command palette" action in the command palette and it... does
nothing (it probably should hide the palette). I followed @pluiedev's
lead and made this subjective in this PR but I wonder if we should
actually force all binding actions to be available.
There are various other improvements I'd like to make but omitted from
this PR for the sake of limiting scope:
* Instead of an entry with no matches doing nothing, we can allow users
to manually input _any_ configurable binding.
* Localization, since macOS doesn't have any yet. But for Linux when we
port this we probably have to change our strings extraction.
## Demo
https://github.com/user-attachments/assets/a2155cfb-d86b-4c1a-82b5-74ba927e4d69
Resolves#7108
This PR adds visual notification badges to the Ghostty dock icon when
bell events are triggered while the application is in the background.
This complements the existing dock bounce notification, making it easier
for users to notice when a terminal needs attention.
https://github.com/user-attachments/assets/b54c881f-fea8-4085-8614-432d9e5847b9
Fixes#2446
Supersedes #7144
Two separate issues:
1. Ensure that our screen size matches the viewport size when drawFrame
is called. By the time drawFrame is called, GTK will have updated the
OpenGL context, but our deferred screen size may still be incorrect
since we wait for the pty to update the screen size.
2. Do not clear our cells buffer when the screen size changes, instead
changing to a mechanism that only clears the buffers when we have over
50% wasted space.
/cc @adlr I added you as a coauthor.
Fixes#2446
Two separate issues:
1. Ensure that our screen size matches the viewport size when
drawFrame is called. By the time drawFrame is called, GTK will have
updated the OpenGL context, but our deferred screen size may still
be incorrect since we wait for the pty to update the screen size.
2. Do not clear our cells buffer when the screen size changes, instead
changing to a mechanism that only clears the buffers when we have
over 50% wasted space.
Co-authored-by: Andrew de los Reyes <adlr@rivosinc.com>
Fixes a regression where `C-S-c` stopped working properly in both legacy
and Kitty modes (although the Kitty mode side only affected alternates
and not the key itself so it probably worked fine in most programs).
The issue is that `charactersIgnoringModifiers` changes behavior if
`control` is pressed, so it doesn't really ignore all modifiers. We have
to use `characters(byApplyingModifiers:)` to get the proper unshifted
codepoint when `control` is pressed.
This replaces the use of our custom `Ghostty.KeyEquivalent` with the
SwiftUI `KeyboardShortcut` type. This is a more standard way to
represent keyboard shortcuts and lets us more tightly integrate with
SwiftUI/AppKit when necessary over our custom type.
This PR should have no user impact. This is just some cleanup for future
work.
Note that not all Ghostty triggers can be represented as
KeyboardShortcut values because macOS itself does not support binding
keys such as function keys (e.g. F1-F12) to KeyboardShortcuts.
This isn't an issue since all input also passes through a lower level
libghostty API which can handle all key events, we just can't show these
keyboard shortcuts on things like the menu bar. This was already true
before this commit.
Fixes a regression where `C-S-c` stopped working properly in both legacy
and Kitty modes (although the Kitty mode side only affected alternates
and not the key itself so it probably worked fine in most programs).
The issue is that `charactersIgnoringModifiers` changes behavior if
`control` is pressed, so it doesn't really ignore all modifiers. We have
to use `characters(byApplyingModifiers:)` to get the proper unshifted codepoint
when `control` is pressed.
This replaces the use of our custom `Ghostty.KeyEquivalent` with
the SwiftUI `KeyboardShortcut` type. This is a more standard way to
represent keyboard shortcuts and lets us more tightly integrate with
SwiftUI/AppKit when necessary over our custom type.
Note that not all Ghostty triggers can be represented as
KeyboardShortcut values because macOS itself does not support
binding keys such as function keys (e.g. F1-F12) to KeyboardShortcuts.
This isn't an issue since all input also passes through a lower level
libghostty API which can handle all key events, we just can't show these
keyboard shortcuts on things like the menu bar. This was already true
before this commit.
This reverts commit 14134d61fb, reversing
changes made to 6a876ef8ec.
This causes translation failures, this should be reintroduced when the
CI check passes.
Fixes#7131
Regression from #7121
Our consumed mods should not include "alt" if `macos-option-as-alt` is
set. To do this, we need to calculate our consumed mods based on the
actual translation event mods (if available, only available during
keyDown).
This is a large refactor of the keyboard input handling code in
libghostty and macOS. Previously, libghostty did a lot of things that
felt out of scope or was repeated work due to lacking context. For
example, libghostty would do full key translation from key event to
character (including unshifted translation) as well as managing dead key
states and setting the proper preedit text.
This is all information the apprt can and should have on its own.
NSEvent on macOS already provides us with all of this information,
there's no need to redo the work. The reason we did in the first place
is mostly historical: libghostty powered our initial macOS port years
ago when we didn't have an AppKit runtime yet.
This cruft has already practically been the source of numerous issues,
e.g.
https://github.com/ghostty-org/ghostty/issues/5558, but many other hacks
along the way, too.
This commit pushes all preedit (e.g. dead key) handling and key
translation
including unshifted keys up into the caller of libghostty.
Besides code cleanup, a practical benefit of this is that key event
handling on macOS is now about 10x faster on average. That's because
we're avoiding repeated key translations as well as other unnecessary
work. This should have a meaningful impact on input latency but I didn't
measure the full end-to-end latency.
A scarier part of this commit is that key handling is not well tested
since its a GUI component. I suspect we'll have some fallout for certain
keyboard layouts or input methods, but I did my best to run through
everything I could think of.
This also fixes one bug where preedit state didn't properly clear when
changing keyboard layouts. This now does and matches the behavior
of native apps like TextEdit and Terminal.app
This is a large refactor of the keyboard input handling code in
libghostty and macOS. Previously, libghostty did a lot of things that
felt out of scope or was repeated work due to lacking context. For
example, libghostty would do full key translation from key event to
character (including unshifted translation) as well as managing dead key
states and setting the proper preedit text.
This is all information the apprt can and should have on its own.
NSEvent on macOS already provides us with all of this information,
there's no need to redo the work. The reason we did in the first place
is mostly historical: libghostty powered our initial macOS port years
ago when we didn't have an AppKit runtime yet.
This cruft has already practically been the source of numerous issues, e.g.
#5558, but many other hacks along the way, too.
This commit pushes all preedit (e.g. dead key) handling and key translation
including unshifted keys up into the caller of libghostty.
Besides code cleanup, a practical benefit of this is that key event
handling on macOS is now about 10x faster on average. That's because
we're avoiding repeated key translations as well as other unnecessary
work. This should have a meaningful impact on input latency but I didn't
measure the full end-to-end latency.
A scarier part of this commit is that key handling is not well tested
since its a GUI component. I suspect we'll have some fallout for certain
keyboard layouts or input methods, but I did my best to run through
everything I could think of.
Fixes#7099
This adds basic bell features to macOS to conceptually match the GTK
implementation. When a bell is triggered, macOS will do the following:
1. Bounce the dock icon once, if the app isn't already in focus.
2. Add a bell emoji (🔔) to the title of the surface that triggered the
bell. This emoji will be removed after the surface is focused or a
keyboard event if the surface is already focused. This behavior matches
iTerm2.
Note that neither of these respect the `system` `bell-features` config
because they're both unobtrusive (the dock icon bounces only once, the
title change is silent and similar to GTK tab attention) and unrelated
to system settings.
This doesn't add an icon badge because macOS's dockTitle.badgeLabel API
wasn't doing anything for me and I wasn't able to fully figure out
why...
Fixes#7099
This adds basic bell features to macOS to conceptually match the GTK
implementation. When a bell is triggered, macOS will do the following:
1. Bounce the dock icon once, if the app isn't already in focus.
2. Add a bell emoji (🔔) to the title of the surface that triggered
the bell. This emoji will be removed after the surface is focused
or a keyboard event if the surface is already focused. This
behavior matches iTerm2.
This doesn't add an icon badge because macOS's dockTitle.badgeLabel API
wasn't doing anything for me and I wasn't able to fully figure out
why...
Fixes#7066
This fixes an issue where under certain conditions (expanded below), we
would not clear the correct row, leading to the screen having duplicate
data.
This was triggered by a page state of the following:
```
+----------+ = PAGE 0
... : :
4305 |1ABCD00000|
4306 |2EFGH00000|
:^ : = PIN 0
+-------------+ ACTIVE
4307 |3IJKL00000| | 0
+----------+ :
+----------+ : = PAGE 1
0 | | | 1
1 | | | 2
+----------+ :
+-------------+
```
Namely, the cursor had to NOT be on the last row of the first page, but
somewhere on the first page. Then, when an `index` (LF) operation was
performed the result would look like this:
```
+----------+ = PAGE 0
... : :
4305 |1ABCD00000|
4306 |2EFGH00000|
+-------------+ ACTIVE
4307 |3IJKL00000| | 0
:^ : : = PIN 0
+----------+ :
+----------+ : = PAGE 1
0 |3IJKL00000| | 1
1 | | | 2
+----------+ :
+-------------+
```
The `3IJKL` line was duplicated. What was happening here is that we
performed the index operation correctly but failed to clear the cursor
line as expected.
This is because we were always clearing the first row in the page
instead of the row of the cursor.
Test added.
Fixes#7066
This fixes an issue where under certain conditions (expanded below), we
would not clear the correct row, leading to the screen having duplicate
data.
This was triggered by a page state of the following:
```
+----------+ = PAGE 0
... : :
4305 |1ABCD00000|
4306 |2EFGH00000|
:^ : = PIN 0
+-------------+ ACTIVE
4307 |3IJKL00000| | 0
+----------+ :
+----------+ : = PAGE 1
0 | | | 1
1 | | | 2
+----------+ :
+-------------+
```
Namely, the cursor had to NOT be on the last row of the first page,
but somewhere on the first page. Then, when an `index` (LF) operation
was performed the result would look like this:
```
+----------+ = PAGE 0
... : :
4305 |1ABCD00000|
4306 |2EFGH00000|
+-------------+ ACTIVE
4307 |3IJKL00000| | 0
:^ : : = PIN 0
+----------+ :
+----------+ : = PAGE 1
0 |3IJKL00000| | 1
1 | | | 2
+----------+ :
+-------------+
```
The `3IJKL` line was duplicated. What was happening here is that we
performed the index operation correctly but failed to clear the cursor
line as expected.
This is because we were always clearing the first row in the page
instead of the row of the cursor.
Test added.
Supersedes #7075Fixes#7070
This fixes a few separate fullscreen issues with the quick terminal:
1. If we're on a fullscreen space, we can't reliably set the
`autoHideMenuBar` presentation option because macOS itself is managing
it. The fix is to use private APIs to detect we're on a fullscreen space
and avoid this.
2. If our quick terminal is fullscreen when we move spaces, we must exit
and re-enter fullscreen because the frame may change (e.g. due to
menubar changes).
3. If we aren't the frontmost app, we must avoid hiding the menu because
it has no effect and our fullscreen frame would be wrong.
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 is a bug I noticed in the following scenario:
1. Open Ghostty
2. Fullscreen normal terminal window (native fullscreen)
3. Open quick terminal
4. Move spaces, QT follows
5. Fullscreen the quick terminal
The result was that the menu bar would not disappear since our app is
not frontmost but we set the fullscreen frame such that we expected it.
Fixes#7077
This follows pretty standard behavior across native or popular
applications on both platforms macOS and Linux. The basic behavior is
that if you do a mouse down event and then drag the mouse beyond the
current character, then any mouse up actions are canceled (beyond
emiting the event itself).
This fixes a specific scenario where you could do the following:
1. Click anywhere (mouse down)
2. Drag over a valid link
3. Press command/control (to activate the link)
4. Release the mouse button (mouse up)
5. The link is triggered
Now, step 3 and step 5 do not happen. Links are not even highlighted in
this scenario. This matches iTerm2 on macOS which has a similar
command-to-activate-links behavior.
## Demo
https://github.com/user-attachments/assets/f79767b1-78fd-432b-af46-28194b816747
Fixes#7071
When the mouse is being actively dragged, AppKit continues to emit
mouseDragged events which will update our position appropriately. The
mouseExit event we were sending sends a synthetic (-1, -1) position
which was causing a scroll up.
Fixes#7077
This follows pretty standard behavior across native or popular applications
on both platforms macOS and Linux. The basic behavior is that if you
do a mouse down event and then drag the mouse beyond the current
character, then any mouse up actions are canceled (beyond emiting the
event itself).
This fixes a specific scenario where you could do the following:
1. Click anywhere (mouse down)
2. Drag over a valid link
3. Press command/control (to activate the link)
4. Release the mouse button (mouse up)
5. The link is triggered
Now, step 3 and step 5 do not happen. Links are not even highlighted in
this scenario. This matches iTerm2 on macOS which has a similar
command-to-activate-links behavior.
Fixes#7071
When the mouse is being actively dragged, AppKit continues to emit
mouseDragged events which will update our position appropriately. The
mouseExit event we were sending sends a synthetic (-1, -1) position
which was causing a scroll up.
The helper team has been discussing some common issues we see with
Discussion submissions (missing info, duplicates, etc.), and pluie's
#6937 has been a huge step forward - this PR introduces a template for
the Issue Triage Discussion type.
The template has gone through a few revisions prior to this PR, but I am
certain there are probably a few places to be cleaned up. You can test
it out by [opening a new "Issue Triage" Discussion in my
fork](https://github.com/taylrfnt/ghostty/discussions/new?category=issue-triage).
~~Creating this as a draft for now, since I may not be able to respond
to any review comments in a timely manner.~~
With this PR now it supports the brazilian portuguese language for
ghostty, native speaker and resident, want to bring this language to
this new terminal that is starting to become very fondly to me! Any
questions about it i am free to answer.
Hi!
I have followed the instructions and added translations for Spanish,
Country Latin America 419 according to
[this](https://localizely.com/locale-code/es-419/) and other online
sources.
In the provided links there was no references to 419, but I created it
via the suggested command:
`msginit -i po/com.mitchellh.ghostty.pot -l es_419.UTF-8 -o
"po/es_419.UTF-8.po" `
Glad to be able to contribute to this excellent product!
Cheers!
Fix: missing ponctuations on various lines.
Change "Warning" to "Attention", might change in the futur if it does not perfectly match common practice.
Fix: Multiline on config errors dialog
From #7052, but for some reason I couldn't push there:
> This helps get around the `CODEOWNERS` file requiring that
contributors need to have write access to the repo in order to be
requested for review. The mechanism is (for any new PR):
> 1. List the changed files
> 2. Fetch and parse the CODEOWNERS file to get a path:owner mapping
> 3. See if any changed file matches the CODEOWNERS mapping
> 4. Fetch team members for affected localization teams
> 5. Request review from individual accounts
Fixes#7032
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.
OSC 7's standard body is a percent-encoded file:// URL. There isn't an
easy way for us to percent-encode the path ($pwd) component here without
implementing a custom function.
Instead, switch to the kitty-shell-cwd:// scheme, which Kitty introduced
to ease this implementation challenge in shell scripts. It accepts the
path string verbatim, without an encoding.
In Ghostty, we accept both the file:// and kitty-shell-cwd:// schemes,
and we attempt to URI-decode them both, so in practice this is more
about the "correctness" of this protocol than a functional change. It's
also possible we might decide to treat these schemes differently in the
runtime, like Kitty does.
Also, fix the `platform:hostname` function call syntax.
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.
OSC 7's standard body is a percent-encoded file:// URL. There isn't an
easy way for us to percent-encode the path ($pwd) component here without
implementing a custom function.
Instead, switch to the kitty-shell-cwd:// scheme, which Kitty introduced
to ease this implementation challenge in shell scripts. It accepts the
path string verbatim, without an encoding.
In Ghostty, we accept both the file:// and kitty-shell-cwd:// schemes,
and we attempt to URI-decode them both, so in practice this is more
about the "correctness" of this protocol than a functional change. It's
also possible we might decide to treat these schemes differently in the
runtime, like Kitty does.
This sort of command is treated as valid by Kitty so we should too. In
fact, it occurs with the example `send-png` script provided in the docs
for the protocol.
Currently the elvish shell integration uses the `hostname` command, this
may not exist on all systems and is somewhat redundant to rely on when
elvish has an
[`platform:hostname`](https://elv.sh/ref/platform.html#platform:hostname)
function that can be used and will work across platform regardless of
command availability. On top of this instead of directly calling the
`pwd` function we can simply use the built-in
[`$pwd`](https://elv.sh/ref/builtin.html#$pwd) variable that elvish
gives us. This should prevent the shell integration from breaking due to
external function availability.
This sort of command is treated as valid by Kitty so we should too. In
fact, it occurs with the example `send-png` script provided in the docs
for the protocol.
Fixes#7000
Related to #6909, the same mechanism, but it turns out some control+keys
are also handled in this same way (namely control+esc leads to "cancel"
by default, which is not what we want).
Fixes#7000
Related to #6909, the same mechanism, but it turns out some control+keys
are also handled in this same way (namely control+esc leads to "cancel"
by default, which is not what we want).
Fixes#2595
This fixes an issue where a left mouse click on a terminal while not
focused would subsequently be encoded to the pty as a mouse event. This
is atypical for macOS applications in general and wasn't something we
wanted to do.
We do, however, want to ensure our terminal gains focus when clicked
without focus. Specifically, a split. This matches iTerm2 behavior and
is rather nice. We had this behavior before but our logic to make this
work before caused the issue this commit is fixing.
I also tested this with command+click which is a common macOS shortcut
to emit a mouse event without raising the focus of the target window. In
this case, we will properly focus the split but will not encode the
mouse event to the pty. I think we actually do a _better job_ here tha
iTerm2 (but, subjective) because we do encode the pty event properly if
the split is focused whereas iTerm2 never does.
Fixes#2595
This fixes an issue where a left mouse click on a terminal while not
focused would subsequently be encoded to the pty as a mouse event. This
is atypical for macOS applications in general and wasn't something we
wanted to do.
We do, however, want to ensure our terminal gains focus when clicked
without focus. Specifically, a split. This matches iTerm2 behavior and
is rather nice. We had this behavior before but our logic to make this
work before caused the issue this commit is fixing.
I also tested this with command+click which is a common macOS shortcut
to emit a mouse event without raising the focus of the target window. In
this case, we will properly focus the split but will not encode the
mouse event to the pty. I think we actually do a _better job_ here tha
iTerm2 (but, subjective) because we do encode the pty event properly if
the split is focused whereas iTerm2 never does.
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.
See: #5070
Fixes#6962
I believe this is an upstream bug
(https://github.com/ziglang/zig/issues/23454), where Zig is allowing
extern unions to be tagged when created via type reification. This
results in a CValue that has an extra trailing byte (the tag).
This wasn't causing any noticeable issues for Ghostty for some reason
but others using our pattern were seeing issues. And I did confirm that
our CValue was indeed tagged and was the wrong byte size. I assume Swift
was just ignoring it because it was extra data. I don't know, but we
should fix this in general for libghostty.
Fixes#6962
I believe this is an upstream bug
(https://github.com/ziglang/zig/issues/23454), where Zig is allowing
extern unions to be tagged when created via type reification. This
results in a CValue that has an extra trailing byte (the tag).
This wasn't causing any noticeable issues for Ghostty for some reason
but others using our pattern were seeing issues. And I did confirm that
our CValue was indeed tagged and was the wrong byte size. I assume Swift
was just ignoring it because it was extra data. I don't know, but we
should fix this in general for libghostty.
See #6957
We were not considering GTK's internal scale factor that converts
between "surface coordinates" and actual device coordinates, and that
worked fine until the scale factor reached 2x (200%).
Since the code is now dependent on the scale factor (which could change
at any given moment), we also listen to scale factor changes and then
unconditionally call `winproto.syncAppearance`. Even though it's
somewhat overkill, I don't expect people to change their scale factor
dramatically all the time anyway...
See #6957
We were not considering GTK's internal scale factor that converts between
"surface coordinates" and actual device coordinates, and that worked fine
until the scale factor reached 2x (200%).
Since the code is now dependent on the scale factor (which could change
at any given moment), we also listen to scale factor changes and then
unconditionally call `winproto.syncAppearance`. Even though it's somewhat
overkill, I don't expect people to change their scale factor dramatically
all the time anyway...
Fixes a regression from #6909
See #6887
In certain scenarios, the last command key state would linger around (I
could only see this happen with global keybinds for unknown reasons
yet). This state is only meant to have an effect within the cycle of a
single keybind and only so we can ensure an event reaches keyDown so it
should be reset if keyDown is ever sent (since, by definition at that
point, keyDown has been reached).
I'm still not happy that this is necessary and I suspect there is a
better root cause to resolve, but I'd rather get this fix in now and
figure out the root cause later.
Fixes a regression from #6909
See #6887
In certain scenarios, the last command key state would linger around (I
could only see this happen with global keybinds for unknown reasons
yet). This state is only meant to have an effect within the cycle of a
single keybind and only so we can ensure an event reaches keyDown so it should
be reset if keyDown is ever sent (since, by definition at that point,
keyDown has been reached).
I'm still not happy that this is necessary and I suspect there is a
better root cause to resolve, but I'd rather get this fix in now and
figure out the root cause later.
Fixes problem pointed out in discussion #6895, as well as adjusting the
constraint logic to account for the offset, since I noticed it was
wrong; the constraint logic now accounts for the x offset, so that the
glyph does not exceed the right edge of the constraint width when the
offset is added, and the offset is zeroed if the glyph is resized down
to fit the constraint width.
|**Before**|**After**|
|-|-|
|<img width="84" alt="image"
src="https://github.com/user-attachments/assets/9561ca40-cfa0-4aed-b192-ee15042d8cbb"
/>|<img width="82" alt="image"
src="https://github.com/user-attachments/assets/9a77ac61-f46d-41de-a859-2b394024f7bc"
/>|
<sup>Zoom in to images for detail if you can't see the
crunchiness.</sup>
> [!NOTE]
> It may be an issue that that glyph is rendered with the constrained
text mode in the first place - Kitty doesn't seem to apply constraint
logic to it, and it seems a little over-eager to do so on our part. This
is something to look in to.
We can't use nearest neighbor filtering for sampling from the atlas
because we might not actually be doing pixel perfect sampling if the
glyph has been constrained. This will change in the future, but this
will have to be set to linear for now.
Fixes#5522
This commit re-dispatches command inputs that are unhandled by our macOS
app so they can be encoded to the pty and handled by the core libghostty
key callback system.
We've had a special case `cmd+period` handling in Ghostty for a very
long time (since well into the private beta). `cmd+period` by default
binds to "cancel" in macOS, so it doesn't encode to the pty. We don't
handle "cancel" in any meaningful way in Ghostty, so we special-cased it
to encode properly to the pty.
However, as shown in #5522, if the user rebinds `cmd+period` at the
system level to some other operation, then this is ignored and we encode
it still. This isn't desirable, we just want to work around not caring
about "cancel."
The callback path that AppKit takes for key events is a bit convoluted.
For command keys, it first calls `performKeyEquivalent`. If this returns
false (we want to continue standard processing), then it calls EITHER
`keyDown` or `doCommand(by:)`. It calls the latter if there is a
standard system command that matches the key event. For `cmd+period` by
default, this is "cancel." Unfortunately, from `doCommand` we can't say
"oops, we don't want to handle this, please continue processing." Its
too late.
So, this commit stores the last command key event from
`performKeyEquivalent` and if we reach `doCommand` for it without having
called `keyDown`, we re-dispatch the event and send it to keyDown.
I'm honestly pretty sus about this whole logic but it is scoped to only
command-keys and I couldn't trigger any adverse behavior in my testing.
It also definitely fixed#5522 as far as I could reproduce it before.
Fixes#5522
This commit re-dispatches command inputs that are unhandled by our macOS
app so they can be encoded to the pty and handled by the core libghostty
key callback system.
We've had a special case `cmd+period` handling in Ghostty for a very
long time (since well into the private beta). `cmd+period` by default
binds to "cancel" in macOS, so it doesn't encode to the pty. We don't
handle "cancel" in any meaningful way in Ghostty, so we special-cased it
to encode properly to the pty.
However, as shown in #5522, if the user rebinds `cmd+period` at the
system level to some other operation, then this is ignored and we encode
it still. This isn't desirable, we just want to work around not caring
about "cancel."
The callback path that AppKit takes for key events is a bit convoluted.
For command keys, it first calls `performKeyEquivalent`. If this returns
false (we want to continue standard processing), then it calls EITHER
`keyDown` or `doCommand(by:)`. It calls the latter if there is a
standard system command that matches the key event. For `cmd+period` by
default, this is "cancel." Unfortunately, from `doCommand` we can't say
"oops, we don't want to handle this, please continue processing." Its
too late.
So, this commit stores the last command key event from
`performKeyEquivalent` and if we reach `doCommand` for it without having
called `keyDown`, we re-dispatch the event and send it to keyDown.
I'm honestly pretty sus about this whole logic but it is scoped to only
command-keys and I couldn't trigger any adverse behavior in my testing.
It also definitely fixed#5522 as far as I could reproduce it before.
This updates our bundled Harfbuzz from 8.4 to 11.0. The changes from 8
to 11 include a number of correctness and performance improvements.
Packaged releases tend to dynamically link so this won't affect existing
users, but build-from-source users hopefully get an improvement.
This updates our bundled Harfbuzz from 8.4 to 11.0. The changes from 8
to 11 include a number of correctness and performance improvements.
Packaged releases tend to dynamically link so this won't affect
existing users, but build-from-source users hopefully get an
improvement.
We have always tracked a post-3.4 release, this brings us up to date.
There's no real motivation beyond this other than keeping up to date
since we're already on non-release versions anyways. The diff is
dominated by auto-generated headers from Wayland-scanner.
We have always tracked a post-3.4 release, this brings us up to date.
There's no real motivation beyond this other than keeping up to date
since we're already on non-release versions anyways.
Fixes#6872
This commit explicitly acquires the GL context in the `unrealize` signal
handler of the GTK Surface prior to cleaning up GPU resources.
A GLArea only guarantees that the associated GdkContext is current for
the `render` signal (see the docs[1]). This is why our OpenGL renderer
"defers" various operations such as resize, font grid changing, etc.
(see the `deferred_`-prefix fields in `renderer/OpenGL.zig`). However,
we missed a spot.
The `gtk-widget::unrealize` signal is emitted when the widget is
destroyed, and it is the last chance we have to clean up our GPU
resources. But it is not guaranteed that the GL context is current at
that point, and we weren't making it current. On the NGL GTK renderer,
this was freeing GPU resources we didn't own.
As best I can understand, the old GL renderer only ever used a handful
of GL resources that were early in the ID space, so by coincidence we
were probably freeing nothing and everything was fine. But with the new
NGL renderer uses a LOT more GL resources (make a few splits and the ID
space is already in the thousands, from GTK!), so we were freeing real
resources that we didn't own, which caused rendering issues. :)
I suspect the above also resulted in VRAM memory leaks (which would be
RAM memory leaks for unified memory GPUs). This potentially relates to
#5491.
The fix is to explicitly make the GL context current in the `unrealize`
handler.
[1]: https://docs.gtk.org/gtk4/method.GLArea.make_current.html
This was a hack, and is no longer required since #6877. Users can
explicitly override the GTK GSK renderer by setting the standard GTK env
var `GSK_RENDERER`.
Fixes#6872
This commit explicitly acquires the GL context in the `unrealize`
signal handler of the GTK Surface prior to cleaning up GPU resources.
A GLArea only guarantees that the associated GdkContext is current for
the `render` signal (see the docs[1]). This is why our OpenGL renderer
"defers" various operations such as resize, font grid changing, etc.
(see the `deferred_`-prefix fields in `renderer/OpenGL.zig`).
However, we missed a spot.
The `gtk-widget::unrealize` signal is emitted when the widget is
destroyed, and it is the last chance we have to clean up our GPU
resources. But it is not guaranteed that the GL context is current at
that point, and we weren't making it current. On the NGL GTK renderer,
this was freeing GPU resources we didn't own.
As best I can understand, the old GL renderer only ever used a handful
of GL resources that were early in the ID space, so by coincidence we
were probably freeing nothing and everything was fine. But with the new
NGL renderer uses a LOT more GL resources (make a few splits and the ID
space is already in the thousands, from GTK!), so we were freeing real
resources that we didn't own, which caused rendering issues. :)
I suspect the above also resulted in VRAM memory leaks (which would be
RAM memory leaks for unified memory GPUs). This potentially relates to
#5491.
The fix is to explicitly make the GL context current in the `unrealize`
handler.
[1]: https://docs.gtk.org/gtk4/method.GLArea.make_current.html
Instead of looking for individual substrings in $GHOSTTY_SHELL_FEATURES,
`str:split` it into a list of feature names and use `has-value` to
detect their presence.
Instead of looking for individual substrings in $GHOSTTY_SHELL_FEATURES,
`string split` it into a list of feature names and use `contains` to
detect their presence.
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.
> 3. If you want to live dangerously, open a pull request and hope for
the best.
Sure, why not!
---
This is a *super common* ask in both the GitHub Discussions and on
Discord; I thus decided to add a small(ish) note to the help output
directing users to try the open command. I did not include a note to
check the man page, as the text was already getting a bit long, but I
can change that if requested. Strings open for bike-shedding, of course.
Of course, feel free to close this if this is not a desirable change for
the project; I would appreciate a note about that though, rather than a
random unexpected close without any reason, as that would prevent any
future PRs about this from others.
As I do not use macOS, I was unable to test the appearance of the string
I edited in `main_ghostty.zig`.
On a slightly related note: are there any plans to translate the CLI's
strings? I assume they're in the same boat as the configuration parsing
errors which were [discussed in #maintainers] on [the Ghostty Discord].
[discussed in #maintainers]:
https://discord.com/channels/1005603569187160125/1337443701403815999/1352390511553417327
[the Ghostty Discord]: https://discord.org/ghostty
We don't currently support rendering SVG glyphs so they should be
ignored when loading. Additionally, the check for whether a glyph is
colored has been simplified by just checking the pixel mode of the
rendered bitmap.
This commit also fixes a bug caused by calling the color check inside of
`renderGlyph`, which caused the bitmap to be freed creating a chance for
memory corruption and garbled glyphs. This bug was introduced by 40c1140
(#6602) and discussed in #6781.
Fixes#6821
UTF8 translation using KeymapDarwin requires a buffer and the buffer was
stack allocated in the coreKeyEvent call and returned from the function.
We need the buffer to live longer than this.
Long term, we're removing KeymapDarwin (there is a whole TODO comment in
there about how to do it), but this fixes a real problem today.
Fixes#6821
UTF8 translation using KeymapDarwin requires a buffer and the buffer was
stack allocated in the coreKeyEvent call and returned from the function.
We need the buffer to live longer than this.
Long term, we're removing KeymapDarwin (there is a whole TODO comment in
there about how to do it), but this fixes a real problem today.
Also update shaper test that fails because the run iterator can't apply
that logic since `testWriteString` doesn't do proper grpaheme clustering
so the parts are actually split across multiple cells.
Several other tests are technically incorrect for the same reason but
still pass, so I've decided not to fix them here.
This tweak is minor and fixes some grammatical errors.
I did not change `kopier` to `kopiér`, even though that is a common way
to write the word. The language council of Norway suggest writing the
word as it is written here, without the accent, but it does read weird
and allows for misunderstanding. If this was my project I would have
added the accent. Let me know if you want the accent added.
What do you think regarding the accents @Uzaaft ?
this fix addresses issue occurring in nvim/vim where Backspace deletes
previous letter while in preedit state.
related to #1638
> same issue reproducible
> However, that only fixed for legacy encoding.
Unanswered Discussion
https://github.com/ghostty-org/ghostty/discussions/5312<div
type='discussions-op-text'> can be closed afterwards
tested on macos sequioa IME '2-set Korean', 'Japanese Romaji'
This tweak is minor and fixes some grammatical errors.
I did not change `kopier` to `kopiér`, even though that is a common way
to write the word. The language council of Norway suggest writing the
word as it is written here, without the accent, but it does read weird
and allows for misunderstanding. If this was my project I would have
added the accent. Let me know if you want the accent added.
Closes#6760
Supersedes #6762
This introduces the concept of a "dist resource" (specifically a
`GhosttyDist.Resource` type). This is a resource that may be present in
dist tarballs but not in the source tree. If the resource is present and
we're not in a Git checkout, then we use it directly instead of
generating it. This is a generic concept we can apply to any
preprocessing we want to do that we don't want users/packagers to do.
This is used for the first time in this commit for the gresource c/h
files, which depend on a variety of external tools (blueprint-compiler,
glib-compile-resources, etc.) that we do not want to require downstream
users/packagers to have and we also do not want to worry about them
having the right versions.
This also adds a check for `distcheck` to ensure our distribution
contains all the expected files.
Note @jcollie that there may be elements of 6762 you'll want to bring
back after this.
This introduces the concept of a "dist resource" (specifically a
`GhosttyDist.Resource` type). This is a resource that may be present in
dist tarballs but not in the source tree. If the resource is present and
we're not in a Git checkout, then we use it directly instead of
generating it.
This is used for the first time in this commit for the gresource c/h
files, which depend on a variety of external tools (blueprint-compiler,
glib-compile-resources, etc.) that we do not want to require downstream
users/packagers to have and we also do not want to worry about them
having the right versions.
This also adds a check for `distcheck` to ensure our distribution
contains all the expected files.
We don't currently support rendering SVG glyphs so they should be
ignored when loading. Additionally, the check for whether a glyph is
colored has been simplified by just checking the pixel mode of the
rendered bitmap.
This commit also fixes a bug caused by calling the color check inside of
`renderGlyph`, which caused the bitmap to be freed creating a chance for
memory corruption and garbled glyphs.
Discover resourcesdir with `terminfo/g/ghostty`
as well as existing `terminfo/x/xterm-ghostty`.
This allows either terminfo file to be installed,
notably ncurses only provides a `terminfo/g/ghostty`.
It was brought up that the `ncurses-term` package on Fedora 42 at
<https://packages.fedoraproject.org/pkgs/ncurses/ncurses-term/fedora-42.html>
now provides a `/g/ghostty` terminfo entry which conflicts with
installing the similarly named file from ghostty's build process.
However a build with `zig build -Demit-terminfo=false` won't work as the
`x/xterm-ghostty` terminfo entry is used as a sentinel to discover the
`GHOSTTY_RESOURCES_DIR` used as a reference path for finding locale and
theme files.
```
src/Surface.zig|546 col 43| .resources_dir = global_state.resources_dir,
src/cli/list_themes.zig|112 col 22| if (global_state.resources_dir == null)
src/global.zig|38 col 5| resources_dir: ?[]const u8,
src/global.zig|173 col 14| self.resources_dir = try internal_os.resourcesDir(self.alloc);
src/global.zig|174 col 27| errdefer if (self.resources_dir) |dir| self.alloc.free(dir);
src/global.zig|177 col 18| if (self.resources_dir) |v| internal_os.i18n.init(v) catch |err| {
src/global.zig|185 col 18| if (self.resources_dir) |dir| self.alloc.free(dir);
```
We also have some comments that intend to change how the terminfo
database is discovered.
https://github.com/ghostty-org/ghostty/blob/main/src/termio/Exec.zig#L776C1-L781C60
I don't see any actual changes here, just reordering. It's using the Nix
environment so I'm not sure why this happened but it seemed to stem from
the Norwegian work originally. Fixing it back.
I don't see any actual changes here, just reordering. It's using the Nix
environment so I'm not sure why this happened but it seemed to stem from
the Norwegian work originally. Fixing it back.
Discover resourcesdir with `terminfo/g/ghostty`
as well as existing `terminfo/x/xterm-ghostty`.
This allows either terminfo file to be installed,
notably ncurses only provides a `terminfo/g/ghostty`.
This adds a CI test to ensure that all Zig files are properly formatted.
This avoids unrelated diff noise in future PRs.
This also runs `zig fmt` once to clean up all formatting issues for
future PRs.
I also introduced a new `xsm` (extra small) runner profile to use less
resources for our tiny tasks.
1. Refactored Nix devshell/package to make it easier to keep
LD_LIBRARY_PATH & buildInputs in sync (plus make it easier to re-use in
other Nix environment).
2. Added a CI job to ensure that Blueprints are formatted correctly and
that they will compile using `blueprint-compiler` 0.16.0.
3. Reformatted all Blueprints with `blueprint-compiler format`.
1. Refactored Nix devshell/package to make it easier to keep
LD_LIBRARY_PATH & buildInputs in sync (plus make it easier to re-use
in other Nix environment).
2. Added a CI job to ensure that Blueprints are formatted correctly and
that they will compile using `blueprint-compiler` 0.16.0.
3. Reformatted all Blueprints with `blueprint-compiler format`.
This moves the source tarball creation process into the Zig build system
and follows the autotools-standard naming conventions of `dist` and
`distcheck`.
This doesn't change any of our build process otherwise. This is the
foundation for #6760 along with other source tarball tasks I have
planned (i.e. gobject bindings too).
The `dist` target creates a source tarball in the `PREFIX/dist`
directory. The tarball is named `ghostty-VERSION.tar.gz` as expected by
standard source tarball conventions.
The `distcheck` target does the same as `dist`, but also takes the
resulting tarball, extracts it, and runs tests on the extracted source
to verify the source tarball works as expected. Distcheck currently only
runs `zig build test` but in the future we can add additional checks to
run.
This commit also updates CI:
1. Tagged releases now use the new `zig build distcheck` command.
2. Tip releases now use the new `zig build dist` command.
3. A new test build tests that source tarball generation works on every
commit.
This moves the source tarball creation process into the Zig build system
and follows the autotools-standard naming conventions of `dist` and
`distcheck`.
The `dist` target creates a source tarball in the `PREFIX/dist`
directory. The tarball is named `ghostty-VERSION.tar.gz` as expected by
standard source tarball conventions.
The `distcheck` target does the same as `dist`, but also takes the
resulting tarball, extracts it, and runs tests on the extracted source
to verify the source tarball works as expected.
This commit also updates CI:
1. Tagged releases now use the new `zig build distcheck` command.
2. Tip releases now use the new `zig build dist` command.
3. A new test build tests that source tarball generation works on
every commit.
As of Adwaita 1.5.0, the GTK Box is not being properly unref'd when the
parent window is closed. Update the conditional to account for this.
Also add a couple of missing unref()s in errdefers.
This fixes an issue where Ghostty would not properly quit after closing
the last surface.
https://github.com/ghostty-org/ghostty/discussions/3807 is related
(though I'm not sure it's the exact same problem).
As of Adwaita 1.5.0, the GTK Box is not being properly unref'd when the
parent window is closed. Update the conditional to account for this.
Also add a couple of missing unref()s in errdefers.
Fixes#6772
When typing Korean with the fcitx5-hangful input method, moving between
graphemes does not trigger a preedit end/start cycle and instead just
clears the preexisting preedit and reuses the started state.
Every other input method we've tested up until now doesn't do this. We
need to mark composing set to "false" in "commit" because some input
methods on the contrary fail to ever call END.
What is the point of start/end events if they are just ignored depending
on the whim of the input method? Nothing. That's what. Its all a mess
that GTK should be protecting us from but it doesn't and now its the app
developer's problem. I'm frustrated because I feel like the point of an
app framework is to mask this kind of complexity from the app developer
and I'm playing whack-a-mole with input methods.
Well, here's another whack. Let's see if it works.
Fixes#6772
When typing Korean with the fcitx5-hangful input method, moving between
graphemes does not trigger a preedit end/start cycle and instead just
clears the preexisting preedit and reuses the started state.
Every other input method we've tested up until now doesn't do this. We
need to mark composing set to "false" in "commit" because some input
methods on the contrary fail to ever call END.
What is the point of start/end events if they are just ignored depending
on the whim of the input method? Nothing. That's what. Its all a mess
that GTK should be protecting us from but it doesn't and now its the app
developer's problem. I'm frustrated because I feel like the point of an
app framework is to mask this kind of complexity from the app developer
and I'm playing whack-a-mole with input methods.
Well, here's another whack. Let's see if it works.
1. Remove usage of C header imports for gtk x11/wayland.
2. Move X11 C header imports to winproto_x11.zig
3. Clean up long line by breaking it up into multiple steps.
Some wheel mice are capable of reporting fractional wheel ticks. These
mice don't necessarily report a corresponding precision scroll start
event, at least in Wayland + GTK. We can treat all discrete (ie
non-precision) events as the number of wheel ticks - for wheel mice,
yoff will be "1.0" per tick, while precision wheel mice may report
fractional values. This unifies handling of scroll events by normalizing
all events to "pixels to scroll".
We now report `mouse-scroll-multiplier` wheel or arrow events per wheel
tick (or per accumulated cell height). This means that applications
which subscribe to mouse button events will receive (by default) three
wheel events per wheel tick. For precision scrolls, they will receive
one wheel tick per line of scroll. In my opinion, this provides the best
user experience while also allowing customization of how much a
wheel tick should scroll
Reference: https://github.com/ghostty-org/ghostty/discussions/6677
Upstream is now mostly pure Zig and the build.zig.zon.* files are
generated directly by zon2nix. The JSON file is no longer used as an
intermediate file but is retained for downstream packager usage.
Upstream is now mostly pure Zig and the build.zig.zon.* files are
generated directly by zon2nix. The JSON file is no longer used as an
intermediate file but is retained for downstream packager usage.
Some wheel mice are capable of reporting fractional wheel ticks. These
mice don't necessarily report a corresponding precision scroll start
event, at least in Wayland + GTK. We can treat all discrete (ie
non-precision) events as the number of wheel ticks - for wheel mice,
yoff will be "1.0" per tick, while precision wheel mice may report
fractional values. This unifies handling of scroll events by normalizing
all events to "pixels to scroll".
We now report `mouse-scroll-multiplier` wheel or arrow events per wheel
tick (or per accumulated cell height). This means that applications
which subscribe to mouse button events will receive (by default) three
wheel events per wheel tick. For precision scrolls, they will receive
one wheel tick per line of scroll. In my opinion, this provides the best
user experience while also allowing customization of how much a
wheel tick should scroll
Reference: https://github.com/ghostty-org/ghostty/discussions/6677
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.
Fixes#6727
The major change in this commit is to consolidate all the C imports in a
single decl in main.zig. This is required for Zig 0.14. Without it, the
problem in #6727 will happen. I was never able to minimize why this
happens in order to open a Zig bug.
Beyond this, I fixed the build.zig and build.zig.zon to work with Zig
0.14 so that we can test building `pkg/macos` in isolation. There are no
downstream impacting changes in the build.zig files.
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.
Fixes#6727
The major change in this commit is to consolidate all the C imports in
a single decl in main.zig. This is required for Zig 0.14. Without it,
the problem in #6727 will happen. I was never able to minimize why this
happens in order to open a Zig bug.
Beyond this, I fixed the build.zig and build.zig.zon to work with Zig
0.14 so that we can test building `pkg/macos` in isolation. There are no
downstream impacting changes in the build.zig files.
Closes#6703
Lazy dependencies are only fetched if the build script would actually
reach a usage of that dependency at runtime (when the `lazyDependency`
function is called). This can save a lot of network traffic, disk uage,
and time because we don't have to fetch and build dependencies that we
don't actually need.
Prior to this commit, Ghostty fetched almost everything for all
platforms and configurations all the time. This commit reverses that to
fetching almost nothing until it's actually needed.
There are very little downsides to doing this[1]. One downside is `zig
build --fetch` doesn't fetch lazy dependencies, but we don't rely on
this command for packaging and suggest using our custom shell script
that downloads a cached list of URLs (`build.zig.zon.txt`).
This commit doesn't cover 100% of dependencies, since some provide no
benefit to make lazy while the complexity to make them lazy is higher
(in code style typically).
Conversely, some simple dependencies are marked lazy even if they're
almost always needed if they don't introduce any real complexity to the
code, because there is very little downside to do so.
[1]: https://ziggit.dev/t/lazy-dependencies-best-dependencies/5509/5
Lazy dependencies are only fetched if the build script would actually
reach a usage of that dependency at runtime (when the `lazyDependency`
function is called). This can save a lot of network traffic, disk uage,
and time because we don't have to fetch and build dependencies that we
don't actually need.
Prior to this commit, Ghostty fetched almost everything for all
platforms and configurations all the time. This commit reverses that to
fetching almost nothing until it's actually needed.
There are very little downsides to doing this[1]. One downside is `zig
build --fetch` doesn't fetch lazy dependencies, but we don't rely on
this command for packaging and suggest using our custom shell script
that downloads a cached list of URLs (`build.zig.zon.txt`).
This commit doesn't cover 100% of dependencies, since some provide no
benefit to make lazy while the complexity to make them lazy is higher
(in code style typically).
Conversely, some simple dependencies are marked lazy even if they're
almost always needed if they don't introduce any real complexity to the
code, because there is very little downside to do so.
[1]: https://ziggit.dev/t/lazy-dependencies-best-dependencies/5509/5
Closes#6702
This removes our mach-glfw dependency and replaces it with an in-tree
pkg/glfw that includes both the source for compiling glfw as well as the
Zig bindings. This matches the pattern from our other packages.
This is based on the upstream mach-glfw work and therefore includes the
original license and copyright information.
The reasoning is stated in the issue but to summarize for the commit:
- mach-glfw is no longer maintained, so we have to take ownership
- mach-glfw depended on some large blobs of header files to enable
cross-compilation but this isn't something we actually care about, so we
can (and do) drop the blobs
- mach-glfw blobs were hosted on mach hosts. given mach-glfw is
unmaintained, we can't rely on this hosting
- mach-glfw relied on a "glfw" package which was owned by another person
to be Zig 0.14 compatible, but we no longer need to rely on this
- mach-glfw builds were outdated based on latest Zig practices
Closes#6702
This removes our mach-glfw dependency and replaces it with an in-tree
pkg/glfw that includes both the source for compiling glfw as well as the
Zig bindings. This matches the pattern from our other packages.
This is based on the upstream mach-glfw work and therefore includes the
original license and copyright information.
The reasoning is stated in the issue but to summarize for the commit:
- mach-glfw is no longer maintained, so we have to take ownership
- mach-glfw depended on some large blobs of header files to enable
cross-compilation but this isn't something we actually care about,
so we can (and do) drop the blobs
- mach-glfw blobs were hosted on mach hosts. given mach-glfw is
unmaintained, we can't rely on this hosting
- mach-glfw relied on a "glfw" package which was owned by another
person to be Zig 0.14 compatible, but we no longer need to rely on
this
- mach-glfw builds were outdated based on latest Zig practices
This PR adds a translation for German `de_DE`.
Additionally it excludes all `*.po` files from the typos CI action.
Some comments on the decisions I made (open to discuss them):
- I choosed to use `du` instead of `Sie` as this seems appropriate
to me.
- I added `Window` (`Fenster` in German) to all split commands
as it appears more naturally to me.
Closes#5744
This gets Ghostty onto Zig 0.14. The goal of this PR is to focus only on
Zig 0.14 _compatibility_. I plan to open a number of subsequent issues
for future improvements I'd like to tackle, as noted in #5744.
I did run some basic benchmarks on a Zig 0.13 vs 0.14 build and didn't
notice any statistically significant changes. All our scrolling
benchmarks on vtebench got consistently 1 or 2ms faster but that may
just be noise. The good news is nothing got consistently slower (nothing
got slower at all on any runs!).
The Git history here is kind of nasty, I'm going to squash it.
I noticed we weren't doing system-integration against the pkgconfig for
gtk4-layer-shell. This behaviour differed from how we handled system
integration for existing deps in `pkg/` (oniguruma, fontconfig).
Refactored `pkg/gtk4-layer-shell/build.zig` referencing
`pkg/oniguruma/build.zig` to use pkgconfig names in system integration.
Previously we used to libname `libgtk4-layer-shell.so`
(`gtk4-layer-shell`) instead of pkgconfig name `gtk4-layer-shell-0.pc`
which meant system integration still relied on fetching the C-headers
via `zig fetch` instead of system C-headers.
I've tested this with a `--system` build where the relevant
`.zig-cache/p/<hash of gtk4-layer-shell>` is stubbed to an empty
directory and `pkgconfig(gtk4-layer-shell-0)` is installed instead on
fedora linux.
When scaling emoji (with freetype), we would unilaterally scale the
bitmap to fit within the `cell_height`. For narrow fonts, this would
result in a horizontal overflow:

Modify the glyph rendering such that we scale to fit within the cell
width. After doing so, the above image looks like:

The emoji glyph is noticeably smaller because we have constrained the
height further than before, but fits perfectly within two cells. I am
using Victor Mono as my font, which is pretty narrow. The effect would
be even more pronounced on something like Iosevka.
Fixes#6633
For macOS, we set LANGUAGE to the priority list of preferred languages
for the app bundle, using the GNU gettext priority list format (colon
separated list of language codes).
This previously was inherited by the termio env. At first, this was by
design, but this has inherent flaws. Namely, the priority list format is
a GNU gettext specific format, and programs that use alternate gettext
implementations (like musl or Python) do not understand it and actually
do the wrong thing (not their fault!).
This change removes the inheritance of LANGUAGE in the termio env. To
make it extra safe, we only do set and unset LANGUAGE when we know we
launch from an app bundle. That was always the desired behavior but this
makes it more explicit.
Fixes#6633
For macOS, we set LANGUAGE to the priority list of preferred languages
for the app bundle, using the GNU gettext priority list format (colon
separated list of language codes).
This previously was inherited by the termio env. At first, this was by
design, but this has inherent flaws. Namely, the priority list format is
a GNU gettext specific format, and programs that use alternate gettext
implementations (like musl or Python) do not understand it and actually
do the wrong thing (not their fault!).
This change removes the inheritance of LANGUAGE in the termio env. To
make it extra safe, we only do set and unset LANGUAGE when we know we
launch from an app bundle. That was always the desired behavior but this
makes it more explicit.
Fixes#2384 on GTK
I'm not exactly sure how to deal with centered quick terminals so I
opted to make them similar to either top/bottom or left/right quick
terminals based on the monitor's orientation (portrait/landscape). This
may not be the right approach, so I'd like to hear more thoughts about
this.
clearCells() always asserts its page's integrity after finishing its
work (via a `defer`). We don't need to re-assert the page's integrity
immediately thereafter.
Sets the LANGUAGE environment variable based on the preferred languages
as reported by NSLocale.
macOS has a concept of preferred languages separate from the system
locale. The set of preferred languages is a list in priority order of
what translations the user prefers. A user can have, for example,
"fr_FR" as their locale but "en" as their preferred language. This would
mean that they want to use French units, date formats, etc. but they
prefer English translations.
gettext uses the LANGUAGE environment variable to override only
translations and a priority order can be specified by separating the
languages with colons. For example, "en:fr" would mean that English
translations are preferred but if they are not available then French
translations should be used.
To further complicate things, Apple reports the languages in BCP-47
format which is not compatible with gettext's POSIX locale format so we
have to canonicalize them. To canonicalize the languages we use an
internal function from libintl. This isn't normally available but since
we compile from source on macOS we can use it. This isn't necessary for
other platforms.
This logic is only run if the user didn't explicitly request a specific
locale, so it should really only affect macOS app launches. From the CLI
the environment will have a locale unless the user really explicitly
clears it out.
Fixes#2384 on GTK
I'm not exactly sure how to deal with centered quick terminals so I opted
to make them similar to either top/bottom or left/right quick terminals
based on the monitor's orientation (portrait/landscape). This may not be
the right approach, so I'd like to hear more thoughts about this.
clearCells() always asserts its page's integrity after finishing its
work (via a `defer`). We don't need to re-assert the page's integrity
immediately thereafter.
Sets the LANGUAGE environment variable based on the preferred languages
as reported by NSLocale.
macOS has a concept of preferred languages separate from the system
locale. The set of preferred languages is a list in priority order
of what translations the user prefers. A user can have, for example,
"fr_FR" as their locale but "en" as their preferred language. This would
mean that they want to use French units, date formats, etc. but they
prefer English translations.
gettext uses the LANGUAGE environment variable to override only
translations and a priority order can be specified by separating
the languages with colons. For example, "en:fr" would mean that
English translations are preferred but if they are not available
then French translations should be used.
To further complicate things, Apple reports the languages in BCP-47
format which is not compatible with gettext's POSIX locale format so
we have to canonicalize them. To canonicalize the languages we use
an internal function from libintl. This isn't normally available but
since we compile from source on macOS we can use it. This isn't
necessary for other platforms.
By linking using the pkg-config name we gain the compiler flags in pkgconf
for linking, specifically the -I <headers> to include system-installed
headers. This allows the gtk4-layer-shell pkg to not require the source
files specified in the `pkg/gtk4-layer-shell/build.zig.zon`.
pkg(gtk4-layer-shell): Refactor to allow dynamic linking
Refactored `pkg/gtk4-layer-shell/build.zig` to have similar structure
to `pkg/oniguruma/build.zig`.
Now dynamic link using pkgconfig, this adds pkgconfig compiler flags.
So we are now using system-installed headers to resolve @cInclude().
This builds on @pluiedev's excellent #6004.
## Background: The macOS (and libghostty consumer) Plan
Broadly, the decision I've come to is that for cross-platform
translations (i.e. strings shared across libghostty), we will be using
gettext and libghostty will export helper methods to call those (e.g.
`ghostty_translate` in this PR for singular forms). To be clear, **this
only applies to strings owned by libghostty**. For application-level
strings such as macOS-specific menu items and so on, we still have
choice but will likely using native features.
The reason for this is because converting gettext translations (`po`) to
native formats (Xcode String Catalog, `.strings`/`.stringsdict`) is
nightmare level, in particular for plural forms. I don't see a robust
path to doing it. And if we don't convert and don't use gettext, then
translators would have to maintain an identical translation in multiple
locations. To make matters worse, the macOS translation formats require
Apple-tooling for now unless you want to edit raw JSON.
Leveraging gettext lets us share translations across platforms and take
advantage of proven tech.
## PR Contents
**`pkg/libintl` builds and statically links libintl for macOS.** macOS
doesn't ship libintl with the system while Linux generally does with
libc, so we need to build this ourselves. This makes gettext available
to macOS. libintl is LGPL and we remain in compliance with that despite
static linking because our build process is fully open source, so
downstream consumers can modify our build scripts to replace it if they
wanted to.
~~**`src/os/locale.zig` now sets the `LANGUAGE` environment variable on
macOS based on the app's preferred languages.** macOS lets you configure
the system locale separate from preferred language. We previously relied
solely on `NSLocale.currentLocale`, but this only represents the system
locale. We now also look at `NSLocale.preferredLanguages` (a list in
priority order) and if we support a given language we set `LANGUAGE` so
gettext translates properly. Notably, the above lets us debug
translations in Xcode by setting alternate languages for debug builds
only.~~ Removed this for a future PR since it was problematic.
**`build.zig` unconditionally builds binary `mo` files** since they're
required for all apprts now.
**The macOS app bundles the translation strings.** This includes our
GTK-specific translation strings but the size of these is so small it
isn't worth the complexity of splitting up into multiple `pot`s at this
time, I think.
**i18n APIs moved to `src/os` from `src/apprt/gtk`.** Since these are
now cross-platform/cross-apprt, they're a core API. The only notable
change here is that `_` now maps to `dgettext` and explicitly specifies
our domain so that it's library-friendly. The GTK apprt calls
`initGlobalDomain` so that blueprint translations still work.
## Next Steps
This PR is all groundwork. The macOS app doesn't leverage any of this
yet, although I've verified it all works (e.g. calling the
`ghostty_translate` API from Swift).
For next steps, we need to have a use case for cross-platform
translations and the first one I was looking at was configuration error
messages and other core strings.
AdwAlertDialog is the recommended way to do alert/message dialogs
starting from libadwaita 1.5, and is much easier to manage than
GtkMessageDialog. (The latter is also deprecated since GTK 4.10, but
this PR does not migrate it to use GtkAlertDialog, mostly because of its
obtuse interface and that we'll remove the GtkMessageDialog code anyway
in 1.2 when we remove non-Adwaita builds.)
We also had two bugs where tabs with only one split would display the
"close surface" confirmation dialog, and windows would do the same when
closed via the "Close Window" menu item or by the `close_window` keybind
action. (The "close window" dialog only appears when the user clicks on
the close button on the titlebar.) Initially I was very confused by
this, but it turns out that we don't have any apprt action related to
closing a window, and it was simply closing surfaces...
As of now `gtk4-layer-shell` is unavailable on recent, stable releases
of many distros (Debian 12, Ubuntu 24.04, openSUSE Leap & Tumbleweed,
etc.) and outdated on many others (Nixpkgs 24.11/unstable, Fedora 41,
etc.) This is inconvenient for our users and severely limits where the
quick terminal can be used. As a result we then build gtk4-layer-shell
ourselves by default unless `--system` or `-fsys=gtk4-layer-shell` are
specified. This also allows me to add an idiomatic Zig API on top of the
library and avoiding adding even more raw C code in the GTK apprt.
Since we now build gtk4-layer-shell it should be theoretically available
on all Linux systems we target. As such, the `-Dgtk-layer-shell` build
option has been removed. This is somewhat of an experimental change as I
don't know if gtk4-layer-shell works perfectly across all distros, and
we can always add the option back if need be.
As of now `gtk4-layer-shell` is unavailable on recent, stable releases
of many distros (Debian 12, Ubuntu 24.04, openSUSE Leap & Tumbleweed, etc.)
and outdated on many others (Nixpkgs 24.11/unstable, Fedora 41, etc.)
This is inconvenient for our users and severely limits where the quick
terminal can be used. As a result we then build gtk4-layer-shell ourselves
by default unless `--system` or `-fsys=gtk4-layer-shell` are specified.
This also allows me to add an idiomatic Zig API on top of the library
and avoiding adding even more raw C code in the GTK apprt.
Since we now build gtk4-layer-shell it should be theoretically available
on all Linux systems we target. As such, the `-Dgtk-layer-shell` build
option has been removed. This is somewhat of an experimental change as
I don't know if gtk4-layer-shell works perfectly across all distros, and
we can always add the option back if need be.
For *some* reason we have a binding for close_window but it merely closes
the surface and not the entire window. That is not only misleading but
also just wrong. Now we make a separate apprt action for close_window
that would make it show a close confirmation prompt identical to as if
the user had clicked the (X) button on the window titlebar.
When scaling emoji, scale so that they entirely fit within 2 cells. The
previous behavior was to scale to fill vertically, however with fonts
which are narrow this would result in horizontal overflow.
When adding a glyph, we didn't pass the expected width to the glyph
renderer. This can be helpful when scaling emoji, as will happen in the
next commit.
Fixes https://github.com/ghostty-org/ghostty/issues/4947 for gtk
This PR implements the senstive content hiding when displaying the paste
confirmation dialog in secure input mode.
Following changes are implemented:
- in the blueprint for each dialog add a show/hide button that is not
visible by default, and a Revealer that is revealed by default
- save the `secure_input` action value for each surface in the GTK apprt
- pass the value when initializing the paste confirmation dialog
- in the dialog code, alter the visibility of the content and
reveal/hide buttons based on secure input flag value
Demo:
https://github.com/user-attachments/assets/c91cbd3d-ed3b-464d-b4cf-e51fe7aa23b7
I feel like this is already a nearly full implementation, but I'm
leaving this as a draft for now, since i need to look into blueprints
for Adwaita 1.2, and verify if it behaves properly when the dialog is in
not-sensitive input mode and in OSC52 mode.
This is my third (!) attempt at implementing localization support. By
leveraging GTK builder to do most of the `gettext` calls, I can avoid
the whole mess about missing symbols on non-glibc platforms.
Added some documentation too for contributors and translators, just for
good measure.
Supersedes #5214, resolves the GTK half of #2357
When one develops Ghostty while using Ghostty it could lead to an
interesting conundrum: the freshly built Ghostty would use the parent
Ghostty's resources, which would be stale and not reflect any new
changes to resources. This is especially bad for translators, since
their translations would not be reflected in the newly built Ghostty
if they happen to run it under older Ghostty, which is not only
counterintuitive and also painful in terms of workflow.
Now, on debug builds we always try to use the terminfo detection method
first in order to locate the zig-out/share/ghostty folder, and only fall
back to GHOSTTY_RESOURCES_DIR if the executable is for some reason no
longer in zig-out. You can test this behavior by manually moving the
Ghostty executable out of zig-out, and then launching it with and without
Ghostty.
This is my third (!) attempt at implementing localization support.
By leveraging GTK builder to do most of the `gettext` calls, I
can avoid the whole mess about missing symbols on non-glibc platforms.
Added some documentation too for contributors and translators,
just for good measure.
Fixes#5929
Replaces #5984
On macOS, native applications typically never encode any text for key
events that use the command key. This is because the command key is used
for key equivalents and "commands" and should not be used for text
input.
This can be verified with apps like TextEdit but also terminals like
Terminal.app officially but also iTerm2 unofficially. Anything such as
`Cmd+b` or `Cmd+Shift+b` will not produce any text input.
Cross-platform terminals generally don't follow this, for example Kitty
performs CSI-u encoding and Alacritty and WezTerm encode the text as-is
(i.e. `Cmd+b` will produce `b`).
On Linux, the super key (command-equivalent) does produce text input.
For example, `Super+b` will produce `b` in Gnome Console, Foot, and all
the cross-platform terminals mentioned above.
In the interest of matching the behavior of native macOS applications,
we should not encode text for command key events on macOS. We continue
to encode text for the super key on non-macOS platforms. This matches
the behaviors appropriately on each platform.
Fixes#5929
Replaces #5984
On macOS, native applications typically never encode any text for
key events that use the command key. This is because the command key
is used for key equivalents and "commands" and should not be used
for text input.
This can be verified with apps like TextEdit but also terminals like
Terminal.app officially but also iTerm2 unofficially. Anything such as
`Cmd+b` or `Cmd+Shift+b` will not produce any text input.
Cross-platform terminals generally don't follow this, for example Kitty
performs CSI-u encoding and Alacritty and WezTerm encode the text as-is
(i.e. `Cmd+b` will produce `b`).
On Linux, the super key (command-equivalent) does produce text input.
For example, `Super+b` will produce `b` in Gnome Console, Foot, and
all the cross-platform terminals mentioned above.
In the interest of matching the behavior of native macOS applications,
we should not encode text for command key events on macOS. We continue
to encode text for the super key on non-macOS platforms.
Fixes#5934 for macOS
If a `title` config is set, this change sets the title immediately on
windowDidLoad to ensure that the window appears with the correct title
right away.
If there is any reason to set another title, the `set_title` apprt
action will come on another event loop tick (due to our usage of
notifications) but that's okay since that's already how it works. This
is just to say that setting this here won't break any shell integration
or anything.
Fixes#5934 for macOS
If a `title` config is set, this change sets the title immediately on
windowDidLoad to ensure that the window appears with the correct title
right away.
If there is any reason to set another title, the `set_title` apprt
action will come on another event loop tick (due to our usage of
notifications) but that's okay since that's already how it works. This
is just to say that setting this here won't break any shell integration
or anything.
Modify the scroll behavior to better match other terminals, as well as
provide a
better overall experience. Before this PR, ghostty would scale
non-precision
scroll events dependent on the screen size. This is in line with kitty,
but no
other terminal. Ghostty also was the only terminal to send *more than
one* wheel
event.
```
# 50% Screen height
| terminal | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty | 3 | 1 |
| foot | 3 | 1 |
| xterm | 5 | 1 |
| kitty | 3 | 1 |
| ghostty | 2 | 2 |
# 100% Screen height
| terminal | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty | 3 | 1 |
| foot | 3 | 1 |
| xterm | 5 | 1 |
| kitty | 5 | 1 |
| ghostty | 3 | 3 |
```
This PR modifies Ghostty to behave like foot and alacritty.
For an improved user experience, we only use the configured multiplier
for
non-precision scrolls. This multiplier now *only applies* to viewport
scrolling
and alternate scroll mode. The default value has been updated to 3.0.
GTK also now supports precision scrolling.
Remove the flag which reports all scrolls as discrete scrolls. This
enables precision scrolling in GTK. We have to track a flag between
continuous scroll events.
Precision scrolls don't require a multiplier to behave nicely. However,
wheel scrolls feel extremely slow without one. We apply the multiplier
to wheel scrolls only
Make Ghostty behave like other terminals by multiplying scrolls by 3.0.
This only affects when we are reporting arrow keys (alternate scroll
mode) or when we are scrolling the scrollback.
When we report mouse scroll wheel events, they should not be multiplied.
Refactor the scrollCallback to only use a multiplier for viewport or
alternate scroll reports.
Calculate the scroll amount for non-precision scrolls as a direct
multiple of yoff. This fixes an issue where Ghostty sends scroll wheel
events (or arrow keys if in alternate scroll mode) that are variable,
dependent on the screen size. I checked multiple terminals, and each
responds to a single wheel click by sending only a single wheel / arrow
key - independent of screen size.
```sh
printf "\x1b[?1049h"
printf "\x1b[?1007h"
cat -v
```
Using the above procedure, with varying screen sizes:
```
# 50% Screen height
| terminal | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty | 3 | 1 |
| foot | 3 | 1 |
| xterm | 5 | 1 |
| kitty | 3 | 1 |
| ghostty | 2 | 2 |
# 100% Screen height
| terminal | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty | 3 | 1 |
| foot | 3 | 1 |
| xterm | 5 | 1 |
| kitty | 5 | 1 |
| ghostty | 3 | 3 |
```
Both ghostty and kitty scale the number of arrow keys sent in proportion
to the screen size. However, when mouse reporting is on, only ghostty
does this.
This commit makes Ghostty behave like foot, and more generally removes
the dependence on screen size.
This adds a note in the default config to note that may require a
restart. Also adds a note with the window-padding-x and window-padding-y
to indicate a restart will be required to update existing windows.
I ran into this while updating one of the existing values in the default
config file. `window-padding-x` The value defaulted to 2 so figured it
was safe to assume and just uncomment it and try reloading the config.
Doing that doesn't work only restarting will make it take effect for the
main window ( or of course more tricky opening new windows and killing
off the old one )
https://github.com/ghostty-org/ghostty/discussions/6022
Fixes https://github.com/ghostty-org/ghostty/issues/5940
The mentioned problem occurs because when creating a new tab through the
tab overview we do not have focus on the terminal area widget, it does
not matter if we have a custom title set or not.
I think it is just safe to remove this check from the code. I've tested
the change and I don't really see a valid use case in which we would not
want to set the window title even if we don't have focus on the terminal
area.
It is possible that fonts people are using don't contain these
characters as evidenced by users in the Discord.
Signed-off-by: Tristan Partin <tristan@partin.io>
Related to #6035
This implements the keybind/action portion of #5974 so that this can
have a binding and so that other apprts can respond to this and
implement it this way.
Related to #6035
This implements the keybind/action portion of #5974 so that this can
have a binding and so that other apprts can respond to this and
implement it this way.
As noted in the comments, this is so that apprt's can always know what
the default size of a window would be so they can utilize this for
"return to default size" actions.
The initial size shouldn't be treated as a "resize" event and was
already documented as such. Prior to this commit the docs already noted
that the initial size may be sent multiple times but only the first time
during initialization should be used as a resize.
Therefore, this shouldn't impact prior behavior. I've verified this with
the apprts.
As noted in the comments, this is so that apprt's can always know what
the default size of a window would be so they can utilize this for
"return to default size" actions.
The initial size shouldn't be treated as a "resize" event and was
already documented as such. Prior to this commit the docs already noted
that the initial size may be sent multiple times but only the first time
during initialization should be used as a resize.
Therefore, this shouldn't impact prior behavior. I've verified this with
the apprts.
Using `gtk4-layer-shell` still seems like the path of least resistance,
and to my delight it pretty much Just Works. Hurrah!
This implementation could do with some further polish (e.g. animations,
which can be implemented via libadwaita's animations API, and global
shortcuts), but as a MVP it works well enough.
It even supports tabs!
Fixes#4624.
Fixed: [2475](https://github.com/ghostty-org/ghostty/issues/2475)
The problem actually existed because of the responder chain, as
previously pointed out in the report (thanks to @mitchellh).
When we first click on Toggle Terminal Inspector:
* the responder chain goes to _toggleTerminalInspector_
(_SurfaceView_AppKit_ implementation).
When we click the second toggleTerminalInspector:
* it tries to find the next responder, but the one available is
_TerminalController_. (if we remove this method from there, the bug will
reproduce even without quick mode)
**Problem**:
TerminalController not available during quick terminal, so there's no
way to toggle inspector
**Solution**:
We add toggleTerminalInspector to the _QuickTerminalController_:
selector, as we did with other similar methods.
This was causing garbled text due to a non-rebuilt rows referencing an
outdated atlas when the DPI changed but not the grid dimensions, which
could be caused by a variety of things such as the quick terminal
slide-in, dpi scaling changes on sleep/wake, moving windows between
displays because of closing/opening the laptop lid, etc.
Fixes#2731
The meaning of "c" and "r" can be somewhat cryptic to non-native English
speakers as it may not be immediately obvious that "c" stands for
"columns", and "r" stands for "rows". I propose replacing them with
left-right and up-down double-headed arrows that convey the same
meaning, but in a truly language-neutral manner.
Related to #2357
The meaning of "c" and "r" can be somewhat cryptic to non-native English
speakers as it may not be immediately obvious that "c" stands for "columns",
and "r" stands for "rows". I propose replacing them with left-right and
up-down double-headed arrows that convey the same meaning, but in a
truly language-neutral manner.
Related to #2357
This was causing garbled text due to a non-rebuilt rows referencing an
outdated atlas when the DPI changed but not the grid dimensions, which
could be caused by a variety of things such as the quick terminal
slide-in, dpi scaling changes on sleep/wake, moving windows between
displays because of closing/opening the laptop lid, etc.
Elvish integration is broken when running `sudo`, because the function
`sudo-with-terminfo` uses `command` command, which is not implemented in
Elvish. Changing it to `external` command should fix possible error when
bypassing aliases, functions and builtins, like `command` does in Bash.
Discussion about this issue: #5979
Also I updated documentation about Elvish integration to provide fix
when using `use ghostty-integration` outside of Ghostty with `rc.elv`.
Note that for Debian 12, the blueprints must be compiled on a distro
with a newer version of `blueprint-compiler` and the raw UI XML
committed to git. Debian 12 includes `blueprint-compiler` 0.6.0 which
doesn't support compiling `adw.MessageDialog` even though the version of
`libadwaita` supports it.
In docs option reference, for macos-icon info. It is showing two times
similar thing. And Note looks good instead of just text because it wont
work without `custom-style`. So a Big Note is a good highlight.
There's been *far* too many people who aren't aware of the new
dependency, and that is partly our fault: a "FileNotFound" error is
quite obtuse, unless you religiously follow every PR and every commit
made to the repository. Instead of shepherding everyone who runs into
this manually, we should offer better signposting.
There's been *far* too many people who aren't aware of the new dependency,
and that is partly our fault: a "FileNotFound" error is quite obtuse,
unless you religiously follow every PR and every commit made to the
repository. Instead of shepherding everyone who runs into this manually,
we should offer better signposting.
Fixes#5927
This doesn't fix the underlying reason that GTK sometimes reports
content scales as negative. If GTK reports a negative scale, we ignore
that and use 1.0 for the scale.
See #5930
Kakoune sends a real SGR sequence with 17 parameters. Our previous max
was 16 so we threw away the entire sequence. This commit increases the
max rather than fundamentally addressing limitations.
Practically, it took us this long to witness a real world sequence that
exceeded our previous limit. We may need to revisit this in the future,
but this is an easy fix for now.
In the future, as the comment states in this diff, we should probably
look into a rare slow path where we heap allocate to accept up to some
larger size (but still would need a cap to avoid DoS). For now,
increasing to 24 slightly increases our memory usage but shouldn't
result in any real world issues.
This should resolve#5938.
I tested this locally using `zig build -D=emit-docs=true` and confirmed
that the mdgen process provided the new manpages in
`zig-out/share/man/man*`, respectively.
Let me know if this needs any changes, I tried to keep this as similar
as possible to the existing manpages (both format and content).
See #5930
Kakoune sends a real SGR sequence with 17 parameters. Our previous max
was 16 so we through away the entire sequence. This commit increases the
max rather than fundamentally addressing limitations.
Practically, it took us this long to witness a real world sequence that
exceeded our previous limit. We may need to revisit this in the future,
but this is an easy fix for now.
In the future, as the comment states in this diff, we should probably
look into a rare slow path where we heap allocate to accept up to some
larger size (but still would need a cap to avoid DoS). For now,
increasing to 24 slightly increases our memory usage but shouldn't
result in any real world issues.
This will be needed to the convert GTK apprt surfaces to a DerivedConfig
model rather than accessing the global config stored in the app. It's a
no-op for now, but I have a PR in the works to update the resize overlay
that will take advantage of this.
Menus (context menus and the window hamburger menu) now use popovers and
are defined using GTK builder UI files. This is a bit more "modern" and
reduces the amount of code to define menus.
## Root Cause
The issue has two aspects:
1. The window creation process didn't explicitly force focus on the new
window after showing it.
2. More fundamentally, we were relying on `NSApp.isActive` to determine
whether to activate the application, which is problematic because:
- When creating a window from quick terminal, the application is already
"active" but this active state is owned by the quick terminal
- The [`NSApp.isActive`
check](4cfe5522db/macos/Sources/Features/Terminal/TerminalManager.swift (L100))
doesn't accurately reflect our intent - creating a new window is an
explicit user action that should always result in that window gaining
focus
## Solution
Removing the `NSApp.isActive` check.
```swift
// Before
if !NSApp.isActive {
NSApp.activate(ignoringOtherApps: true)
}
// After
NSApp.activate(ignoringOtherApps: true)
```
Fixes#5688
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.
For platforms that don't have multiple choices (currently macOS), the
dynamic API is literally the static API so there should be no
performance or behavioral changes whatsoever.
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.
The GTK side of appearance code is kind of a mess with several different
functions all having the responsibility of interacting with each other
and setting the appropriate window appearance. It should solely be the
responsibility of the `syncAppearance` function to apply appearance
changes, with other callbacks/functions calling it instead: much like
what we already do for the macOS apprt.
~~I also took the time to refactor the libadwaita version checks since
calling `versionAtLeast(0, 0, 0)` does get old after a while. Now almost
all checks are given human-readable names and contributors need not
memorize what the relevant version checks all are.~~ Moved to another PR
The GTK side of appearance code is kind of a mess with several different
functions all having the responsibility of interacting with each other
and setting the appropriate window appearance. It should solely be the
responsibility of the `syncAppearance` function to apply appearance
changes, with other callbacks/functions calling it instead: much like
what we already do for the macOS apprt.
refer to #4007
there are also quite a few posts about this in the discord help section
Basically for some reason for the equal key shift is not a typical
modifier where it would be the original key + shift modifier but it
actually just changes equal to respond as the plus key this is the only
key on the keyboard I can see exhibit this behavior
it seems all 3 major operating systems report the key this way (mac,
linux, and windows)
`std.debug.assert(x)` _is not_ the same as `if (!x) unreachable` because
the function call is not `inline`. Since it's not inline the Zig
compiler will try to compile any code that might otherwise be
unreachable.
Also, added a CI test that compiles Ghostty in a Debian 12 container to
ensure that regressions do not happen.
`std.debug.assert(x)` _is not_ the same as `if (!x) unreachable`
because the function call is not `inline`. Since it's not inline the
Zig compiler will try to compile any code that might otherwise be
unreachable.
Also, added a CI test that compiles Ghostty in a Debian 12 container to
ensure that regressions do not happen.
Added snap packaging, fixes#3153
If whoever registered the name in the snap store could add me as a
collaborator, I can handle getting it released in the store, setup
automated builds, and request the necessary classic permissions in the
store.
This commit removes support for building without libadwaita. (Y'all knew
that I just had this sitting in my back pocket). This will need some
serious review to ensure that we haven't lost any functionality.
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.
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.
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.
Add a `+boo` command to show the animation from the website. The data
for the frames is compressed during the build process. This build step
was added to the SharedDeps object because it is used in both
libghostty and in binaries.
The compression is done as follows:
- All files are concatenated together using \x01 as a combining byte
- The files are compressed to a cached build file
- A zig file is written to stdout which `@embedFile`s the compressed
file and exposes it to the importer
- A new anonymous module "framedata" is added in the SharedDeps object
Any file can import framedata and access the compressed bytes via
`framedata.compressed`. In the `boo` command, we decompress the slice
and
split it into frames for use in the animation.
The overall addition to the binary size is 348k.
Add a `+boo` command to show the animation from the website. The data
for the frames is compressed during the build process. This build step
was added to the SharedDeps object because it is used in both
libghostty and in binaries.
The compression is done as follows:
- All files are concatenated together using \x01 as a combining byte
- The files are compressed to a cached build file
- A zig file is written to stdout which `@embedFile`s the compressed
file and exposes it to the importer
- A new anonymous module "framedata" is added in the SharedDeps object
Any file can import framedata and access the compressed bytes via
`framedata.compressed`. In the `boo` command, we decompress the file and
split it into frames for use in the animation.
The overall addition to the binary size is 348k.
Tries to resolve#2509 partially by enabling tab title renaming feature
for MacOS.
* Used the existing set_title action
* Added the set_title action to Bindings so that a keybind can be set
for this feature (defaulted this to cmd+i -- same as iTerm2)
* 3 ways to trigger this feature: "Set Title" option in the right-click
menu for the window, "Set Title" option in the "View" menu in the title
bar, and the keybind (cmd+i default)
* Prevented esc sequences from the different programs from resetting the
tab title once it is set manually using a new state var called
`titleSetManually` in the SurfaceView
* The input box is a basic native macos dialog with "OK" and "Cancel".
Leaving the title text box empty resets the title to automatic / from
the `title` property in the config
**Demo**:
https://github.com/user-attachments/assets/5cacc389-4486-46c4-8cd5-dda347e9c663
Need some feedback. Thanks!
This adds a new script `update-mirror.sh` which generates the proper
blob format for R2 (or any blob storage) to mirror all of our
dependencies.
It doesn't automate updating build.zig.zon but on an ongoing basis this
should be easy to do manually, and we can strive to automate it in the
future.
I omitted iTerm2 color themes because we auto-update that via CI and
updating all of the machinery to send it to our mirror and so on is a
pain. Additionally, this doesn't mirror transitive dependencies because
Zig doesn't have a way to fetch those from a mirror instead (unless you
pre-generate a full cache like packagers but that's not practical for
day to day development).
It's hugely beneficial just to get most of our dependencies mirrored.
This adds a new script `update-mirror.sh` which generates the proper
blob format for R2 (or any blob storage) to mirror all of our
dependencies.
It doesn't automate updating build.zig.zon but on an ongoing basis this
should be easy to do manually, and we can strive to automate it in the
future.
I omitted iTerm2 color themes because we auto-update that via CI and
updating all of the machinery to send it to our mirror and so on is a
pain. Additionally, this doesn't mirror transitive dependencies because
Zig doesn't have a way to fetch those from a mirror instead (unless you
pre-generate a full cache like packagers but that's not practical for
day to day development).
It's hugely beneficial just to get most of our dependencies mirrored.
This fixes a regression in 1.1.1/1.1.2 where our PACKAGING docs mention
using `fetch-zig-cache.sh` but it was removed. This commit adds it back,
generating its contents from the build.zig.zon file (via zon2nix which
we use for our Nix packaging).
For packagers, there are no dependency changes: you still need Zig and
POSIX sh. For release time, Ghostty has a new dependency on `jq` but
otherwise the release process is the same. The check-zig-cache.sh script
is updated to generate the new build.zig.zon.txt file.
This fixes a regression in 1.1.1/1.1.2 where our PACKAGING docs mention
using `fetch-zig-cache.sh` but it was removed. This commit adds it back,
generating its contents from the build.zig.zon file (via zon2nix which
we use for our Nix packaging).
For packagers, there are no dependency changes: you still need Zig and
POSIX sh. For release time, Ghostty has a new dependency on `jq` but
otherwise the release process is the same. The check-zig-cache.sh script
is updated to generate the new build.zig.zon.txt file.
Finishes #378
Supercedes #4159
This adds a new enum value for `macos-non-native-fullscreen`:
`padded-notch`. This value will add padding to the top of the window to
account for the notch on applicable devices while still hiding the menu.
This value is preferred over "visible-menu" by some people because for
screens without a notch, the window will take up the full height.
The plan in the future is that we may color the padded area when a notch
is present. In this commit it appears as transparent.
EDIT: Hah, the video below is pretty useless since the screen recording
software omits the notch. 😄https://github.com/user-attachments/assets/4b1e84dd-3b3a-44a8-a83b-0f51e44f6cf8
Finishes #378
Supercedes #4159
This adds a new enum value for `macos-non-native-fullscreen`:
`padded-notch`. This value will add padding to the top of the window to
account for the notch on applicable devices while still hiding the
menu.
This value is preferred over "visible-menu" by some people because for
screens without a notch, the window will take up the full height.
The plan in the future is that we may color the padded area when a notch
is present. In this commit it appears as transparent.
> Please read through [the Discussion rules](https://github.com/ghostty-org/ghostty/discussions/6937), review [the FAQs](https://ghostty.org/docs/help#common-issues-and-solutions), and check for both existing [Discussions](https://github.com/ghostty-org/ghostty/discussions?discussions_q=) and [Issues](https://github.com/ghostty-org/ghostty/issues?q=sort%3Areactions-desc) prior to opening a new Discussion.
- type:markdown
attributes:
value:"# Issue Details"
- type:textarea
attributes:
label:Issue Description
description:|
Provide a detailed description of the issue. Include relevant information, such as:
- The feature or configuration option you encounter the issue with.
- Screenshots, screen recordings, or other supporting media (as needed).
- If this is a regression of an existing issue that was closed or resolved, please include the previous item reference (Discussion, Issue, PR, commit) in your description.
> [!TIP]
> **Not sure what information to include?**
> Here are some recommendations:
> - **Input issues:** include your keyboard layout, a screenshot of logged keystrokes from the terminal inspector's "Keyboard" tab (Linux: <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>i</kbd>; MacOS: <kbd>cmd</kbd>+<kbd>alt</kbd>+<kbd>i</kbd>), input method, Linux input method engine (IBus, Fcitx 5, or none) and its version.
> - **Font issues:** include the problematic character(s), the output of `ghostty +show-face` for these character(s), and if they work in other applications.
> - **Terminal emulation issues (including image rendering issues):** attach an [asciinema](https://docs.asciinema.org/getting-started/) cast file, shell script, or text file for reproduction.
> - **Renderer issues:** (Linux) include your OpenGL version, graphics card, driver version.
> - **Crashes:** (macOS) include the [Sentry UUID](https://github.com/ghostty-org/ghostty?tab=readme-ov-file#crash-reports); (Linux) try to reproduce using a debug build and provide the stack trace.
placeholder:|
When using SSH to connect to my remote Linux machine from my local macOS device in Ghostty, I try to run `clear`, and the screen does not clear. Instead, I see the following error message printed to the terminal: `Error opening terminal:xterm-ghostty.`
validations:
required:true
- type:textarea
attributes:
label:Expected Behavior
description:|
Describe how you expect Ghostty to behave in this situation. Include any relevant documentation links.
placeholder:|
The screen is cleared and the prompt is redrawn at the top of the window.
validations:
required:true
- type:textarea
attributes:
label:Actual Behavior
description:|
Describe how Ghostty actually behaves in this situation. If it is not immediately obvious how the actual behavior differs from the expected behavior described above, please be sure to mention the deviation specifically.
placeholder:|
The screen is not cleared, and an error is printed: `Error opening terminal:xterm-ghostty`.
validations:
required:true
- type:textarea
attributes:
label:Reproduction Steps
description:|
Provide a detailed set of step-by-step instructions for reproducing this issue.
placeholder:|
1. Open Ghostty.
2. Connect to a remote server via SSH.
3. Try to execute `clear`.
4. Observe `xterm-ghostty` error message above.
validations:
required:true
- type:textarea
attributes:
label:Ghostty Logs
description:|
Provide any captured Ghostty logs or stacktraces during your issue reproduction in this field. On Linux, logs can be found by running `ghostty` from the command-line; on macOS, logs can be viewed with `sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'` from another terminal emulator.
render:text
- type:textarea
attributes:
label:Ghostty Version
description:Paste the output of `ghostty +version` here.
placeholder:|
Ghostty 1.1.3
Version
- version:1.1.3
- channel:stable
Build Config
- Zig version:0.13.0
- build mode :builtin.OptimizeMode.ReleaseFast
- app runtime:apprt.Runtime.none
- font engine:font.main.Backend.coretext
- renderer :renderer.Metal
- libxev :main.Backend.kqueue
render:text
validations:
required:true
- type:input
attributes:
label:OS Version Information
description:|
Please tell us what operating system (name and version) you are using.
placeholder:Ubuntu 24.04.1 (Noble Numbat)
validations:
required:true
- type:dropdown
attributes:
label:(Linux only) Display Server
description:|
If you run Linux, please tell us if you use X11 or Wayland. If you aren't sure, you can determine this by running `[ -z "$WAYLAND_DISPLAY" ] && echo X11 || echo Wayland`.
If you run Linux, please tell us what Desktop Environment/Window Manager you are using (include the name and version).
placeholder:GNOME 47.4
validations:
required:false
- type:textarea
attributes:
label:Minimal Ghostty Configuration
description:|
Please provide the **minimum** configuration needed to reproduce this issue. If you can still reproduce the issue with one of the lines removed, do not include that line. If and **only** if you are not able to determine this, paste the contents of your Ghostty configuration file here.
placeholder:|
font-family = CommitMono Nerd Font
font-family-bold = CommitMono Nerd Font
font-family-italic = CommitMono Nerd Font
font-family-bold-italic = CommitMono Nerd Font
font-feature = +cv07
font-size = 16
font-thicken = true
theme = catppuccin-mocha
render:ini
validations:
required:true
- type:textarea
attributes:
label:Additional Relevant Configuration
description:|
If your issue involves other programs, tools, or applications in addition to Ghostty (e.g. Neovim, tmux, Zellij, etc.), please provide the minimum configuration and versions needed for all relevant programs to reproduce the issue here. If you use custom CSS or shaders for Ghostty, also include them here, if applicable to your issue.
placeholder:|
#### `tmux.conf` (tmux 3.5a)
```
set -g default-terminal "tmux-256color"
set-option -sa terminal-overrides ",xterm*:Tc"
set -g base-index 1
setw -g pane-base-index 1
```
validations:
required:false
- type:markdown
attributes:
value:|
# User Acknowledgements
> [!TIP]
> Use these links to review the existing Ghostty [Discussions](https://github.com/ghostty-org/ghostty/discussions?discussions_q=) and [Issues](https://github.com/ghostty-org/ghostty/issues?q=sort%3Areactions-desc).
- type:checkboxes
attributes:
label:"I acknowledge that:"
options:
- label:I have reviewed the FAQ and confirm that my issue is NOT among them.
required:true
- label:I have searched the Ghostty repository (both open and closed Discussions and Issues) and confirm this is not a duplicate of an existing issue or discussion.
required:true
- label:I have checked the "Preview" tab on all text fields to ensure that everything looks right, and have wrapped all configuration and code in code blocks with a group of three backticks (` ``` `) on separate lines.
@ -86,6 +241,32 @@ pull request will be accepted with a high degree of certainty.
> working on Ghostty.** If you're a user reporting an issue, you can
> ignore the rest of this document.
## Including and Updating Translations
See the [Contributor's Guide](po/README_CONTRIBUTORS.md) for more details.
## Checking for Memory Leaks
While Zig does an amazing job of finding and preventing memory leaks,
Ghostty uses many third-party libraries that are written in C. Improper usage
of those libraries or bugs in those libraries can cause memory leaks that
Zig cannot detect by itself.
### On Linux
On Linux the recommended tool to check for memory leaks is Valgrind. The
recommended way to run Valgrind is via `zig build`:
```sh
zig build run-valgrind
```
This builds a Ghostty executable with Valgrind support and runs Valgrind
with the proper flags to ensure we're suppressing known false positives.
You can combine the same build args with `run-valgrind` that you can with
`run`, such as specifying additional configurations after a trailing `--`.
## Input Stack Testing
The input stack is the part of the codebase that starts with a
@ -245,7 +426,7 @@ recreated the next time you run the VM.
We welcome the contribution of new VM definitions, as long as they meet the following criteria:
1. The should be different enough from existing VM definitions that they represent a distinct
1. They should be different enough from existing VM definitions that they represent a distinct
user (and developer) experience.
2. There's a significant Ghostty user population that uses a similar environment.
3. The VMs can be built using only packages from the current stable NixOS release.
@ -257,3 +438,60 @@ We welcome the contribution of new VM definitions, as long as they meet the foll
2. VMs should not expose any services to the network, or run any remote access
software like SSH daemons, VNC or RDP.
3. VMs should auto-login using the "ghostty" user.
## Nix VM Integration Tests
Several Nix VM tests are provided by the project for testing Ghostty in a "live"
environment rather than just unit tests.
Running these requires a working Nix installation, either Nix on your
favorite Linux distribution, NixOS, or macOS with nix-darwin installed. Further
requirements for macOS are detailed below.
### Linux
1. Check out the Ghostty source and change to the directory.
2. Run `nix run .#check.<system>.<test-name>.driver`. `<system>` should be
`x86_64-linux` or `aarch64-linux` (even on macOS, this launches a Linux
VM, not a macOS one). `<test-name>` should be one of the tests defined in
`nix/tests.nix`. The test will build and then launch. Depending on the speed
of your system, this can take a while. Eventually though the test should
complete. Hopefully successfully, but if not error messages should be printed
out that can be used to diagnose the issue.
3. To run _all_ of the tests, run `nix flake check`.
### macOS
1. To run the VMs on macOS you will need to enable the Linux builder in your `nix-darwin`
config. This _should_ be as simple as adding `nix.linux-builder.enable=true` to your
configuration and then rebuilding. See [this](https://nixcademy.com/posts/macos-linux-builder/)
blog post for more information about the Linux builder and how to tune the performance.
2. Once the Linux builder has been enabled, you should be able to follow the Linux instructions
above to launch a test.
### Interactively Running Test VMs
To run a test interactively, run `nix run
.#check.<system>.<test-name>.driverInteractive`. This will load a Python console
that can be used to manage the test VMs. In this console run `start_all()` to
start the VM(s). The VMs should boot up and a window should appear showing the
VM's console.
For more information about the Nix test console, see [the NixOS manual](https://nixos.org/manual/nixos/stable/index.html#sec-call-nixos-test-outside-nixos)
### SSH Access to Test VMs
Some test VMs are configured to allow outside SSH access for debugging. To
This document describes the technical details behind Ghostty's development.
If you'd like to open any pull requests or would like to implement new features
into Ghostty, please make sure to read our ["Contributing to Ghostty"](CONTRIBUTING.md)
document first.
To start development on Ghostty, you need to build Ghostty from a Git checkout,
which is very similar in process to [building Ghostty from a source tarball](http://ghostty.org/docs/install/build). One key difference is that obviously
you need to clone the Git repository instead of unpacking the source tarball:
```shell
git clone https://github.com/ghostty-org/ghostty
cd ghostty
```
> [!NOTE]
>
> Ghostty may require [extra dependencies](#extra-dependencies)
> when building from a Git checkout compared to a source tarball.
> Tip versions may also require a different version of Zig or other toolchains
> (e.g. the Xcode SDK on macOS) compared to stable versions — make sure to
> follow the steps closely!
When you're developing Ghostty, it's very likely that you will want to build a
_debug_ build to diagnose issues more easily. This is already the default for
Zig builds, so simply run `zig build`**without any `-Doptimize` flags**.
There are many more build steps than just `zig build`, some of which are listed
read by most of the popular AI agents to help produce higher quality
results.
We also provide commands in `.agents/commands` that have some vetted
prompts for common tasks that have been shown to produce good results.
We provide these to help reduce the amount of time a contributor has to
spend prompting the AI to get good results, and hopefully to lower the slop
produced.
- `/gh-issue <number/url>` - Produces a prompt for diagnosing a GitHub
issue, explaining the problem, and suggesting a plan for resolving it.
Requires `gh` to be installed with read-only access to Ghostty.
> [!WARNING]
>
> All AI assistance usage [must be disclosed](https://github.com/ghostty-org/ghostty/blob/main/CONTRIBUTING.md#ai-assistance-notice)
> and we expect contributors to understand the code that is produced and
> be able to answer questions about it. If you don't understand the
> code produced, feel free to disclose that, but if it has problems, we
> may ask you to fix it and close the issue. It isn't a maintainers job to
> review a PR so broken that it requires significant rework to be acceptable.
## Logging
Ghostty can write logs to a number of destinations. On all platforms, logging to
`stderr` is available. Depending on the platform and how Ghostty was launched,
logs sent to `stderr` may be stored by the system and made available for later
retrieval.
On Linux if Ghostty is launched by the default `systemd` user service, you can use
`journald` to see Ghostty's logs: `journalctl --user --unit app-com.mitchellh.ghostty.service`.
On macOS logging to the macOS unified log is available and enabled by default.
Use the system `log` CLI to view Ghostty's logs: `sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'`.
Ghostty's logging can be configured in two ways. The first is by what
optimization level Ghostty is compiled with. If Ghostty is compiled with `Debug`
optimizations debug logs will be output to `stderr`. If Ghostty is compiled with
any other optimization the debug logs will not be output to `stderr`.
Ghostty also checks the `GHOSTTY_LOG` environment variable. It can be used
to control which destinations receive logs. Ghostty currently defines two
destinations:
- `stderr` - logging to `stderr`.
- `macos` - logging to macOS's unified log (has no effect on non-macOS platforms).
Combine values with a comma to enable multiple destinations. Prefix a
destination with `no-` to disable it. Enabling and disabling destinations
can be done at the same time. Setting `GHOSTTY_LOG` to `true` will enable all
destinations. Setting `GHOSTTY_LOG` to `false` will disable all destinations.
## Linting
### Prettier
Ghostty's docs and resources (not including Zig code) are linted using
[Prettier](https://prettier.io) with out-of-the-box settings. A Prettier CI
check will fail builds with improper formatting. Therefore, if you are
modifying anything Prettier will lint, you may want to install it locally and
run this from the repo root before you commit:
```
prettier --write .
```
Make sure your Prettier version matches the version of Prettier in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
Nix users can use the following command to format with Prettier:
```
nix develop -c prettier --write .
```
### Alejandra
Nix modules are formatted with [Alejandra](https://github.com/kamadorueda/alejandra/). An Alejandra CI check
will fail builds with improper formatting.
Nix users can use the following command to format with Alejandra:
```
nix develop -c alejandra .
```
Non-Nix users should install Alejandra and use the following command to format with Alejandra:
```
alejandra .
```
Make sure your Alejandra version matches the version of Alejandra in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
### Updating the Zig Cache Fixed-Output Derivation Hash
> purposely contain sensitive information, but it does contain the full
> stack memory of each thread at the time of the crash. This information
> is used to rebuild the stack trace but can also contain sensitive data
> depending when the crash occurred.
## Developing Ghostty
See the documentation on the Ghostty website for
[building Ghostty from source](http://ghostty.org/docs/install/build).
For development, omit the `-Doptimize` flag to build a debug build.
On Linux or macOS, you can use `zig build -Dapp-runtime=glfw run` for a quick
GLFW-based app for a faster development cycle while developing core
terminal features. Note that this app is missing many features and is also
known to crash in certain scenarios, so it is only meant for development
tasks.
Other useful commands:
- `zig build test` for running unit tests.
- `zig build test -Dtest-filter=<filter>` for running a specific subset of those unit tests
- `zig build run -Dconformance=<name>` runs a conformance test case from
the `conformance` directory. The `name` is the name of the file. This runs
in the current running terminal emulator so if you want to check the
behavior of this project, you must run this command in Ghostty.
### Linting
#### Prettier
Ghostty's docs and resources (not including Zig code) are linted using
[Prettier](https://prettier.io) with out-of-the-box settings. A Prettier CI
check will fail builds with improper formatting. Therefore, if you are
modifying anything Prettier will lint, you may want to install it locally and
run this from the repo root before you commit:
```
prettier --write .
```
Make sure your Prettier version matches the version of Prettier in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
Nix users can use the following command to format with Prettier:
```
nix develop -c prettier --write .
```
#### Alejandra
Nix modules are formatted with [Alejandra](https://github.com/kamadorueda/alejandra/). An Alejandra CI check
will fail builds with improper formatting.
Nix users can use the following command to format with Alejanda:
```
nix develop -c alejandra .
```
Non-Nix users should install Alejandra and use the following command to format with Alejandra:
```
alejandra .
```
Make sure your Alejandra version matches the version of Alejandra in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
#### Updating the Zig Cache Fixed-Output Derivation Hash