Repository navigation
Honor libghostty mouse-cursor-shape (OSC 22) requests - #7318
Conversation
cmux's action dispatch dropped GHOSTTY_ACTION_MOUSE_SHAPE, so the terminal showed a static arrow and never reflected the pointer shape libghostty requests: OSC 22 (`\e]22;<shape>`), the pointer over OSC-8 links, the crosshair during rectangle select, or even the default text/iBeam over the grid. Handle the action in GhosttyNSView: store the requested shape (defaulting to text/iBeam), map it to a public NSCursor, and drive the terminal's base cursor rect from it via resetCursorRects. The existing Cmd-hover pointingHand push/pop overlays on top and reverts to the base shape on release.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds handling for ChangesMouse cursor shape support
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant GhosttyCore
participant ActionDispatcher
participant GhosttyNSView
participant AppKit
GhosttyCore->>ActionDispatcher: GHOSTTY_ACTION_MOUSE_SHAPE(shape)
ActionDispatcher->>GhosttyNSView: updateGhosttyMouseShape(shape)
GhosttyNSView->>GhosttyNSView: ghosttyMouseCursor(for: shape)
GhosttyNSView->>AppKit: invalidate cursor rects
AppKit->>GhosttyNSView: resetCursorRects()
GhosttyNSView->>AppKit: addCursorRect(bounds, cursor)
Possibly related issues
🚥 Pre-merge checks | ✅ 25✅ Passed checks (25 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR fixes a long-standing omission where
Confidence Score: 5/5Safe to merge — the change is confined to AppKit cursor-rect management on the main thread with no auth, persistence, or concurrency concerns. All mutations to No files require special attention. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant L as libghostty (I/O thread)
participant D as GhosttyApp.handleAction
participant M as Main thread
participant V as GhosttyNSView
participant A as AppKit cursor system
L->>D: GHOSTTY_ACTION_MOUSE_SHAPE(shape)
D->>M: DispatchQueue.main.async
M->>V: updateGhosttyMouseShape(shape)
V->>V: "guard ghosttyMouseShape != shape"
V->>V: "ghosttyMouseShape = shape"
V->>A: window?.invalidateCursorRects(for: self)
A->>V: resetCursorRects()
V->>A: addCursorRect(bounds, cursor: NSCursor mapped from shape)
Note over V,A: On surface switch
V->>V: attachSurface(newSurface)
V->>V: updateGhosttyMouseShape(GHOSTTY_MOUSE_SHAPE_TEXT)
V->>A: invalidateCursorRects → resetCursorRects() → iBeam
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant L as libghostty (I/O thread)
participant D as GhosttyApp.handleAction
participant M as Main thread
participant V as GhosttyNSView
participant A as AppKit cursor system
L->>D: GHOSTTY_ACTION_MOUSE_SHAPE(shape)
D->>M: DispatchQueue.main.async
M->>V: updateGhosttyMouseShape(shape)
V->>V: "guard ghosttyMouseShape != shape"
V->>V: "ghosttyMouseShape = shape"
V->>A: window?.invalidateCursorRects(for: self)
A->>V: resetCursorRects()
V->>A: addCursorRect(bounds, cursor: NSCursor mapped from shape)
Note over V,A: On surface switch
V->>V: attachSurface(newSurface)
V->>V: updateGhosttyMouseShape(GHOSTTY_MOUSE_SHAPE_TEXT)
V->>A: invalidateCursorRects → resetCursorRects() → iBeam
Reviews (3): Last reviewed commit: "Reset OSC 22 mouse shape on surface swap" | Re-trigger Greptile |
Bugbot flagged that ghosttyMouseShape persists across attachSurface when a different TerminalSurface binds to the view, so cursor rects could show the previous surface's OSC 22 shape until libghostty emits a new GHOSTTY_ACTION_MOUSE_SHAPE. Reset to the default (GHOSTTY_MOUSE_SHAPE_TEXT) when a new surface attaches; the new surface re-emits its own shape if any. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ab1288c. Configure here.
| let shape = action.action.mouse_shape | ||
| DispatchQueue.main.async { | ||
| surfaceView.updateGhosttyMouseShape(shape) | ||
| } |
There was a problem hiding this comment.
Stale shape after surface swap
Medium Severity
GHOSTTY_ACTION_MOUSE_SHAPE applies the shape on the main queue without checking that the emitting surface is still the one bound to surfaceView. After attachSurface resets the cursor for a new surface, a queued update from the previous surface can overwrite it and show the wrong cursor until another shape event arrives.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit ab1288c. Configure here.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/GhosttyTerminalView.swift (1)
2821-2826: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winGuard the mouse-shape update against surface reattachment. The
DispatchQueue.main.asynchop can still run afterattachSurface(_:)resets the cursor, so a staleGHOSTTY_ACTION_MOUSE_SHAPEfrom the previous surface can reapply the old shape. Check the surface identity/generation in the closure before callingupdateGhosttyMouseShape.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Sources/GhosttyTerminalView.swift` around lines 2821 - 2826, The mouse-shape update path in GhosttyTerminalView’s GHOSTTY_ACTION_MOUSE_SHAPE handling can run after attachSurface(_:) has already switched surfaces, letting a stale async callback reapply an old cursor. Update the DispatchQueue.main.async closure to verify the current surface identity or generation before calling surfaceView.updateGhosttyMouseShape, and skip the update when it no longer matches the surface that scheduled it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 2821-2826: The mouse-shape update path in GhosttyTerminalView’s
GHOSTTY_ACTION_MOUSE_SHAPE handling can run after attachSurface(_:) has already
switched surfaces, letting a stale async callback reapply an old cursor. Update
the DispatchQueue.main.async closure to verify the current surface identity or
generation before calling surfaceView.updateGhosttyMouseShape, and skip the
update when it no longer matches the surface that scheduled it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1f1cc00e-e3e1-4bfb-b34b-1a4a5e15114a
⛔ Files ignored due to path filters (1)
.github/swift-file-length-budget.tsvis excluded by!**/*.tsv
📒 Files selected for processing (1)
Sources/GhosttyTerminalView.swift
…ng, themes) Delta: #7318/#7319 mouse cursor-shape + right/middle drag forwarding, #7320 ghostty upstream, #6664 iOS themes, #7196 iOS scroll, #7257/#7307 web, #7329 CLI suggestions, #7222 codex resume update-suppression. Conflicts resolved keeping HEAD structure: - GhosttyTerminalView.swift: took main's viewDidEndLiveResize + resetCursorRects (cursor-shape #7318; ghosttyMouseShape/ghosttyMouseCursor already in HEAD). - Mobile/MobileHostService.swift: union (kept HEAD sharedRequestActivity + main publicStatusPayload theme payload #6664). - project.pbxproj: union-dedup, normalized; test-wiring green. - swift-file-length-budget.tsv regenerated. #7222 (codex update-check suppression) partially landed: the CMUXAgentLaunch package part (AgentResumeArgv.codexUpdateCheckSuppressionOverride) auto-merged and is present; the app-target files (+CodexUpdateCheck.swift + test) extend SurfaceResumeCommandCanonicalizer, a type HEAD dissolved into the CmuxWorkspaces package, so they can't compile and the suppression was never wired (call site was in the HEAD-deleted +PortableAgentExecutable). Removed those two files; re-wire recorded in merge-deferred-gaps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n HEAD's resize model) main's #7318 viewDidEndLiveResize called updateSurfaceSize(bypassLiveResizeCoalescing:), but HEAD's refactored updateSurfaceSize lacks the coalescePixelOnlyResize model (#6386); HEAD applies resizes continuously during live resize. Call HEAD's updateSurfaceSize() (kept the new viewDidEndLiveResize + resetCursorRects cursor-shape). #6386 divergence recorded in merge-deferred-gaps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


What
cmux's action dispatch dropped
GHOSTTY_ACTION_MOUSE_SHAPE, so the terminalshowed a static arrow and never reflected the mouse-cursor shape libghostty
requests:
OSC 22(\e]22;<shape>from apps), the pointer over OSC-8 links,the crosshair during rectangle select, or even the default text/iBeam over the
grid.
How
GhosttyNSViewnow stores the requested shape (defaulting totext/iBeam),maps it to a public
NSCursor, and drives the terminal's base cursor rect fromit via
resetCursorRects(). TheGHOSTTY_ACTION_MOUSE_SHAPEcase hops to main,updates the shape (guarded against redundant same-shape events to avoid
cursor-rect thrash), and invalidates the cursor rects. The existing Cmd-hover
pointingHandpush/pop overlays on top and reverts to the base shape onrelease. No ghostty/submodule change (the C API was already present); public
NSCursorAPIs only.Test
Hover the terminal grid -> iBeam (was arrow).
printf '\e]22;pointer\e\\'->pointer;
\e]22;crosshair\e\\-> crosshair;\e]22;wait\e\\-> arrow fallback;\e]22;\e\\-> back to iBeam. Cmd-hover a real filename -> pointingHand, revertto iBeam on release.
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Note
Low Risk
Localized AppKit cursor-rect behavior with no auth, data, or protocol changes beyond handling an existing Ghostty action.
Overview
The terminal view no longer ignores
GHOSTTY_ACTION_MOUSE_SHAPEfrom libghostty, so the pointer can follow OSC 22, link hover, selection, and grid text states instead of staying a static arrow.GhosttyNSViewkeeps the latest requested shape (default text/iBeam), maps eachghostty_action_mouse_shape_eto a publicNSCursor, and applies it inresetCursorRects(). The action handler dispatches to the main thread, updates the shape only when it changes, and invalidates cursor rects. Existing Cmd-hoverpointingHandpush/pop still overlays on top and pops back to the base shape.Reviewed by Cursor Bugbot for commit 7e80e00. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Restore dynamic mouse cursor by honoring
OSC 22fromlibghostty. The terminal now shows the correct shape (iBeam, pointer, crosshair, etc.) instead of a static arrow.Bug Fixes
GHOSTTY_ACTION_MOUSE_SHAPE; dispatch to main and updateGhosttyNSView.NSCursorand set the base cursor viaresetCursorRects().Refactors
Sources/GhosttyTerminalView.swift.Written for commit ab1288c. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes