diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 309a97b09..e4839e707 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -83,6 +83,7 @@ extension Ghostty { .focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize) #if canImport(AppKit) .backport.pointerVisibility(surfaceView.pointerVisible ? .visible : .hidden) + .backport.pointerStyle(surfaceView.pointerStyle) .onReceive(pubBecomeKey) { notification in guard let window = notification.object as? NSWindow else { return } guard let surfaceWindow = surfaceView.window else { return } diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 8d12c6193..90d259d22 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -39,7 +39,8 @@ extension Ghostty { @Published var surfaceSize: ghostty_surface_size_s? = nil // Whether the pointer should be visible or not - @Published var pointerVisible: Bool = true + @Published private(set) var pointerVisible: Bool = true + @Published private(set) var pointerStyle: BackportPointerStyle = .default // An initial size to request for a window. This will only affect // then the view is moved to a new window. @@ -102,7 +103,6 @@ extension Ghostty { private var markedText: NSMutableAttributedString private(set) var focused: Bool = true private var prevPressureStage: Int = 0 - private var cursor: NSCursor = .iBeam private var appearanceObserver: NSKeyValueObservation? = nil // This is set to non-null during keyDown to accumulate insertText contents @@ -267,49 +267,49 @@ extension Ghostty { func setCursorShape(_ shape: ghostty_mouse_shape_e) { switch (shape) { case GHOSTTY_MOUSE_SHAPE_DEFAULT: - cursor = .arrow - - case GHOSTTY_MOUSE_SHAPE_CONTEXT_MENU: - cursor = .contextualMenu + pointerStyle = .default case GHOSTTY_MOUSE_SHAPE_TEXT: - cursor = .iBeam - - case GHOSTTY_MOUSE_SHAPE_CROSSHAIR: - cursor = .crosshair + pointerStyle = .horizontalText case GHOSTTY_MOUSE_SHAPE_GRAB: - cursor = .openHand + pointerStyle = .grabIdle case GHOSTTY_MOUSE_SHAPE_GRABBING: - cursor = .closedHand + pointerStyle = .grabActive case GHOSTTY_MOUSE_SHAPE_POINTER: - cursor = .pointingHand + pointerStyle = .link case GHOSTTY_MOUSE_SHAPE_W_RESIZE: - cursor = .resizeLeft + pointerStyle = .resizeLeft case GHOSTTY_MOUSE_SHAPE_E_RESIZE: - cursor = .resizeRight + pointerStyle = .resizeRight case GHOSTTY_MOUSE_SHAPE_N_RESIZE: - cursor = .resizeUp + pointerStyle = .resizeUp case GHOSTTY_MOUSE_SHAPE_S_RESIZE: - cursor = .resizeDown + pointerStyle = .resizeDown case GHOSTTY_MOUSE_SHAPE_NS_RESIZE: - cursor = .resizeUpDown + pointerStyle = .resizeUpDown case GHOSTTY_MOUSE_SHAPE_EW_RESIZE: - cursor = .resizeLeftRight + pointerStyle = .resizeLeftRight case GHOSTTY_MOUSE_SHAPE_VERTICAL_TEXT: - cursor = .iBeamCursorForVerticalLayout + pointerStyle = .default + // These are not yet supported. We should support them by constructing a + // PointerStyle from an NSCursor. + case GHOSTTY_MOUSE_SHAPE_CONTEXT_MENU: + fallthrough + case GHOSTTY_MOUSE_SHAPE_CROSSHAIR: + fallthrough case GHOSTTY_MOUSE_SHAPE_NOT_ALLOWED: - cursor = .operationNotAllowed + pointerStyle = .default default: // We ignore unknown shapes. diff --git a/macos/Sources/Helpers/View+Extension.swift b/macos/Sources/Helpers/View+Extension.swift index db17b441f..fb6e0c20f 100644 --- a/macos/Sources/Helpers/View+Extension.swift +++ b/macos/Sources/Helpers/View+Extension.swift @@ -16,3 +16,16 @@ extension View { ) } } + +extension View { + func pointerStyleFromCursor(_ cursor: NSCursor) -> some View { + if #available(macOS 15.0, *) { + return self.pointerStyle(.image( + Image(nsImage: cursor.image), + hotSpot: .init(x: cursor.hotSpot.x, y: cursor.hotSpot.y) + )) + } else { + return self + } + } +}