Commit Graph

9411 Commits

Author SHA1 Message Date
Mitchell Hashimoto
b661adad2e font: add exact glyph protocol constraints
Extend glyph render constraints with cell-span sizing modes for height,
width, contain, cover bounds, and stretch bounds. These preserve the
existing face-targeted behavior for platform fonts, emoji, and Nerd Font
rules while giving registered glyphs a target based on terminal cell
spans.

Map Glyph Protocol registration options to the new constraint modes so
sizing follows the spec formulas based on authored advance width and line
height. Baseline alignment now places design-space y=0 on the terminal
text baseline instead of approximating it as start alignment.

Document the placement formulas in the local protocol summary and add
focused tests for constraint mapping, cell-span padding, line-height and
advance scaling, contain versus cover behavior, stretch, and baseline
placement.
2026-06-05 13:37:10 -07:00
Mitchell Hashimoto
e45f002d1a terminal/apc: reject malformed glyph register input
Register parsing now validates the full register request shape before
constructing the parsed command. Inputs that only contain the verb
separator, such as `r`, `r;cp=e0a0`, or `r;foo`, now fail with
InvalidFormat instead of reaching Register invariants guarded by asserts.

Valid empty-payload requests still parse when they include the payload
separator, allowing execution to report malformed_payload through the
normal protocol response path.
2026-06-05 13:19:00 -07:00
Mitchell Hashimoto
f0d81f15ee terminal/apc: reject malformed glyph clear cp
Glyph clear execution previously treated an unparsable cp option the same
as an omitted cp option. That made inputs such as c;cp=zz behave like a
bare clear request and remove every glossary registration.

Track clear option presence separately from successful decoding. A
present but malformed cp now returns a malformed_payload clear failure
without mutating the glossary, while an omitted cp still clears all
registrations.
2026-06-05 13:13:04 -07:00
Mitchell Hashimoto
05eeb43942 font: exclude libghostty-vt from embedded font tests 2026-06-05 13:09:09 -07:00
Mitchell Hashimoto
1c0aac54bd font: reshuffle glyph sizing types to Glyph.zig 2026-06-05 06:58:32 -07:00
Mitchell Hashimoto
d271b271f9 terminal/glyph: query 2026-06-05 06:39:22 -07:00
Mitchell Hashimoto
6f83d8a4f1 terminal/glyph: clear 2026-06-05 06:34:53 -07:00
Mitchell Hashimoto
cc91940993 terminal/glyph: register request 2026-06-05 06:32:41 -07:00
Mitchell Hashimoto
0cd815f94a terminal/apc: glyph glossary delete, contains, clear 2026-06-05 06:23:48 -07:00
Mitchell Hashimoto
cf548a3aad terminal/apc: glyph glossary registration business logic 2026-06-04 18:44:44 -07:00
Mitchell Hashimoto
59d2ad9b6a terminal: glyph protocol Glossary entry starting to take shape 2026-06-04 18:23:08 -07:00
Mitchell Hashimoto
2055bb6dd6 terminal: glyph request glyf decode 2026-06-04 12:02:13 -07:00
Mitchell Hashimoto
8fcead00e5 font: glyf outline decoder and rasterizer (#12893)
This adds a Glyf outline decoder and rasterizer.

So it turns out that FreeType and CoreText have very shitty APIs for raw
Glyf table rasterization. CoreText as far as I can find can't do it at
all. In both cases you have to create a synthetic font with just this
entry and rasterize the glyph. And the code to do all that was WAYYYYYY
complex such that this made way more sense.

We need this for the Glyph Protocol.

**AI disclosure:** Hand-written parser, rasterizer. AI assisted
validation and test writing. I read the spec myself.

cc @qwerasd205
2026-06-04 11:04:19 -07:00
Mitchell Hashimoto
52368cbcff core: send selection_changed notification (#12902)
The core had no signal to the apprt when the active selection changed,
so a consumer (e.g. a screen reader) kept reading a stale selection
until some unrelated query refreshed it.

This change adds a payload-less selection_changed action that's fired on
a selection state transition. The apprt reads the current selection
through the normal read path.

This consolidates selection state changes so the notification fires
consistently: all sites route through setSelection rather than calling
screen.select directly, including the mouse paths that previously
bypassed it for clipboard timing.

The new setSelectionAndCopy extends setSelection with the additional
'copy_on_select' behavior.

On macOS, this posts .ghosttySelectionDidChange, which is debounced
before posting a NSAccessibility .selectedTextChanged notification.

GTK has no consumer yet and no-ops the action.

See: #9932
2026-06-04 11:03:59 -07:00
Zongyuan Li
f135b95098 terminal: test shrinking both axes with cursor past new bottom
Adds a PageList regression test exercising the underflow path fixed in
7fa6fffbc, and a libghostty-vt C API test mirroring the original repro
through ghostty_terminal_resize.
2026-06-04 00:01:24 +08:00
zongyuan.li
7fa6fffbca terminal: saturate cursor subtraction in resizeCols
PageList.resize takes the .lt branch when columns shrink, which calls
resizeWithoutReflow (mutating self.rows to the new smaller value) and
then resizeCols with the original opts.cursor.y. When both axes shrink
in one call and the cursor sits at or past the new bottom row, the
expression `self.rows - c.y - 1` underflows and panics in safety builds.

Use saturating subtraction; "remaining rows below cursor" is 0 once the
cursor sits at or past the new bottom.
2026-06-03 14:36:52 +08:00
Jon Parise
c4e1ab8883 core: send selection_changed notification
The core had no signal to the apprt when the active selection changed,
so a consumer (e.g. a screen reader) kept reading a stale selection
until some unrelated query refreshed it.

This change adds a payload-less selection_changed action that's fired on
a selection state transition. The apprt reads the current selection
through the normal read path.

This consolidates selection state changes so the notification fires
consistently: all sites route through setSelection rather than calling
screen.select directly, including the mouse paths that previously
bypassed it for clipboard timing.

The new setSelectionAndCopy extends setSelection with the additional
'copy_on_select' behavior.

On macOS, this posts .ghosttySelectionDidChange, which is debounced
before posting a NSAccessibility .selectedTextChanged notification.

GTK has no consumer yet and no-ops the action.
2026-06-02 19:37:49 -04:00
Mitchell Hashimoto
51995a7822 font: glyf rasterization png comparison 2026-06-02 06:08:59 -07:00
Mitchell Hashimoto
6246c288ae core: fix use-after-free in Surface.setSelection (#12894)
`setSelection` captured the previous selection, then called
`Screen.select` (which deinits the previous selection's tracked pins),
then compared the new selection against the now-freed previous pin via
`sel.eql(prev)`. That read freed pin memory (use-after-free).

The comparison was a copy-on-select optimization ("only re-copy if the
selection changed"). Remove it rather than repair it because:

- It never fired correctly. It compared against freed memory, so the
shipped behavior was already "always copy".

- It can't be repaired by copying `prev`'s pin before `Screen.select`.
That fixes the use-after-free but not the logic: the call sites (e.g.
mouse drag release) pass a selection equal to the one already set, so a
working `eql` skip would suppress the very copy those sites exist to
perform. A correct optimization would have to compare against the
last-copied selection (before the mouse event mutated the live one),
which would require extra state.

- It isn't worth tracking that additional state. The copy runs once per
selection gesture (mouse up, double-click), which isn't in a hot path,
so skipping a redundant re-copy only saves a single clipboard write.

Removing the skip eliminates the use-after-free and keeps the behavior
consistent with what we've already been doing.

---

_AI Disclosure_: Claude Opus 4.8 found this in a review while I was
working on adjacent code.
2026-06-02 06:07:31 -07:00
Mitchell Hashimoto
8eff74ef76 font: add glyf rasterizer 2026-06-01 20:38:02 -07:00
Jon Parise
76b9bdb199 terminal: test Screen.select frees existing pins 2026-06-01 20:09:25 -04:00
Jon Parise
ab82b8ab72 core: fix use-after-free in Surface.setSelection
setSelection captured the previous selection, then called Screen.select
(which deinits the previous selection's tracked pins), then compared the
new selection against the now-freed previous pin via `sel.eql(prev)`.
That read freed pin memory (use-after-free).

The comparison was a copy-on-select optimization ("only re-copy if the
selection changed"). Remove it rather than repair it because:

- It never fired correctly. It compared against freed memory, so the
  shipped behavior was already "always copy".

- It can't be repaired by copying `prev`'s pin before Screen.select.
  That fixes the use-after-free but not the logic: the call sites (e.g.
  mouse drag release) pass a selection equal to the one already set, so
  a working `eql` skip would suppress the very copy those sites exist to
  perform. A correct optimization would have to compare against the
  last-copied selection (before the mouse event mutated the live one),
  which would require extra state.

- It isn't worth tracking that additional state. The copy runs once per
  selection gesture (mouse up, double-click), which isn't in a hot path,
  so skipping a redundant re-copy only saves a single clipboard write.

Removing the skip eliminates the use-after-free and keeps the behavior
consistent with what we've already been doing.
2026-06-01 20:09:14 -04:00
Mitchell Hashimoto
d8f56b790e font: add glyf entry decoder to outline
Add Glyf.Outline for decoding the contours and points of a Glyf.
2026-06-01 14:52:23 -07:00
Mitchell Hashimoto
d3775d1ed0 terminal: glyph protocol parser and response encoder
This adds the core parse/encode for the still in-development and experimental
terminal glyph protocol: https://github.com/raphamorim/rio/pull/1542
Up to version 1.9.

The only cross-cutting change necessary was changing the APC
identification logic which previously only looked at a single byte to
support multi-byte identifiers since the glyph protocol uses `25a1`.
2026-06-01 10:50:05 -07:00
masterflitzer
a181c386ca config: fix missing space in docs
fixes #12873

comment/docs only change:
switched space and tab in default value of `selection-word-chars`
so there is no space at the value boundary
needed because markdown trims spaces at the beginning & end
of a code snippet
2026-05-31 18:23:29 +02:00
Jeffrey C. Ollie
2c62d182ce gtk: fix context menu hiding quick-terminal (#12843)
Fixes #12783 where opening the context menu (with right click) inside
the quick-terminal will hide the quick-terminal if autohide is enabled.

The cause of this issue is the quick-terminal window becoming inactive
and immediately active again when you open the context-menu. When the
window becomes inactive, the autohide feature hides the quick-terminal.
The temporary focus loss in GTK is triggered by GDK focus change events,
which probably originate from the windowing backend treating the context
menu as its own window. Whereas in GTK the context menu is not a
separate window but instead part of the widget tree of the window it was
opened from, so even when the context menu has focus that window is
still the active one in GTK.

As a fix `Window.propIsActive`, which implements the autohide logic,
will now do its work from a timeout callback, since there is probably no
reliable way to distinguish a temporary focus loss from a real one from
inside GTK and I'm not sure we can make any assumptions about the timing
of things happening in the windowing backend. A 100ms delay should be
long enough for the focus state to settle while still hiding the
quick-terminal quickly.

I reproduced the bug and verified the fix on Wayland with both Hyprland
and KDE. Temporary focus loss happens on X11+KDE as well, although it
doesn't matter there because there is no quick-terminal.

### AI Disclosure

No AI was used, code and comments were written by myself.
2026-05-29 22:44:30 -05:00
Daniel Kinzler
ff963f3119 Renamed timeout source and callback function. Added comment explaining timeout delay. 2026-05-29 17:40:25 +02:00
Mitchell Hashimoto
90175950d5 libghostty-vt: preserve shell prompts on resize by default (#12653)
This PR makes libghostty-vt preserve shell prompts across resize unless
the shell explicitly opts into prompt clearing/redraw with `redraw=1`.
2026-05-29 06:41:23 -07:00
Mitchell Hashimoto
519a612beb libghostty: fix wasm build for selection gesture 2026-05-28 13:00:49 -07:00
Mitchell Hashimoto
3cf01e8445 libghostty: add utf-8 grapheme cell getter to C API
Add a render-state row-cells getter that encodes the current cell's
full grapheme cluster directly as UTF-8 into a caller-provided
GhosttyBuffer. The getter writes the base codepoint first, followed by
any extra grapheme codepoints, and follows the existing buffer-writer
convention where len is bytes written on success or required capacity
on GHOSTTY_OUT_OF_SPACE.

Previously C consumers could query grapheme codepoints, but bindings
that needed UTF-8 text had to reconstruct and encode the cluster
themselves. That duplicated terminal internals in downstream bindings
and made users pay for awkward cross-language struct handling. By
owning the UTF-8/grapheme behavior in libghostty, bindings can use one
stable C API and optionally wrap it with small binding-local helpers.
2026-05-28 12:33:36 -07:00
Daniel Kinzler
1753d57bfd remove timeout source when window is disposed 2026-05-28 15:08:12 +02:00
Mitchell Hashimoto
8beea5f92d libghostty: expose row cell styling bit
Add a render row-cells data key for querying whether the current cell has
explicit styling. This lets consumers avoid fetching a raw cell or full style
snapshot when all they need is the cell's HasStyling bit.

The new key is appended to the existing enum for ABI safety and is served by
the existing row-cells getter path. Existing data keys and function exports are
unchanged.
2026-05-27 21:10:26 -07:00
Mitchell Hashimoto
f730ee0557 libghostty: expose viewport active state
Expose whether the terminal viewport is currently pinned to the active
area through the libghostty-vt terminal data API. Previously embedders
could only infer this from scrollbar geometry, which was indirect and
could require the more expensive scrollbar calculation.

The new GHOSTTY_TERMINAL_DATA_VIEWPORT_ACTIVE value returns the exact
PageList viewport state as a bool. The scroll viewport test now verifies
the value while moving between the active area and scrollback.
2026-05-27 15:24:49 -07:00
Mitchell Hashimoto
4e2d7c314b libghostty: optimize bits for selection gesture validation fields 2026-05-27 11:05:33 -07:00
Mitchell Hashimoto
f0fcb10406 libghostty: selection gesture deep press 2026-05-27 10:57:50 -07:00
Mitchell Hashimoto
603684ba11 libghostty: selection gesture autotick 2026-05-27 10:55:15 -07:00
Mitchell Hashimoto
90fd1ec2e7 libghostty: selection gesture drag events 2026-05-27 10:53:10 -07:00
Mitchell Hashimoto
3fd2c66a04 libghostty: selection gesture release event 2026-05-27 10:49:18 -07:00
Mitchell Hashimoto
5ac8e6569a libghostty: add ghostty_selection_gesture_event 2026-05-27 10:47:26 -07:00
Mitchell Hashimoto
bbfa984aec libghostty: GhosttySelectionGestureEvent 2026-05-27 10:42:29 -07:00
Mitchell Hashimoto
2f61ba036e libghostty: starting the SelectionGesture API, just init/get 2026-05-27 09:02:25 -07:00
Mitchell Hashimoto
756fda776b cli: rework +ssh-cache internals and user interface (#12814)
This change primarily focused on a revised +ssh-cache user interface,
but it also reworks a bunch of the internals.

The primary CLI improvement is support for positional arguments and a
consistent list output format that includes both the ISO-formatted
timestamp and relative age.

ghostty +ssh-cache # List all cached destinations
    ghostty +ssh-cache user@example.com          # Show that destination
ghostty +ssh-cache example.com # Show all users on that host
ghostty +ssh-cache --add=user@example.com # Manually add a destination
    ghostty +ssh-cache --remove=user@example.com # Remove a destination
ghostty +ssh-cache --prune=30d # Remove entries older than 30 days
    ghostty +ssh-cache --clear                   # Clear entire cache

Notable, we now support a --prune operation that replaces the previous
--expire-days flag that was never actually hooked up to anything (!!).
--prune also supports a wider range of Duration-based values.

We're also much more consistent with error codes: 0=success, 1=failure,
2=usage.

While working on those changes, I also reworked the cache internals,
particularly the code around timestamp handling and errors. For example,
I dropped the explicit error sets because they were growing unwieldy,
and in practice we only matched on a subset of those errors.

Lastly, overall test coverage should be much improved, especially around
the time- and allocation-related operations.

---

*AI Disclosure:* I made a lot of iterative, AI-assisted (Claude Opus
4.7) correctness passes over this work. It was particularly helpful in
tracing through the various failure modes, and it wrote those unit tests
in the process.
2026-05-27 07:52:00 -07:00
Mitchell Hashimoto
c343c5a67a Extract click/drag selection handling into SelectionGesture (#12830)
Refactor terminal text selection into a reusable `SelectionGesture`
state machine. Most importantly, this means our click+drag logic around
selection is now fully unit tested! And we found bugs! And fixed them!

The large line increase in this diff is mainly comments + tests.

I've wanted to do this forever so we can unit test this, but I was
kicked in the butt to do it recently because reimplementing selection
logic in libghostty consumers turns out to be complex and error prone
and we have a perfectly battle tested logic machine here so why not
extract it?

Behavioral changes from main surfaced via unit testing:

- Dragging now drags by output across semantic output blocks when the
initial press was an output selection. This matches the behavior of
dragging continuing whatever the initial selection logic was.
- Selection autoscroll now stops when the click anchor is invalidated by
a screen change (e.g. primary to alt)
- Deep press (macOS force touch) now selects the word at the original
press location and consumes the active drag gesture, preventing later
movement from dragging or autoscrolling that selection. This matches
built-in macOS apps.
- Mouse release records whether the gesture moved away from the pressed
cell, so link and prompt clicks are skipped after a drag while normal
clicks still activate them.

Example usage:

```zig
var gesture: terminal.SelectionGesture = .init;
defer gesture.deinit(t);

const press_selection = try gesture.press(t, .{
    .time = try std.time.Instant.now(),
    .pin = press_pin,
    .xpos = mouse_x,
    .ypos = mouse_y,
    .max_distance = cell_width,
    .repeat_interval = mouse_interval,
    .word_boundary_codepoints = selection_word_chars,
    .behaviors = &.{ .cell, .word, .output },
});
try t.screens.active.select(press_selection);

if (gesture.drag(t, drag_event)) |drag_selection| {
    try t.screens.active.select(drag_selection);
}

gesture.release(t, .{ .pin = release_pin });
```
2026-05-27 07:48:23 -07:00
Mitchell Hashimoto
68959c5c63 terminal: fix selection gesture edge cases
Selection gestures now treat releases with invalidated anchors as dragged,
so a press that crosses screen boundaries cannot also activate links or
prompt clicks on release. Cell drags that create a same-cell selection also
mark the gesture as dragged, which keeps click-only actions from firing
after a threshold-crossing drag.

Autoscroll now resolves the drag pin after moving the viewport instead of
reusing the pin from before the scroll. This keeps the selection aligned
with the row currently under the pointer. The inspector also validates the
tracked click pin before displaying it so stale pins from inactive screens
are ignored.
2026-05-27 06:58:44 -07:00
Mitchell Hashimoto
7d4d1e5819 terminal: add configurable behaviors based on click count 2026-05-27 06:32:51 -07:00
Mitchell Hashimoto
82a73f2bf1 terminal: SelectionGesture press returns standard behaviors 2026-05-27 06:26:07 -07:00
Mitchell Hashimoto
9b00bb436a terminal: better SelectionGesture docs 2026-05-27 06:20:22 -07:00
Mitchell Hashimoto
f5f9d32d0a terminal: SelectionGesture deep press 2026-05-26 21:36:22 -07:00
Mitchell Hashimoto
df98b6d983 terminal: SelectionGesture autoscrollTick 2026-05-26 21:26:49 -07:00
Mitchell Hashimoto
141c7d44d2 SelectionGesture: release event 2026-05-26 21:17:03 -07:00