- Expose that ID as the environment variable GHOSTTY_SURFACE_ID to
processes running in Ghostty surfaces.
- Add a function to the core app to search for surfaces by ID.
- ID is randomly generated, it has no other meaning other than as a
unique identifier for the surface. The ID also cannot be zero as that
is used to indicate a null ID in some situations.
This adds a function to the core surface to get process information
about the process(es) running in the terminal. Currently supported is
the PID of the foreground process and the name of the slave PTY.
If there is an error retrieving the information, or the platform does
not support retieving that information `null` is returned.
This will be useful in exposing the foreground PID and slave PTY name to
AppleScript or other APIs.
TL;DR: this description is (intentionally) nonsense but I ran
`\b(\w+)\s\1\b` over `src` and stole a singular typo fix from #11528.
Replacement of #11528 with 100% less slop and 99% less AI; I didn't feel
like saying no to free(ish) typo checking. Note that many of the fixes
there were outright incorrect (and clearly had no review from sentient
lifeforms, contrary to its—sorry, it's—description). A lot of extra
double words were caught with a handy `rg --pcre2 '\b(\w+)\s+\1\b' src`;
you could say this PR was “ripgrep-assisted” the way that one was
“AI-assisted”. Rather ironic since that PR also claims to have used grep
via Claude Code, but missed a lot of them.
The its → it's changes from that PR were elided; I decided to run a `rg
"\bit'?s\b" src`, but someone REALLY likes their its, so I reverted my
changes as there were an extremely large number of changes (probably a
hundred files with multiple hundred cases). The only other change was
“baout” → “about”.
# AI Usage
Claude Code was used by proxy for finding baout. Claude Code was used by
proxy for realizing that the correct spelling is about. Claude Code was
not used for fixing it. Oh my god it was so difficult to fix, the
original PR had it so easy. I had to type out the file name (fish's AI
sorry I mean autocomplete helped though) and like, type /baout, press R,
press ab, then save and exit. This is so difficult you know we should
use an AI for this, like this is so hard I don't know how people manage.
All changes were verified by me: I consulted the dictionary to delve
into double-checkment of “in existence; being in evidence; apparent.”
Uhhh insert assorted other AI impersonation here maybe? THE LLM IN ME
WANTS TO ESCAPE PLEASE HELP
Move mouse event encoding logic from Surface.zig into a new
input/mouse_encode.zig file.
The new file encapsulates event filtering (shouldReport),
button code computation, viewport bounds checking, motion
deduplication, and all five wire formats (X10, UTF-8, SGR,
urxvt, SGR-pixels). This makes the encoding independently
testable and adds unit tests covering each format and edge
case.
Additionally, Surface `mouseReport` can no longer fail, since the only
failure mode is no buffer space which should be impossible. Updated
the signature to remove the error set.
Change `window-padding-balance` from `bool` to an enum with three
values:
- `false` - no balancing (default, unchanged)
- `true` - balance with vshift that caps top padding and shifts excess
to bottom (existing behavior, unchanged)
- `equal` - balance whitespace equally on all four sides
This gives users who prefer truly equal padding a way to opt in without
changing the default behavior.
Fixes#11316
This mirrors the `prompt` actions (hence why there is no window action
here) and enables setting titles via keybind actions which importantly
lets this work via command palettes, App Intents, AppleScript, etc.
Fixes#11336
Introduce a proper WorkingDirectory tagged union type with home, inherit,
and path variants. The field is now an optional (?WorkingDirectory) where
null represents "use platform default" which is resolved during Config.finalize
to .inherit (CLI) or .home (desktop launcher).
As discussed in Discord, this commit drops the `ConfigOverride` object
in favor of a simpler method of passing the overrides around. Completely
avoiding changes to the core wasn't possible but it's very minimal now.
Fixes: #8862Fixes: #10716
This adds the machinery to pass configuration settings received over
DBus down to the GObject Surface so that that configuration information
can be used to override some settings from the current "live" config
when creating a new window. Currently it's only possible to override
`--working-directory` and `--command`. `-e` on the `ghostty +new-window`
CLI works as well.
Adding more overridable settings is possible, but being able to fully
override any possible setting would better be served with a major
revamp of how Ghostty handles configs, which I is way out of scope at
the moment.
Fixes#7937
Added `computeInitialSize` to GTK `Surface` and call it in GTK
`Application` before the first `present()`, so the window manager
centers the correct size on initial show.
The issue occurs because the core `Surface.recomputeInitialSize()` runs
only after the renderer is initialized. In GTK, the `GLArea` isn’t
realized until after `present()`, so the initial size arrives too late
for WM centering.
**Limitations**: when we precompute size before `present()` we do not
have access to padding, so the sizing will be very slightly off... but
since it is only off a few pixels I was unable to tell visually that it
wasn't perfectly centered.
**Other thoughts**: I was hesitant to make changes to core `Surface`
because the issue is Linux-specific, but it may make sense to extract a
helper from `recomputeInitialSize` to avoid duplicating the sizing math.
**AI Disclosure:** I used AI to explore the project, help with any
language / API questions (I've never used zig before and rarely use
gtk), and make implementation suggestions.
When cursor-click-to-move is set to false, disable all prompt
click-to-move mechanisms including shell-native methods such as OSC 133
cl= (arrow key synthesis) and click_events.
I forgot to port this config over when we did the OSC133 stuff.
Also update the config documentation to accurately describe the current
behavior.
Fixes#11138
This changes the way Ghostty assigns itself and subprocesses to
cgroups and how resource controls are applied.
* Ghostty itself no longer modifies it's own cgroup or moves itself
to a transient scope. To modify the main Ghostty process' resource
controls ensure that you're launching Ghostty with a systemd unit and
use the standard systemd methods for overriding and applying changes
to systemd units.
* If configured (on by default), the process used to run your command
will be moved to a transient systemd scope after it is forked from
Ghostty but before the user's command is executed. Resource controls
will be applied to the transient scope at this time. Changes to
the `linux-cgroup*` configuration entries will not alter existing
commands. If changes are made to the `linux-cgroup*` configuration
entries commands will need to be relaunched. Resource limits can also
be modified after launch outside of Ghostty using systemd tooling. The
transient scope name can be shown by running `systemctl --user whoami`
in a shell running inside Ghostty.
Fixes#2084.
Related to #6669
The `cursor` shell feature always used a blinking bar (beam), often to
the surprise of users who set `cursor-style-blink = false`.
This change extends our GHOSTTY_SHELL_FEATURES format to include either
`cursor:blink` (default) or `cursor:steady` based on cursor-style-blink
when the `cursor` feature is enabled, and all shell integrations have
been updated to use that additional information to choose the DECSCUSR
cursor value (5=blinking bar, 6=steady bar).
I also considered passing a DECSCUSR value in GHOSTTY_SHELL_FEATURES
(e.g. `cursor:5`). This mostly worked well, but zsh also needs the blink
state on its own for its block cursor. We also don't support any other
shell feature cursor configurability (e.g. the shape), so this was an
over generalization.
This does change the behavior for users who like the blinking bar in the
shell but have `cursor-blink-style = false` for other reasons. We could
provide additional `cursor` shell feature configurability (e.g.
`cursor:blink` in `shell-integration-features`), but I'll propose that
as its own change.
See: #2812Closes: #8681
---
**AI Disclosure:** I did a lot of rubber ducking with Claude Code while
trying out various ideas. It was particularly useful for this kind of
feature because I could try out one thing and have it evaluate the
impact on all of the shell integration scripts at once.
When a user renames a surface via "Change Terminal Title" and then
uses copy_title_to_clipboard, the raw terminal title was copied
instead of the custom name.
This adds a new `copy_title` apprt action so each platform resolves
the effective title (user override or terminal-set) before copying
to clipboard.
Fixes#10345
The `cursor` shell feature always used a blinking bar (beam), often to
the surprise of users who set `cursor-style-blink = false`.
This change extends our GHOSTTY_SHELL_FEATURES format to include either
`cursor:blink` (default) or `cursor:steady` based on cursor-style-blink
when the `cursor` feature is enabled, and all shell integrations have
been updated to use that additional information to choose the DECSCUSR
cursor value (5=blinking bar, 6=steady bar).
I also considered passing a DECSCUSR value in GHOSTTY_SHELL_FEATURES
(e.g. `cursor:5`). This mostly worked well, but zsh also needs the blink
state on its own for its block cursor. We also don't support any other
shell feature cursor configurability (e.g. the shape), so this was an
over generalization.
This does change the behavior for users who like the blinking bar in the
shell but have `cursor-blink-style = false` for other reasons. We could
provide additional `cursor` shell feature configurability, but I think
that's best left to a separate change.
The reporting of color scheme was handled asynchronously by queuing a
handler in the surface. This could lead to race conditions where the DSR
is reported after subsequent VT sequences.
Fixes#5922
- Change all codepoint types from u32 to u21 to align with Zig stdlib
- Update ArrayList to use Zig 0.15 unmanaged pattern (.empty)
- Remove unnecessary @intCast when encoding UTF-8
- Fix formatEntry to use stack-allocated buffer
Refactor the selection-word-chars implementation to parse UTF-8 boundary
characters once during config initialization instead of on every selection
operation.
Changes:
- Add SelectionWordChars type that stores pre-parsed []const u32 codepoints
- Parse UTF-8 to codepoints in parseCLI() during config load
- Remove UTF-8 parsing logic from selectWord() hot path (27 lines removed)
- Remove arbitrary 64-character buffer limit
- Update selectWord() and selectWordBetween() to accept []const u32
- Update DerivedConfig to store codepoints directly
- Update all tests to use codepoint arrays
Benefits:
- No runtime UTF-8 parsing overhead on every selection
- No arbitrary character limit (uses allocator instead)
- Cleaner separation of concerns (config handles parsing, selection uses data)
- Better performance in selection hot path
Add new `selection-word-chars` config option to customize which characters
mark word boundaries during text selection operations (double-click, word
selection, etc.). Similar to zsh's WORDCHARS environment variable, but
specifies boundary characters rather than word characters.
Default boundaries: ` \t'"│`|:;,()[]{}<>$`
Users can now customize word selection behavior, such as treating
semicolons as part of words or excluding periods from boundaries:
selection-word-chars = " \t'\"│`|:,()[]{}<>$"
Changes:
- Add selection-word-chars config field with comprehensive documentation
- Modify selectWord() and selectWordBetween() to accept boundary_chars parameter
- Parse UTF-8 boundary string to u32 codepoints at runtime
- Update all call sites in Surface.zig and embedded.zig
- Update all test cases to pass boundary characters
The reporting of color scheme was handled asynchronously by queuing a
handler in the surface. This could lead to race conditions where the
DSR is reported after subsequent VT sequences.
Fixes#5922
This is a solution for #2107.
When a user right-clicks, and there's no existing selection, the
existing behavior is to try to select the word under the cursor:
3548acfac6/src/Surface.zig (L3740-L3742)
This PR tweaks that behavior _slightly_: If there's a link under our
cursor, as determined by `linkAtPos`, select the link (to copy with the
right-click context menu). Otherwise, select the word as before.
As noted in #2107, this matches the behavior of iTerm and Gnome
Terminal.
It's worth noting that `linkAtPos` already does the right thing in terms
of checking the links from config and their highlight/hover states
(modified by Ctrl or Super depending on platform).
3548acfac6/src/Surface.zig (L3896-L3901)
It also therefore respects `link-url` from config.
3548acfac6/src/config/Config.zig (L3411-L3416)
By using `linkAtPos`, we get all that behavior for free. In practical
terms, that means:
- If I'm holding Ctrl so a link is underlined and I right click on it,
it selects the underlined link.
- If I'm not holding Ctrl and I right click on a link that is not
underlined, it selects the word as before.
- This behavior respects per-platform key bindings and user config
settings.
`linkAtPos` requires that the render state mutex is held. I believe it's
safe to call because we're inside a block holding the mutex:
3548acfac6/src/Surface.zig (L3702-L3704)
**AI Disclosure:** I used Gemini CLI to help me with this PR because
while I have many years of programming experience, this is my first time
writing Zig. I prototyped a couple different approaches with AI before
landing on this one, so AI generated various prototypes and I chose the
final imlementation. I've verified that my code compiles and works as
intended.
When double-clicking text, first check if the position is part of a URL
using the default URL regex pattern. If a URL is detected, select the
entire URL instead of just the word.
This follows the feedback from PR #2324 to modify the selection behavior
rather than introducing a separate selectLink function. The implementation
uses the existing URL regex from config/url.zig which already handles
various URL schemes (http, https, ftp, ssh, etc.) and file paths.
The URL detection runs before the normal word selection, falling back to
selectWord if no URL is found at the clicked position.
Adds the `selection_for_search` action, with Cmd+E keybind by default.
This action inputs the currently selected text into the search
field without changing focus, matching standard macOS behavior.
End the currently active key sequence, if any, and flush the
keys up to this point to the terminal, excluding the key that
triggered this action.
For example: `ctrl+w>escape=end_key_sequence` would encode
`ctrl+w` to the terminal and exit the key sequence.
Normally, an invalid sequence will reset the key sequence and
flush all data including the invalid key. This action allows
you to flush only the prior keys, which is useful when you want
to bind something like a control key (`ctrl+w`) but not send
additional inputs.