From e89a4f74089b66c00043096589dc75fff5ab6674 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Sep 2024 21:21:05 -0700 Subject: [PATCH 1/7] macos: use macOS 15 pointerVisibility to show/hide cursor --- macos/Sources/Features/Terminal/Terminal.xib | 4 +- macos/Sources/Ghostty/SurfaceView.swift | 1 + .../Sources/Ghostty/SurfaceView_AppKit.swift | 117 +----------------- macos/Sources/Helpers/Backport.swift | 63 ++++++++-- 4 files changed, 59 insertions(+), 126 deletions(-) diff --git a/macos/Sources/Features/Terminal/Terminal.xib b/macos/Sources/Features/Terminal/Terminal.xib index 4078fa2c6..65b03b6eb 100644 --- a/macos/Sources/Features/Terminal/Terminal.xib +++ b/macos/Sources/Features/Terminal/Terminal.xib @@ -1,8 +1,8 @@ - + - + diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 6d20e1e82..309a97b09 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -82,6 +82,7 @@ extension Ghostty { .focusedValue(\.ghosttySurfaceView, surfaceView) .focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize) #if canImport(AppKit) + .backport.pointerVisibility(surfaceView.pointerVisible ? .visible : .hidden) .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 c3ae03138..8d12c6193 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -38,6 +38,9 @@ extension Ghostty { // structure because I'm lazy. @Published var surfaceSize: ghostty_surface_size_s? = nil + // Whether the pointer should be visible or not + @Published var pointerVisible: Bool = true + // An initial size to request for a window. This will only affect // then the view is moved to a new window. var initialSize: NSSize? = nil @@ -97,11 +100,9 @@ extension Ghostty { private(set) var surface: ghostty_surface_t? private var markedText: NSMutableAttributedString - private var mouseEntered: Bool = false private(set) var focused: Bool = true private var prevPressureStage: Int = 0 private var cursor: NSCursor = .iBeam - private var cursorVisible: CursorVisibility = .visible private var appearanceObserver: NSKeyValueObservation? = nil // This is set to non-null during keyDown to accumulate insertText contents @@ -114,15 +115,6 @@ extension Ghostty { // so we'll use that to tell ghostty to refresh. override var wantsUpdateLayer: Bool { return true } - // State machine for mouse cursor visibility because every call to - // NSCursor.hide/unhide must be balanced. - enum CursorVisibility { - case visible - case hidden - case pendingVisible - case pendingHidden - } - init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { self.markedText = NSMutableAttributedString() self.uuid = uuid ?? .init() @@ -194,13 +186,6 @@ extension Ghostty { trackingAreas.forEach { removeTrackingArea($0) } - // mouseExited is not called by AppKit one last time when the view - // closes so we do it manually to ensure our NSCursor state remains - // accurate. - if (mouseEntered) { - mouseExited(with: NSEvent()) - } - // Remove ourselves from secure input if we have to SecureInput.shared.removeScoped(ObjectIdentifier(self)) @@ -242,8 +227,6 @@ extension Ghostty { } func sizeDidChange(_ size: CGSize) { - guard let surface = self.surface else { return } - // Ghostty wants to know the actual framebuffer size... It is very important // here that we use "size" and NOT the view frame. If we're in the middle of // an animation (i.e. a fullscreen animation), the frame will not yet be updated. @@ -332,44 +315,10 @@ extension Ghostty { // We ignore unknown shapes. return } - - // Set our cursor immediately if our mouse is over our window - if (mouseEntered) { cursorUpdate(with: NSEvent()) } - if let window = self.window { - window.invalidateCursorRects(for: self) - } } func setCursorVisibility(_ visible: Bool) { - switch (cursorVisible) { - case .visible: - // If we want to be visible, do nothing. If we want to be hidden - // enter the pending state. - if (visible) { return } - cursorVisible = .pendingHidden - - case .hidden: - // If we want to be hidden, do nothing. If we want to be visible - // enter the pending state. - if (!visible) { return } - cursorVisible = .pendingVisible - - case .pendingVisible: - // If we want to be visible, do nothing because we're already pending. - // If we want to be hidden, we're already hidden so reset state. - if (visible) { return } - cursorVisible = .hidden - - case .pendingHidden: - // If we want to be hidden, do nothing because we're pending that switch. - // If we want to be visible, we're already visible so reset state. - if (!visible) { return } - cursorVisible = .visible - } - - if (mouseEntered) { - cursorUpdate(with: NSEvent()) - } + pointerVisible = visible } // MARK: - Notifications @@ -419,7 +368,6 @@ extension Ghostty { addTrackingArea(NSTrackingArea( rect: frame, options: [ - .mouseEnteredAndExited, .mouseMoved, // Only send mouse events that happen in our visible (not obscured) rect @@ -433,11 +381,6 @@ extension Ghostty { userInfo: nil)) } - override func resetCursorRects() { - discardCursorRects() - addCursorRect(frame, cursor: self.cursor) - } - override func viewDidChangeBackingProperties() { super.viewDidChangeBackingProperties() @@ -578,40 +521,6 @@ extension Ghostty { self.mouseMoved(with: event) } - override func mouseEntered(with event: NSEvent) { - // For reasons unknown (Cocoaaaaaaaaa), mouseEntered is called - // multiple times in an unbalanced way with mouseExited when a new - // tab is created. In this scenario, we only want to process our - // callback once since this is stateful and we expect balancing. - if (mouseEntered) { return } - - mouseEntered = true - - // Update our cursor when we enter so we fully process our - // cursorVisible state. - cursorUpdate(with: NSEvent()) - } - - override func mouseExited(with event: NSEvent) { - // See mouseEntered - if (!mouseEntered) { return } - - mouseEntered = false - - // If the mouse is currently hidden, we want to show it when we exit - // this view. We go through the cursorVisible dance so that only - // cursorUpdate manages cursor state. - if (cursorVisible == .hidden) { - cursorVisible = .pendingVisible - cursorUpdate(with: NSEvent()) - assert(cursorVisible == .visible) - - // We set the state to pending hidden again for the next time - // we enter. - cursorVisible = .pendingHidden - } - } - override func scrollWheel(with event: NSEvent) { guard let surface = self.surface else { return } @@ -675,24 +584,6 @@ extension Ghostty { quickLook(with: event) } - override func cursorUpdate(with event: NSEvent) { - switch (cursorVisible) { - case .visible, .hidden: - // Do nothing, stable state - break - - case .pendingHidden: - NSCursor.hide() - cursorVisible = .hidden - - case .pendingVisible: - NSCursor.unhide() - cursorVisible = .visible - } - - cursor.set() - } - override func keyDown(with event: NSEvent) { guard let surface = self.surface else { self.interpretKeyEvents([event]) diff --git a/macos/Sources/Helpers/Backport.swift b/macos/Sources/Helpers/Backport.swift index 000251e49..2d4eef5ca 100644 --- a/macos/Sources/Helpers/Backport.swift +++ b/macos/Sources/Helpers/Backport.swift @@ -25,6 +25,14 @@ extension Backport where Content: Scene { } extension Backport where Content: View { + func pointerVisibility(_ v: BackportVisibility) -> some View { + if #available(macOS 15, *) { + return content.pointerVisibility(v.official) + } else { + return content + } + } + func pointerStyle(_ style: BackportPointerStyle) -> some View { if #available(macOS 15, *) { return content.pointerStyle(style.official) @@ -32,19 +40,52 @@ extension Backport where Content: View { return content } } +} - enum BackportPointerStyle { - case grabIdle - case grabActive - case link +enum BackportVisibility { + case automatic + case visible + case hidden - @available(macOS 15, *) - var official: PointerStyle { - switch self { - case .grabIdle: return .grabIdle - case .grabActive: return .grabActive - case .link: return .link - } + @available(macOS 15, *) + var official: Visibility { + switch self { + case .automatic: return .automatic + case .visible: return .visible + case .hidden: return .hidden + } + } +} + +enum BackportPointerStyle { + case `default` + case grabIdle + case grabActive + case horizontalText + case verticalText + case link + case resizeLeft + case resizeRight + case resizeUp + case resizeDown + case resizeUpDown + case resizeLeftRight + + @available(macOS 15, *) + var official: PointerStyle { + switch self { + case .default: return .default + case .grabIdle: return .grabIdle + case .grabActive: return .grabActive + case .horizontalText: return .horizontalText + case .verticalText: return .verticalText + case .link: return .link + case .resizeLeft: return .frameResize(position: .trailing, directions: [.inward]) + case .resizeRight: return .frameResize(position: .leading, directions: [.inward]) + case .resizeUp: return .frameResize(position: .bottom, directions: [.inward]) + case .resizeDown: return .frameResize(position: .top, directions: [.inward]) + case .resizeUpDown: return .frameResize(position: .top) + case .resizeLeftRight: return .frameResize(position: .trailing) } } } From 0e1258b7fefa32c961a724585b803128d8dd622e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Sep 2024 21:35:49 -0700 Subject: [PATCH 2/7] macos: pointer style uses macOS 15 helpers --- macos/Sources/Ghostty/SurfaceView.swift | 1 + .../Sources/Ghostty/SurfaceView_AppKit.swift | 42 +++++++++---------- macos/Sources/Helpers/View+Extension.swift | 13 ++++++ 3 files changed, 35 insertions(+), 21 deletions(-) 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 + } + } +} From c01bdc6d7c71746cecec8b0a04879e1569dfb41a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Sep 2024 21:46:39 -0700 Subject: [PATCH 3/7] macos: use pointerStyle for SplitView Divider --- macos/Sources/Helpers/Backport.swift | 4 ++-- .../Helpers/SplitView/SplitView.Divider.swift | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/macos/Sources/Helpers/Backport.swift b/macos/Sources/Helpers/Backport.swift index 2d4eef5ca..c8c6de309 100644 --- a/macos/Sources/Helpers/Backport.swift +++ b/macos/Sources/Helpers/Backport.swift @@ -33,9 +33,9 @@ extension Backport where Content: View { } } - func pointerStyle(_ style: BackportPointerStyle) -> some View { + func pointerStyle(_ style: BackportPointerStyle?) -> some View { if #available(macOS 15, *) { - return content.pointerStyle(style.official) + return content.pointerStyle(style?.official) } else { return content } diff --git a/macos/Sources/Helpers/SplitView/SplitView.Divider.swift b/macos/Sources/Helpers/SplitView/SplitView.Divider.swift index f1a7f666d..83847ff0c 100644 --- a/macos/Sources/Helpers/SplitView/SplitView.Divider.swift +++ b/macos/Sources/Helpers/SplitView/SplitView.Divider.swift @@ -44,15 +44,30 @@ extension SplitView { } } + private var pointerStyle: BackportPointerStyle { + return switch (direction) { + case .horizontal: .resizeLeftRight + case .vertical: .resizeUpDown + } + } + var body: some View { ZStack { Color.clear .frame(width: invisibleWidth, height: invisibleHeight) + .contentShape(Rectangle()) // Makes it hit testable for pointerStyle Rectangle() .fill(color) .frame(width: visibleWidth, height: visibleHeight) } + .backport.pointerStyle(pointerStyle) .onHover { isHovered in + // macOS 15+ we use the pointerStyle helper which is much less + // error-prone versus manual NSCursor push/pop + if #available(macOS 15, *) { + return + } + if (isHovered) { switch (direction) { case .horizontal: From f9bd009ce5bb16dee2fc9c04ff0523a9520cf3b2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 21 Sep 2024 09:59:19 -0700 Subject: [PATCH 4/7] macos: unhide cursor on clipboard confirmation --- macos/Ghostty.xcodeproj/project.pbxproj | 4 ++ .../ClipboardConfirmationView.swift | 22 +++++++++++ macos/Sources/Ghostty/SurfaceView.swift | 17 ++++++++- macos/Sources/Helpers/Cursor.swift | 38 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 macos/Sources/Helpers/Cursor.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 111c9aeef..ea866e430 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; }; A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; }; A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; }; + A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.swift */; }; A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */; }; A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36142C9CDA03004D6760 /* View+Extension.swift */; }; A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */; }; @@ -128,6 +129,7 @@ A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = ""; }; A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = ""; }; + A5CBD0572C9F30860017A1AE /* Cursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cursor.swift; sourceTree = ""; }; A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInputOverlay.swift; sourceTree = ""; }; A5CC36142C9CDA03004D6760 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = ""; }; @@ -212,6 +214,7 @@ children = ( A5CEAFFE29C2410700646FDA /* Backport.swift */, A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */, + A5CBD0572C9F30860017A1AE /* Cursor.swift */, A5D0AF3C2B37804400D21823 /* CodableBridge.swift */, 8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */, A59630962AEE163600D64628 /* HostingWindow.swift */, @@ -527,6 +530,7 @@ A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */, AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */, A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */, + A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */, A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */, A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */, A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */, diff --git a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift index 8a4f24678..1a7272e16 100644 --- a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift +++ b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift @@ -34,6 +34,9 @@ struct ClipboardConfirmationView: View { /// Optional delegate to get results. If this is nil, then this view will never close on its own. weak var delegate: ClipboardConfirmationViewDelegate? = nil + /// Used to track if we should rehide on disappear + @State private var cursorHiddenCount: UInt = 0 + var body: some View { VStack { HStack { @@ -65,6 +68,25 @@ struct ClipboardConfirmationView: View { } .padding(.bottom) } + .onAppear { + // I can't find a better way to handle this. There is no API to detect + // if the cursor is hidden and OTHER THINGS do unhide the cursor. So we + // try to unhide it completely here and hope for the best. Issue #1516. + cursorHiddenCount = Cursor.unhideCompletely() + + // If we didn't unhide anything, we just send an unhide to be safe. + // I don't think the count can go negative on NSCursor so this handles + // scenarios cursor is hidden outside of our own NSCursor usage. + if (cursorHiddenCount == 0) { + _ = Cursor.unhide() + } + } + .onDisappear { + // Rehide if we unhid + for _ in 0.. Bool { + // Its always safe to call unhide when the counter is zero because it + // won't go negative. + NSCursor.unhide() + + if (counter > 0) { + counter -= 1 + return true + } + + return false + } + + static func unhideCompletely() -> UInt { + let counter = self.counter + for _ in 0.. Date: Sat, 21 Sep 2024 10:00:30 -0700 Subject: [PATCH 5/7] typos --- macos/Sources/Ghostty/SurfaceView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index e4c9de1fd..8aa4e18ad 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -60,7 +60,7 @@ extension Ghostty { @EnvironmentObject private var ghostty: Ghostty.App // The visibility state of the mouse pointer - private var pointerVisibililty: BackportVisibility { + private var pointerVisibility: BackportVisibility { // If our window or surface loses focus we always bring it back if (!windowFocus || !surfaceFocus) { return .visible @@ -97,7 +97,7 @@ extension Ghostty { .focusedValue(\.ghosttySurfaceView, surfaceView) .focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize) #if canImport(AppKit) - .backport.pointerVisibility(pointerVisibililty) + .backport.pointerVisibility(pointerVisibility) .backport.pointerStyle(surfaceView.pointerStyle) .onReceive(pubBecomeKey) { notification in guard let window = notification.object as? NSWindow else { return } From 3769c83bdfcdbc5568eafc26c041673097e16755 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 21 Sep 2024 10:03:49 -0700 Subject: [PATCH 6/7] config: note that mouse hide while typing on macos requires 15+ --- src/config/Config.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config/Config.zig b/src/config/Config.zig index 35ca981f8..9bc518326 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -429,6 +429,8 @@ palette: Palette = .{}, /// Hide the mouse immediately when typing. The mouse becomes visible again when /// the mouse is used. The mouse is only hidden if the mouse cursor is over the /// active terminal surface. +/// +/// macOS: This feature requires macOS 15.0 (Sequoia) or later. @"mouse-hide-while-typing": bool = false, /// Determines whether running programs can detect the shift key pressed with a From d0903846007f1b3b8cde0727c8c01e344e139ebb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 21 Sep 2024 10:22:04 -0700 Subject: [PATCH 7/7] macos: fix non-AppKit builds --- macos/Ghostty.xcodeproj/project.pbxproj | 2 ++ macos/Sources/Ghostty/SurfaceView.swift | 2 ++ macos/Sources/Helpers/Backport.swift | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index ea866e430..ee4b52779 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; }; A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; }; A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.swift */; }; + A5CBD0592C9F37B10017A1AE /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; }; A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */; }; A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36142C9CDA03004D6760 /* View+Extension.swift */; }; A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */; }; @@ -566,6 +567,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A5CBD0592C9F37B10017A1AE /* Backport.swift in Sources */, A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */, A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */, A5333E232B5A219A008AEFF7 /* SurfaceView.swift in Sources */, diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 8aa4e18ad..5eb277ba1 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -59,6 +59,7 @@ extension Ghostty { @EnvironmentObject private var ghostty: Ghostty.App + #if canImport(AppKit) // The visibility state of the mouse pointer private var pointerVisibility: BackportVisibility { // If our window or surface loses focus we always bring it back @@ -73,6 +74,7 @@ extension Ghostty { return .hidden } } + #endif var body: some View { let center = NotificationCenter.default diff --git a/macos/Sources/Helpers/Backport.swift b/macos/Sources/Helpers/Backport.swift index c8c6de309..8c3c10502 100644 --- a/macos/Sources/Helpers/Backport.swift +++ b/macos/Sources/Helpers/Backport.swift @@ -26,19 +26,27 @@ extension Backport where Content: Scene { extension Backport where Content: View { func pointerVisibility(_ v: BackportVisibility) -> some View { + #if canImport(AppKit) if #available(macOS 15, *) { return content.pointerVisibility(v.official) } else { return content } + #else + return content + #endif } func pointerStyle(_ style: BackportPointerStyle?) -> some View { + #if canImport(AppKit) if #available(macOS 15, *) { return content.pointerStyle(style?.official) } else { return content } + #else + return content + #endif } } @@ -71,6 +79,7 @@ enum BackportPointerStyle { case resizeUpDown case resizeLeftRight + #if canImport(AppKit) @available(macOS 15, *) var official: PointerStyle { switch self { @@ -88,4 +97,5 @@ enum BackportPointerStyle { case .resizeLeftRight: return .frameResize(position: .trailing) } } + #endif }