Skip to content

Handle libghostty's action callbacks: open_url, cursor shapes, link hover, secure input, bell, titles - #194

Open
thdxg wants to merge 2 commits into
mainfrom
claude/handle-remaining-ghostty-actions
Open

Handle libghostty's action callbacks: open_url, cursor shapes, link hover, secure input, bell, titles#194
thdxg wants to merge 2 commits into
mainfrom
claude/handle-remaining-ghostty-actions

Conversation

@thdxg

@thdxg thdxg commented Jul 28, 2026

Copy link
Copy Markdown
Owner

What

Macterm's action callback (GhosttyCallbacks.action) answered false to ~50 of libghostty's ~64 action tags. Most are deliberate (Macterm owns window/tab/split management), but a batch of real terminal features silently did nothing. This PR fills every gap that has a sensible Macterm behavior:

Action Behavior
OPEN_URL write_scrollback_file:open & friends open natively (text → default text editor, matching Ghostty.app) instead of through the core's error-logged open -t fallback
MOUSE_SHAPE Pointer follows libghostty — I-beam over text, pointing hand over links, resize arrows for TUI drags — applied as the scroll view's documentCursor (same mechanism as Ghostty's mac app)
MOUSE_VISIBILITY mouse-hide-while-typing via NSCursor.setHiddenUntilMouseMoves
MOUSE_OVER_LINK Safari-style hover-URL banner in the pane's bottom-left corner
SECURE_INPUT New SecureInput manager (ported from Ghostty.app): shields keystrokes during password prompts (macos-auto-secure-input) and the toggle_secure_input keybind; focus-scoped, released on app deactivation, balanced enable/disable; per-pane lock badge gated by macos-secure-input-indication
RING_BELL bell-features: system beep, custom bell-audio-path sound, dock attention
OPEN_CONFIG Opens the user's ghostty config in their editor, creating it first if missing
CHECK_FOR_UPDATES Sparkle check (same guard as the palette command)
SET_TAB_TITLE Sets the containing tab's customTitle (empty restores the automatic title)
PROMPT_TITLE Routes to the sidebar rename-tab flow
COPY_TITLE_TO_CLIPBOARD Copies the pane's display title
RENDERER_HEALTH Logged at error level so a black pane is diagnosable

The default: case now documents why the remainder stay unhandled: Macterm-owned window/tab/split actions (a ghostty keybind = cmd+t=new_tab is ignored by design), GTK/iOS-only tags, sizing hints that don't map onto one shared window of splits, UNDO/REDO (no app-level undo stack), COLOR_CHANGE (chrome follows the resolved theme via CONFIG_CHANGE), and SHOW_CHILD_EXITED (returning false is what makes the core render its abnormal-exit overlay — the error UI remote projects rely on).

Quick-terminal note

Tab-title actions are wired through an optional AppState environment; the quick terminal's hosting view has no AppState (and no tab UI), so those no-op there while cursor/link/secure-input/bell all still work.

Testing

  • Unit: SecureInput balancing via injected hooks (global/scoped/focus/inactive/failure), cursor-shape mapping, renameTab(containing:)/setTabTitle(containing:), resolvedOpenTarget URL-vs-path. Full suite green.
  • Empirical (hermetic harness, debug app with a keybind-loaded test config driven over the control socket): write_scrollback_file:open writes and opens the scrollback file with no "apprt did not handle open URL action" warning; open_config opens the config file; toggle_secure_input flows through scope add/remove (the OS flip is correctly withheld while the app is inactive); set_tab_title shows up in tab list.
  • Not headless-testable, verified by parity with Ghostty.app's implementations: cursor shape/hover rendering (needs a real mouse), bell audio/attention, the rename prompt UI.

🤖 Generated with Claude Code

thdxg added 2 commits July 28, 2026 16:23
write_scrollback_file:open and friends worked only through the core's
error-logged 'open -t' fallback; opening the payload ourselves matches
Ghostty.app (text kind opens in the default text editor, schemeless
strings are file paths per ghostty#8763).
Macterm answered false to most action callbacks, so several terminal
features users configure in ghostty silently did nothing:

- MOUSE_SHAPE / MOUSE_VISIBILITY: the pointer now follows libghostty
  (I-beam over text, pointing hand over links, resize arrows for TUI
  drags, hide-while-typing), applied via the scroll view's
  documentCursor like Ghostty's own mac app.
- MOUSE_OVER_LINK: hovering a link shows a Safari-style URL banner in
  the pane's bottom-left corner.
- SECURE_INPUT: a new SecureInput manager (ported from Ghostty.app)
  shields keystrokes during password prompts and the
  toggle_secure_input keybind, with a per-pane lock badge gated by
  macos-secure-input-indication; balancing logic is unit-tested via
  injected hooks.
- RING_BELL: bell-features support (system beep, custom audio, dock
  attention).
- OPEN_CONFIG / CHECK_FOR_UPDATES: open the user's ghostty config in
  their editor (creating it if missing) and run a Sparkle check.
- SET_TAB_TITLE / PROMPT_TITLE / COPY_TITLE_TO_CLIPBOARD: mapped onto
  Macterm's tab customTitle, the sidebar rename flow, and the pane's
  display title.
- RENDERER_HEALTH: logged, so a black pane is diagnosable.

The default case now documents why the rest stay unhandled (Macterm-
owned window/tab/split management, GTK/iOS-only actions, sizing hints,
COLOR_CHANGE, SHOW_CHILD_EXITED relying on the core overlay).
@github-actions github-actions Bot added area:ui Views, Settings UI area:terminal Terminal surface, ghostty integration area:state AppState, models, persistence area:tests Test changes labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Window-state benchmark

State Metric main@9c0b4ccdd this branch Δ
focused CPU % 0.70 0.90 +29%
Memory (RSS MB) 109.3 108.8 -0%
CPU ms/s (powermetrics) 6.5 7.7 +19%
Wakeups/s (powermetrics) 121.4 117.2 -3%
workload-focused CPU % 2.10 2.00 -5%
Memory (RSS MB) 167.0 163.2 -2%
CPU ms/s (powermetrics) 19.7 19.1 -3%
Wakeups/s (powermetrics) 204.2 193.9 -5%
workload-unfocused CPU % 2.00 2.30 +15%
Memory (RSS MB) 129.5 140.0 +8%
CPU ms/s (powermetrics) 20.0 22.7 +13%
Wakeups/s (powermetrics) 226.7 179.2 -21%

Reported value is the median of 3×10s windows per state (splitting the window and taking the median keeps one co-scheduled spike from skewing a state); CPU % is the process CPU-time delta over a window. Runs land on different shared runners, so treat small deltas as noise — 🔺/🔻 marks changes ≥25% that also clear the metric's absolute noise floor (CPU % ≥0.5, Memory (RSS MB) ≥25, CPU ms/s ≥5, Wakeups/s ≥50); CPU deltas off a noise-dominated baseline aren't flagged (CPU % baseline ≥1.5, CPU ms/s baseline ≥15). The benchmark:regression / benchmark:improvement label needs corroboration — ≥2 flagged metrics in the same direction, at least one under workload — so a lone noisy cell shows its arrow here without tagging the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:state AppState, models, persistence area:terminal Terminal surface, ghostty integration area:tests Test changes area:ui Views, Settings UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant