## Problem
Current `Se` sequence (reset cursor style) is `\E[2 q`, which always
sets steady block, regardless of user config.
## Solution
Update sequence to `\E[0 q`, which sets the cursor style to the user
configured default cursor.
fix https://github.com/ghostty-org/ghostty/issues/12482
Helps with neovim issue: https://github.com/neovim/neovim/issues/38987
## AI Disclosure
I didn't use AI for this, haha. Unless you count random questions to
learn about terminfo beforehand, but I relied on [legit
resources](https://invisible-island.net/xterm/terminfo.html) for real
info. It says:
> Se resets the cursor style to the terminal power-on default.
I think the useful interpretation is to set the user configured default.
`allocTmpDir` previously read `%TMP%` via `getenvW` and returned `null`
if the variable wasn't set, requiring each caller to to deal with the
nullable. Unfortunately, there isn't a platform-neutral default value
that makes sense for those cases (i.e. `/tmp` is POSIX-y).
We now use `GetTempPathW` on Windows, which is the official way to get
this directory: `TMP` → `TEMP` → `USERPROFILE` → `GetWindowsDirectoryW`.
With a real system call behind it, the function no longer needs to be
nullable: the only remaining failure modes are OOM (propagated) and the
syscall itself failing or returning data we can't decode. In those later
cases, we use `C:\Windows\Temp` as a fallback, similar to how we use
`/tmp` in the POSIX case.
The Windows path always allocates so it still must be paired with
`freeTmpDir`, which matches the existing contract.
---
*AI Disclosure:* I verified the Windows path using Claude and Zig's
cross-compilation capabilities because I don't have a Windows
environment in which to test this. I do fully understand the code based
on my prior life as a Windows game developer though.
This makes sure that if Ghostty crashes, commands spawned are also
terminated automatically by the Flatpak Session Helper.
The few crashes I got left a lot of background processes, some of them
pretty heavy and took awhile to be figured out.
`allocTmpDir` previously read `%TMP%` via `getenvW` and returned `null`
if the variable wasn't set, requiring each caller to to deal with the
nullable. Unfortunately, there isn't a platform-neutral default value
that makes sense for those cases (i.e. `/tmp` is POSIX-y).
We now use `GetTempPathW` on Windows, which is the official way to get
this directory: `TMP` → `TEMP` → `USERPROFILE` → `GetWindowsDirectoryW`.
With a real system call behind it, the function no longer needs to be
nullable: the only remaining failure modes are OOM (propagated) and the
syscall itself failing or returning data we can't decode. In those later
cases, we use `C:\Windows\Temp` as a fallback, similar to how we use
`/tmp` in the POSIX case.
The Windows path always allocates so it still must be paired with
`freeTmpDir`, which matches the existing contract.
Holding the renderer state mutex is a documented precondition of
`processLinks`, but `mouseButtonCallback` previously called the function
without the mutex.
This creates a race with the I/O thread's `processOutput`, which can
prune scrollback pages while `processLinks` is reading them, resulting
in a use-after-free segfault. See
https://github.com/ghostty-org/ghostty/discussions/12409 (Linux: crash
while selecting text).
57b5e1e250/src/Surface.zig (L4354-L4355)57b5e1e250/src/Surface.zig (L3822-L3824)995e4e375 (os: open) changed the body of `processLinks` to be
non-trivial and documented the precondition, but the lock was not held
at the call site.
This should be safe to delete now after #12461.
I tested saving 27 tabs, 4 with 2 splits,
`TerminalRestorable.encode(with:` finished successfully.
And I check the breakpoints when the Sparkle sends
`-[NSRunningApplication treminate]`. The call stack at `-[NSResponder
invalidateRestorableState]` is pretty much the same as quitting via
`cmd+q`.
First two commits fix the issue when upgrading from 1.2.x to 1.3.x.
(#11304)
> To double check if this pr really fixes the issue, you can either
archive a release build, sign with the same profile, and override
manually.
>
> Or you can find the `savedState` files (located in `~/Library/Daemon\
Containers/<uuid>`), can copy them the local build dir (which is what I
did), and run the debug build.
Following commits add tests for migrations and some logs.
**Currently the minimum version is set to 1.2.x**, since there's a lot
changes comparing to 1.1.x. It will be difficult to restore
`Ghostty.SplitNode` -> `SplitTree<Ghostty.SurfaceView>` without
introducing a lot of checks.
Factor TempDir's name generation into a reusable `randomBasename` (16
random bytes, url-safe base64) and add `randomTmpPath` on top, which
composes `allocTmpDir` + `randomBasename` into a single allocated path
in the form `{TMPDIR}/{prefix}{random}` (`mktemp(1)`-ish).
This is convenient for callers who want a unique path under TMPDIR (for
a temporary file, socket, etc.) without having to think about basename
buffer sizing or path joining.
Also, use `std.base64.url_safe_no_pad.Encoder` instead of the custom
base64 alphabet, which is exactly equivalent.
Factor TempDir's name generation into a reusable `randomBasename` (16
random bytes, url-safe base64) and add `randomTmpPath` on top, which
composes `allocTmpDir` + `randomBasename` into a single allocated path
in the form `{TMPDIR}/{prefix}{random}` (mktemp(1)-ish).
This is convenient for callers who want a unique path under TMPDIR (for
a temporary file, socket, etc.) without having to think about basename
buffer sizing or path joining.
Also, use `std.base64.url_safe_no_pad.Encoder` instead of the custom
base64 alphabet, which is exactly equivalent.
Link detection currently expands the clicked location to a full line
before running the configured regexes. When semantic prompt markers are
present, this can cause prompt text and neighboring content to be
matched together even though they are distinct semantic regions.
Use semantic prompt boundaries when selecting the text to inspect for
link matching. This keeps prompt text separate from the content beside
it and avoids folding prompt text into double-click link/path selection.
Add a regression test that models a prompt and command on the same line
and verifies the prompt region and input region remain separate.
----
this is a fix for the issue users reported in
https://github.com/ghostty-org/ghostty/discussions/11415
**disclaimer**: I used codex addon within Cursor to research this
bug/regression and find a proper fix for it.
> Re-submitting #11857, which was auto-closed pre-vouch. I'm now in the
vouched list.
## Summary
Adds default keybinds for `move_tab` on GTK/Linux, matching the
idiomatic Linux convention used by Firefox, GNOME Terminal, and VSCode:
- **`Ctrl+Shift+PageUp`** → `move_tab:-1` (move tab left)
- **`Ctrl+Shift+PageDown`** → `move_tab:1` (move tab right)
To free these keys, `jump_to_prompt` is reassigned to `Ctrl+Shift+Arrow
Up/Down`, which:
- Mirrors the macOS default (`Cmd+Shift+Arrow Up/Down`)
- Is currently unbound on GTK
- Maintains directional consistency (up = previous, down = next)
The new `move_tab` bindings use `putFlags` with `performable: true` to
match the pattern used by other tab actions (`previous_tab`,
`next_tab`).
Closes#4998
## Changes
- `src/config/Config.zig`: Reassign `jump_to_prompt` from
`Ctrl+Shift+PageUp/PageDown` to `Ctrl+Shift+Arrow Up/Down` (GTK only)
- `src/config/Config.zig`: Add `move_tab` default keybinds as
`Ctrl+Shift+PageUp/PageDown` (GTK only)
## Test plan
- [ ] Build on Linux/GTK: `zig build`
- [ ] Verify `Ctrl+Shift+PageUp` moves current tab left
- [ ] Verify `Ctrl+Shift+PageDown` moves current tab right
- [ ] Verify `Ctrl+Shift+Arrow Up` jumps to previous prompt
- [ ] Verify `Ctrl+Shift+Arrow Down` jumps to next prompt
- [ ] Verify `Ctrl+PageUp/PageDown` still switches tabs (unchanged)
- [ ] Verify macOS keybinds are unaffected
Reassign jump_to_prompt from Ctrl+Shift+PageUp/PageDown to
Ctrl+Shift+Arrow Up/Down on GTK, freeing the idiomatic Linux
keybinds (Ctrl+Shift+PageUp/PageDown) for move_tab.
This matches the tab-moving keybinds used by Firefox, GNOME Terminal,
and VSCode. The new jump_to_prompt binding mirrors the macOS pattern
(Cmd+Shift+Arrow Up/Down).
Closes#4998
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes#11461
- Send Korean IME committed text as a separate text-only key event when
arrow-key handling commits preedit text.
- Replay arrow navigation after the committed text is sent. Do not
replay plain Left Arrow to match Terminal.app behavior.
- Manually tested Arrow keys and Opt/Cmd+Arrow during Korean preedit on
macOS.
Implements the behavior from #9788.
Today, middle-click paste always reads from the selection clipboard (or
the
system clipboard on platforms without a selection clipboard). With this
change
it follows `copy-on-select`:
- `true`: selection clipboard (unchanged)
- `clipboard`: system clipboard
- `false`: selection clipboard (also unchanged, preserves traditional
X11
middle-click behavior)
The idea is symmetry: if `copy-on-select = clipboard` writes selections
to the
system clipboard, middle-click should come back from there too.
Also updated the config doc comment, which previously claimed
middle-click
"will always use the selection clipboard".
### Testing
`zig build test` passes locally (macOS, Zig 0.15.2).
Built and runtime-tested via the fork's CI:
https://github.com/007hacky007/ghostty/actions/runs/24707475544 - I'm
running the resulting binary daily and the three `copy-on-select` modes
behave as described above.
Due to a known Gtk issue, the scrolled_window at the root of the
template is free-ed twice on dispose. This causes crashes when used with
GNOME 49 platform (Gtk 4.20, libadwaita 1.8.5).
Workaround this issue by wrapping the root child in another Adw.Bin,
similar to widgets like ResizeOverlay.
LLM was used to perform discovery against a manually recorded Valgrind
trace, and helped tracking down known fixes for this problem. The
comment in code was taken from another instance in the repository.
Fixes https://github.com/ghostty-org/ghostty/discussions/12306
Assisted-by: OpenAI GPT-5.4
Due to a known Gtk issue, the scrolled_window at the root of the
template is free-ed twice on dispose. This causes crashes when used with
GNOME 49 platform (Gtk 4.20, libadwaita 1.8.5).
Workaround this issue by wrapping the root child in another Adw.Bin,
similar to widgets like ResizeOverlay.
LLM was used to perform discovery against a manually recorded Valgrind
trace, and helped tracking down known fixes for this problem.
Fixes https://github.com/ghostty-org/ghostty/discussions/12306
Assisted-by: OpenAI GPT-5.4