From 4f9d49b380b957fbfa559d5f09349e85cdbc6b1f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Sep 2024 20:21:47 -0700 Subject: [PATCH] macos: handle multiple monitors properly --- macos/Ghostty.xcodeproj/project.pbxproj | 4 ++ .../QuickTerminalController.swift | 9 +++-- .../QuickTerminal/QuickTerminalPosition.swift | 14 +++---- .../QuickTerminal/QuickTerminalScreen.swift | 37 +++++++++++++++++++ macos/Sources/Ghostty/Ghostty.Config.swift | 10 +++++ src/config/Config.zig | 27 ++++++++++++++ 6 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 macos/Sources/Features/QuickTerminal/QuickTerminalScreen.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 295de738c..e3ad5adf3 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ A51BFC272B30F1B800E92F16 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A51BFC262B30F1B800E92F16 /* Sparkle */; }; A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */; }; A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */; }; + A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */; }; A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; }; A5333E1D2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; }; A5333E202B5A2111008AEFF7 /* SurfaceView_UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */; }; @@ -103,6 +104,7 @@ A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyDebug.entitlements; sourceTree = ""; }; A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateDelegate.swift; sourceTree = ""; }; A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Input.swift; sourceTree = ""; }; + A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalScreen.swift; sourceTree = ""; }; A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_UIKit.swift; sourceTree = ""; }; A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrossKit.swift; sourceTree = ""; }; A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_AppKit.swift; sourceTree = ""; }; @@ -391,6 +393,7 @@ A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */, A5CBD05D2CA0C5E70017A1AE /* QuickTerminalController.swift */, A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */, + A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */, A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */, ); path = QuickTerminal; @@ -588,6 +591,7 @@ A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */, A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */, A55685E029A03A9F004303CE /* AppError.swift in Sources */, + A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */, A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */, A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */, A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */, diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift index a185eabfe..f5d899e76 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift @@ -66,7 +66,9 @@ class QuickTerminalController: BaseTerminalController { } func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize { - guard let screen = NSScreen.main else { return frameSize } + // We use the actual screen the window is on for this, since it should + // be on the proper screen. + guard let screen = window?.screen ?? NSScreen.main else { return frameSize } return position.restrictFrameSize(frameSize, on: screen) } @@ -132,7 +134,7 @@ class QuickTerminalController: BaseTerminalController { } private func animateWindowIn(window: NSWindow, from position: QuickTerminalPosition) { - guard let screen = NSScreen.main else { return } + guard let screen = ghostty.config.quickTerminalScreen.screen else { return } // Move our window off screen to the top position.setInitial(in: window, on: screen) @@ -150,7 +152,8 @@ class QuickTerminalController: BaseTerminalController { } private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) { - guard let screen = NSScreen.main else { return } + // We always animate out to whatever screen the window is actually on. + guard let screen = window.screen ?? NSScreen.main else { return } // Keep track of if we were the key window. If we were the key window then we // want to move focus to the next window so that focus is preserved somewhere diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalPosition.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalPosition.swift index 559d7ef88..51b450700 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalPosition.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalPosition.swift @@ -36,7 +36,7 @@ enum QuickTerminalPosition : String { // Position depends window.setFrame(.init( origin: initialOrigin(for: window, on: screen), - size: window.frame.size + size: restrictFrameSize(window.frame.size, on: screen) ), display: false) } @@ -48,7 +48,7 @@ enum QuickTerminalPosition : String { // Position depends window.setFrame(.init( origin: finalOrigin(for: window, on: screen), - size: window.frame.size + size: restrictFrameSize(window.frame.size, on: screen) ), display: true) } @@ -70,10 +70,10 @@ enum QuickTerminalPosition : String { func initialOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint { switch (self) { case .top: - return .init(x: 0, y: screen.frame.maxY) + return .init(x: screen.frame.minX, y: screen.frame.maxY) case .bottom: - return .init(x: 0, y: -window.frame.height) + return .init(x: screen.frame.minX, y: -window.frame.height) case .left: return .init(x: -window.frame.width, y: 0) @@ -87,13 +87,13 @@ enum QuickTerminalPosition : String { func finalOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint { switch (self) { case .top: - return .init(x: window.frame.origin.x, y: screen.visibleFrame.maxY - window.frame.height) + return .init(x: screen.frame.minX, y: screen.visibleFrame.maxY - window.frame.height) case .bottom: - return .init(x: window.frame.origin.x, y: 0) + return .init(x: screen.frame.minX, y: screen.frame.minY) case .left: - return .init(x: 0, y: window.frame.origin.y) + return .init(x: screen.frame.minX, y: window.frame.origin.y) case .right: return .init(x: screen.visibleFrame.maxX - window.frame.width, y: window.frame.origin.y) diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalScreen.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalScreen.swift new file mode 100644 index 000000000..cd07a6f12 --- /dev/null +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalScreen.swift @@ -0,0 +1,37 @@ +import Cocoa + +enum QuickTerminalScreen { + case main + case mouse + case menuBar + + init?(fromGhosttyConfig string: String) { + switch (string) { + case "main": + self = .main + + case "mouse": + self = .mouse + + case "macos-menu-bar": + self = .menuBar + + default: + return nil + } + } + + var screen: NSScreen? { + switch (self) { + case .main: + return NSScreen.main + + case .mouse: + let mouseLoc = NSEvent.mouseLocation + return NSScreen.screens.first(where: { $0.frame.contains(mouseLoc) }) + + case .menuBar: + return NSScreen.screens.first + } + } +} diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 936ac9821..76f85d2a3 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -342,6 +342,16 @@ extension Ghostty { let str = String(cString: ptr) return QuickTerminalPosition(rawValue: str) ?? .top } + + var quickTerminalScreen: QuickTerminalScreen { + guard let config = self.config else { return .main } + var v: UnsafePointer? = nil + let key = "quick-terminal-screen" + guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .main } + guard let ptr = v else { return .main } + let str = String(cString: ptr) + return QuickTerminalScreen(fromGhosttyConfig: str) ?? .main + } #endif var resizeOverlay: ResizeOverlay { diff --git a/src/config/Config.zig b/src/config/Config.zig index c950cf807..0f5e9b81b 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1234,6 +1234,26 @@ keybind: Keybinds = .{}, /// Changing this configuration requires restarting Ghostty completely. @"quick-terminal-position": QuickTerminalPosition = .top, +/// The screen where the quick terminal should show up. +/// +/// Valid values are: +/// +/// * `main` - The screen that the operating system recommends as the main +/// screen. On macOS, this is the screen that is currently receiving +/// keyboard input. This screen is defined by the operating system and +/// not chosen by Ghostty. +/// +/// * `mouse` - The screen that the mouse is currently hovered over. +/// +/// * `macos-menu-bar` - The screen that contains the macOS menu bar as +/// set in the display settings on macOS. This is a bit confusing because +/// every screen on macOS has a menu bar, but this is the screen that +/// contains the primary menu bar. +/// +/// The default value is `main` because this is the recommended screen +/// by the operating system. +@"quick-terminal-screen": QuickTerminalScreen = .main, + /// Whether to enable shell integration auto-injection or not. Shell integration /// greatly enhances the terminal experience by enabling a number of features: /// @@ -4423,6 +4443,13 @@ pub const QuickTerminalPosition = enum { right, }; +/// See quick-terminal-screen +pub const QuickTerminalScreen = enum { + main, + mouse, + @"macos-menu-bar", +}; + /// See grapheme-width-method pub const GraphemeWidthMethod = enum { legacy,