Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
14270 Sources/TerminalController.swift
13172 Sources/Workspace.swift
12348 cmuxTests/AppDelegateShortcutRoutingTests.swift
12212 Sources/GhosttyTerminalView.swift
12276 Sources/GhosttyTerminalView.swift
11669 Sources/Panels/BrowserPanel.swift
9497 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
8032 Sources/Panels/BrowserPanelView.swift
Expand Down
64 changes: 64 additions & 0 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2818,6 +2818,12 @@ class GhosttyApp {
}
case GHOSTTY_ACTION_RENDER:
return false
case GHOSTTY_ACTION_MOUSE_SHAPE:
let shape = action.action.mouse_shape
DispatchQueue.main.async {
surfaceView.updateGhosttyMouseShape(shape)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ab1288c. Configure here.

return true
case GHOSTTY_ACTION_SCROLLBAR:
let scrollbar = GhosttyScrollbar(c: action.action.scrollbar)
surfaceView.enqueueScrollbarUpdate(scrollbar)
Expand Down Expand Up @@ -3415,13 +3421,62 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
private let _renderedFrameLock = NSLock()
var cellSize: CGSize = .zero
private var lastKnownMousePointInView: NSPoint?
private var ghosttyMouseShape: ghostty_action_mouse_shape_e = GHOSTTY_MOUSE_SHAPE_TEXT

static func retainRenderedFrameNotifications() -> () -> Void {
// See GhosttyApp.retainTickNotifications() on the idempotent release.
let retention = GhosttyApp.renderedFrameNotificationDemand.retain()
return { retention.release() }
}

private static func ghosttyMouseCursor(for shape: ghostty_action_mouse_shape_e) -> NSCursor {
switch shape {
case GHOSTTY_MOUSE_SHAPE_DEFAULT:
return .arrow
case GHOSTTY_MOUSE_SHAPE_TEXT:
return .iBeam
case GHOSTTY_MOUSE_SHAPE_VERTICAL_TEXT:
return .iBeamCursorForVerticalLayout
case GHOSTTY_MOUSE_SHAPE_POINTER:
return .pointingHand
case GHOSTTY_MOUSE_SHAPE_CROSSHAIR,
GHOSTTY_MOUSE_SHAPE_CELL:
return .crosshair
case GHOSTTY_MOUSE_SHAPE_GRAB:
return .openHand
case GHOSTTY_MOUSE_SHAPE_GRABBING,
GHOSTTY_MOUSE_SHAPE_MOVE:
return .closedHand
case GHOSTTY_MOUSE_SHAPE_NOT_ALLOWED,
GHOSTTY_MOUSE_SHAPE_NO_DROP:
return .operationNotAllowed
case GHOSTTY_MOUSE_SHAPE_CONTEXT_MENU:
return .contextualMenu
case GHOSTTY_MOUSE_SHAPE_COPY:
return .dragCopy
case GHOSTTY_MOUSE_SHAPE_ALIAS:
return .dragLink
case GHOSTTY_MOUSE_SHAPE_W_RESIZE,
GHOSTTY_MOUSE_SHAPE_E_RESIZE,
GHOSTTY_MOUSE_SHAPE_EW_RESIZE,
GHOSTTY_MOUSE_SHAPE_COL_RESIZE:
return .resizeLeftRight
case GHOSTTY_MOUSE_SHAPE_N_RESIZE,
GHOSTTY_MOUSE_SHAPE_S_RESIZE,
GHOSTTY_MOUSE_SHAPE_NS_RESIZE,
GHOSTTY_MOUSE_SHAPE_ROW_RESIZE:
return .resizeUpDown
default:
return .arrow
}
}

fileprivate func updateGhosttyMouseShape(_ shape: ghostty_action_mouse_shape_e) {
guard ghosttyMouseShape != shape else { return }
ghosttyMouseShape = shape
window?.invalidateCursorRects(for: self)
}
Comment thread
cursor[bot] marked this conversation as resolved.

/// Coalesce high-frequency scrollbar updates into a single main-thread
/// dispatch. The action callback (which may fire thousands of times per
/// second during bulk output like `seq 1 100000`) stores the latest value
Expand Down Expand Up @@ -3794,6 +3849,10 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
let isAlreadyAttached = surface.isAttached(to: self)
if !isSameSurface {
appliedColorScheme = nil
// Reset any OSC 22 mouse shape carried over from the previous
// surface; the newly-bound surface re-emits its own shape via
// GHOSTTY_ACTION_MOUSE_SHAPE if it has one.
updateGhosttyMouseShape(GHOSTTY_MOUSE_SHAPE_TEXT)
}
terminalSurface = surface
tabId = surface.tabId
Expand Down Expand Up @@ -3920,6 +3979,11 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
invalidateTextInputCoordinates()
}

override func resetCursorRects() {
super.resetCursorRects()
addCursorRect(bounds, cursor: Self.ghosttyMouseCursor(for: ghosttyMouseShape))
}

override var isOpaque: Bool { false }

private func resolvedSurfaceSize(preferred size: CGSize?) -> CGSize {
Expand Down
Loading