Add an explicit libghostty-vt title-report option and keep CSI 21 t
disabled unless an embedder enables it.
Previously, registering the general PTY write callback also caused the
terminal to echo attacker-controlled window titles. This exposed
embedders to command injection after user interaction. Ghostty fixed
this a long time ago by making CSI 21 t an opt-in in the config. Do the
same but with our C/Zig API.
Add an explicit libghostty-vt title-report option and keep CSI 21 t
disabled unless an embedder enables it.
Previously, registering the general PTY write callback also caused the
terminal to echo attacker-controlled window titles. This exposed
embedders to command injection after user interaction.
Gate the response in the shared terminal stream, append the C API
option without renumbering existing values, and cover the default,
opt-in, and reset behavior in Zig and C API tests.
Handle VS15 width changes when the wide grapheme base is directly under
the cursor.
A zero cursor distance previously underflowed while locating the spacer
tail. Debug builds panicked and ReleaseFast computed an out-of-bounds
cell pointer before updating it.
Closes#12505
This PR allows Ghostty to respond to DECRQM queries for DECECM with the
"permanently reset".
AI disclosure: I used Codex to help inspect the relevant code path and
explain the issue, but I reviewed and made the code changes myself.
Handle VS15 width changes when the wide grapheme base is directly under
the cursor. Cover both disabled wraparound and restored pending-wrap
cursor states.
A zero cursor distance previously underflowed while locating the spacer
tail. Debug builds panicked and ReleaseFast computed an out-of-bounds
cell pointer before updating it.
Find the spacer from the wide base instead of subtracting from the
cursor distance. Reposition the cursor from the base column and clamp it
to the active right margin.
Specifics in each commit message. This will be part of a security
advisory in 1.4.0 since these patches issues related to overflows, DoS,
unbounded memory allocation, etc.
The long-preedit fallback introduced in e95b1707c intentionally
allocated twice. The encoder wrote into an oversized caller-owned buffer
and returned only the written subslice, so transferring it required
manually shrinking the allocation or tracking its original capacity. The
copy kept that rare path simple.
The key encoder moved to std.Io.Writer.Allocating in 44496df899. Its
toOwnedSlice method handles shrinking and ownership transfer, remapping
when the allocator supports it and falling back to an allocation and
copy when it does not. Use it directly for WriteReq.alloc to remove the
guaranteed second allocation while preserving cleanup on failure.
Reclaim pin-backed Kitty graphics placements after their tracked screen
content is pruned. Treat garbage pins as non-renderable until the next
placement command sweeps them.
Placements that scrolled beyond retained history previously remained in
the placement map and tracked-pin set. Long-running graphics output could
accumulate stale state, and remapped garbage pins could appear at an
unrelated fallback location.
Sweep garbage placements before growing the placement map, releasing each
tracked pin while preserving virtual placements. Return no geometry or
visible render position for garbage pins and cover both storage and C API
behavior with regression tests.
Release a Kitty graphics placement's tracked pin before replacement.
Repeated updates to an external placement previously leaked tracked pins.
Pass the owning screen to storage and deinitialize the old placement.
Limit individual allocator requests made by PNG decoders to the Kitty
graphics protocol's 400 MiB image ceiling. Add a reusable allocator
wrapper for callers that need per-request bounds.
PNG decoding previously used Wuffs' 4 GiB package limit and checked
the result only after allocation. A tiny PNG with oversized dimensions
could cause a multi-gigabyte RSS spike before being rejected.
Wrap decoder allocators with LimitedAllocator and translate limit
rejections to invalid image data while preserving genuine out-of-memory
errors. Add allocator boundary tests and regression coverage for a
crafted PNG below Wuffs' limit.
Validate Kitty shared memory byte ranges before mapping and copying
image data. Interpret S as a byte count from O and preserve default
raw-image sizing.
Shared memory transmissions previously multiplied untrusted u32
dimensions before the limit check and sliced mappings with an unchecked
offset. Malformed commands could panic in safe builds or request a
wrapped allocation in fast builds.
Reject oversized dimensions before widening size arithmetic, derive
bounded ranges from the stat size, and enforce max_size before
constructing a slice. Add regression tests for explicit and implicit
offsets, out-of-bounds offsets, and maximum dimensions.
Validate Kitty file transmissions against a canonical path derived from
the open file handle. Keep temporary file policy and cleanup keyed to
that handle path.
Path validation previously ran before opening, so a local cooperating
process could replace a symlink or directory entry and make Ghostty
read a blocklisted file.
Open the submitted path once, derive its canonical path from the handle,
and use the same handle for stat and reads. Add a regression test that
replaces a blocked symlink after open and verifies the pinned target is
still rejected.
The long-preedit fallback introduced in e95b1707c intentionally allocated
twice. The encoder wrote into an oversized caller-owned buffer and returned
only the written subslice, so transferring it required manually shrinking
the allocation or tracking its original capacity. The copy kept that rare
path simple.
The key encoder moved to std.Io.Writer.Allocating in 44496df899. Its
toOwnedSlice method handles shrinking and ownership transfer, remapping when
the allocator supports it and falling back to an allocation and copy when it
does not. Use it directly for WriteReq.alloc to remove the guaranteed second
allocation while preserving cleanup on failure.
Treat Kitty placement dimensions and offsets as untrusted values when
calculating pixel, grid, and rectangle geometry. Saturate results that
do not fit and return no rectangle when missing pixel metrics produces
an empty grid.
Unchecked u32 arithmetic previously panicked in safe builds and wrapped
in fast builds. A zero row count could underflow into a maximum-size
page traversal, while maximum dimensions could spin cursor movement or
overflow render visibility calculations.
Use checked integer scaling instead of floating-point casts, saturating
arithmetic for extents and cursor columns, and bound off-screen cursor
work to the terminal row count. Compute C API visibility in i64 and
cover maximum protocol values in storage, execution, and render-info
tests.
Require temporary image file paths to match complete directory
components when checking /tmp, /dev/shm, the configured temporary
directory, and its resolved path.
The previous byte-prefix checks accepted similarly named sibling
directories such as /tmpX. A temporary-file transmission could read
and unlink a file outside the permitted temporary directories.
Add a component-boundary helper and regression coverage for built-in
and configured directory prefixes. An integration test also verifies
that a rejected file remains on disk.
Use inclusive image ID bounds for the Kitty graphics protocol range
delete operation.
Range deletion previously joined the lower and upper bound checks with or,
which matched every placement for any valid range. A targeted delete could
therefore remove every graphics placement.
Join the bounds with and and update the lowercase and uppercase range tests
to keep placements below and above the selected interval.
`printRepeat` (CSI `b`, repeat the previous character N times) calls
`print()` once per repeat, so something like `\x1b[2000b` ran grapheme
checks, width lookups, wrap handling, and the integrity assert 2000
times for what is usually the same character on the same row.
`Terminal.print` was 24% of samples on a REP-heavy micro benchmark.
This PR just aims to add a fast path by introducing a chunking
mechanism. anything that needs care (insert mode, grapheme clustering,
hyperlinks) still falls back to per-codepoint print() inside printSlice,
so behavior *should* stay unchanged.
Some profiling data:
Generated with some plain stupid logic:
```py
D = "benchdata"
parts, total = [], 0
while total < 40_000_000:
line = "x" + "\x1b[80b" + "y" + "\x1b[35b" + "\r\n"
parts.append(line); total += len(line)
open(f"{D}/rep.bin", "wb").write("".join(parts).encode())
```
**macOS (hyperfine, 15 runs, warmup 3):**
| | mean |
|---|---|
| before | 2.360 s |
| after | 1.166 s |
And now the really interesting and promising stuff
**Linux, 24-core NixOS x86_64 (poop, 6s sampling):**
| | wall_time | instructions | branch_misses | peak_rss |
|---|---|---|---|---|
| before | 1.51 s | 50.9 G | 9.41 M | 6.82 MB |
| after | 562 ms | 9.07 G | 114 K | 6.74 MB |
While doing some work on my tmux fork I noticed multiple parts of
libghostty-vt was slower than tmux equivalents(isolated). Turns out they
do some smart stuff there.
printRepeat called print() once per repeat, so something like \x1b[2000b
ran grapheme checks, width lookups, wrap handling, etc etc 2000 times.
printSlice is already documented as semantically identical to
calling print per codepoint, so this just feeds the repeated
codepoint through it in 4096-entry stack chunks. Simple runs take
the batched fast path, and anything that needs care falls back to the
previous behaviour.
Fixes#13614
Only translate the shared default commands when building the GTK
runtime. macOS now use the source strings until we do broader
localization.
Fixes#13614
Only translate the shared default commands when building the GTK runtime.
macOS now use the source strings until we do broader localization.
Fix for Issue #12940
I actually do not know if this has already been resolved and the issue
is just still open. Either way, here's a fix. Now we run a check to see
if the current program is accepting mouse events before discarding the
middle click.
Fixes#13386, based on
https://github.com/mustafa0x/ghostty/commit/a8c090
Defer transparent-titlebar KVO rebinding to the next main-queue turn.
Track the observed tab group so unchanged bindings are preserved.
Previously, a tab-group callback could invalidate and recreate its own
observation before returning, leaving closed terminal windows registered
with AppKit after the undo timeout. These windows accumulated titlebar
and layer state, increasing memory use and WindowServer CPU with tab
churn.
Validated with an AppDelegate change that sat and created/closed tabs in
a loop, then counted weak controllers/windows/nsapp window.
Most obvious next step in translating Ghostty is the command palette.
Added support for i18n.N_ (https://docs.gtk.org/glib/i18n.html#macros).
Made a Latvian translation for the command palette to test. Codex did
bulk of the translations but I verified them.
Fixes#13386
Defer transparent-titlebar KVO rebinding to the next main-queue turn.
Track the observed tab group so unchanged bindings are preserved.
Previously, a tab-group callback could invalidate and recreate its own
observation before returning, leaving closed terminal windows registered
with AppKit after the undo timeout. These windows accumulated titlebar and
layer state, increasing memory use and WindowServer CPU with tab churn.
Validated with an AppDelegate change that sat and created/closed tabs
in a loop, then counted weak controllers/windows/nsapp window.
Co-authored-by: Mustafa J <mustafa.0x@gmail.com>
the original wording is a bit confusing; I thought cursor-click-to-move
required shell-integration to be enabled, and was confused when the
mouse was still moving my cursor in fish even with
shell-integration=none.