mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
fix (macOS): Show quick terminal on full-screen app (#4049)
closes #2721 This PR resolves the issue where the Quick Terminal was not visible when pressing the global keybind while a full-screen app was active. ### Changes - Added new configuration options for `quick-terminal-space-behavior` - The Quick Terminal will now overlay properly on top of full-screen applications #### Behavior ##### `quick-terminal-space-behavior = remain` - The Quick Terminal will be remain open on the space when switching spaces. ##### `quick-terminal-space-behavior = move` - The Quick Terminal will be moved to active space when switching spaces.
This commit is contained in:
@ -102,6 +102,7 @@
|
|||||||
C159E89D2B69A2EF00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; };
|
C159E89D2B69A2EF00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; };
|
||||||
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EA62B738B9900404083 /* NSView+Extension.swift */; };
|
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EA62B738B9900404083 /* NSView+Extension.swift */; };
|
||||||
C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EE82B76CBFC00404083 /* VibrantLayer.m */; };
|
C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EE82B76CBFC00404083 /* VibrantLayer.m */; };
|
||||||
|
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */; };
|
||||||
FC5218FA2D10FFCE004C93E0 /* zsh in Resources */ = {isa = PBXBuildFile; fileRef = FC5218F92D10FFC7004C93E0 /* zsh */; };
|
FC5218FA2D10FFCE004C93E0 /* zsh in Resources */ = {isa = PBXBuildFile; fileRef = FC5218F92D10FFC7004C93E0 /* zsh */; };
|
||||||
FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */ = {isa = PBXBuildFile; fileRef = FC9ABA9B2D0F538D0020D4C8 /* bash-completion */; };
|
FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */ = {isa = PBXBuildFile; fileRef = FC9ABA9B2D0F538D0020D4C8 /* bash-completion */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@ -198,6 +199,7 @@
|
|||||||
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantLayer.h; sourceTree = "<group>"; };
|
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantLayer.h; sourceTree = "<group>"; };
|
||||||
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantLayer.m; sourceTree = "<group>"; };
|
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantLayer.m; sourceTree = "<group>"; };
|
||||||
C1F26EEA2B76CC2400404083 /* ghostty-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ghostty-bridging-header.h"; sourceTree = "<group>"; };
|
C1F26EEA2B76CC2400404083 /* ghostty-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ghostty-bridging-header.h"; sourceTree = "<group>"; };
|
||||||
|
CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalSpaceBehavior.swift; sourceTree = "<group>"; };
|
||||||
FC5218F92D10FFC7004C93E0 /* zsh */ = {isa = PBXFileReference; lastKnownFileType = folder; name = zsh; path = "../zig-out/share/zsh"; sourceTree = "<group>"; };
|
FC5218F92D10FFC7004C93E0 /* zsh */ = {isa = PBXFileReference; lastKnownFileType = folder; name = zsh; path = "../zig-out/share/zsh"; sourceTree = "<group>"; };
|
||||||
FC9ABA9B2D0F538D0020D4C8 /* bash-completion */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "bash-completion"; path = "../zig-out/share/bash-completion"; sourceTree = "<group>"; };
|
FC9ABA9B2D0F538D0020D4C8 /* bash-completion */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "bash-completion"; path = "../zig-out/share/bash-completion"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@ -448,6 +450,7 @@
|
|||||||
children = (
|
children = (
|
||||||
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */,
|
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */,
|
||||||
A5CBD05D2CA0C5E70017A1AE /* QuickTerminalController.swift */,
|
A5CBD05D2CA0C5E70017A1AE /* QuickTerminalController.swift */,
|
||||||
|
CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */,
|
||||||
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
|
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
|
||||||
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */,
|
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */,
|
||||||
A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */,
|
A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */,
|
||||||
@ -616,6 +619,7 @@
|
|||||||
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,
|
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,
|
||||||
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
||||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
||||||
|
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
|
||||||
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
||||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
||||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
||||||
|
@ -3,6 +3,12 @@ import Cocoa
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
|
// This is a Apple's private function that we need to call to get the active space.
|
||||||
|
@_silgen_name("CGSGetActiveSpace")
|
||||||
|
func CGSGetActiveSpace(_ cid: Int) -> size_t
|
||||||
|
@_silgen_name("CGSMainConnectionID")
|
||||||
|
func CGSMainConnectionID() -> Int
|
||||||
|
|
||||||
/// Controller for the "quick" terminal.
|
/// Controller for the "quick" terminal.
|
||||||
class QuickTerminalController: BaseTerminalController {
|
class QuickTerminalController: BaseTerminalController {
|
||||||
override var windowNibName: NSNib.Name? { "QuickTerminal" }
|
override var windowNibName: NSNib.Name? { "QuickTerminal" }
|
||||||
@ -18,6 +24,9 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
/// application to the front.
|
/// application to the front.
|
||||||
private var previousApp: NSRunningApplication? = nil
|
private var previousApp: NSRunningApplication? = nil
|
||||||
|
|
||||||
|
// The active space when the quick terminal was last shown.
|
||||||
|
private var previousActiveSpace: size_t = 0
|
||||||
|
|
||||||
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
||||||
private var derivedConfig: DerivedConfig
|
private var derivedConfig: DerivedConfig
|
||||||
|
|
||||||
@ -107,8 +116,28 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
self.previousApp = nil
|
self.previousApp = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if (derivedConfig.quickTerminalAutoHide) {
|
if derivedConfig.quickTerminalAutoHide {
|
||||||
|
switch derivedConfig.quickTerminalSpaceBehavior {
|
||||||
|
case .remain:
|
||||||
|
// If we lose focus on the active space, then we can animate out
|
||||||
animateOut()
|
animateOut()
|
||||||
|
|
||||||
|
case .move:
|
||||||
|
let currentActiveSpace = CGSGetActiveSpace(CGSMainConnectionID())
|
||||||
|
if previousActiveSpace == currentActiveSpace {
|
||||||
|
// We haven't moved spaces. We lost focus to another app on the
|
||||||
|
// current space. Animate out.
|
||||||
|
animateOut()
|
||||||
|
} else {
|
||||||
|
// We've moved to a different space. Bring the quick terminal back
|
||||||
|
// into view.
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.window?.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.previousActiveSpace = currentActiveSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +192,9 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set previous active space
|
||||||
|
self.previousActiveSpace = CGSGetActiveSpace(CGSMainConnectionID())
|
||||||
|
|
||||||
// Animate the window in
|
// Animate the window in
|
||||||
animateWindowIn(window: window, from: position)
|
animateWindowIn(window: window, from: position)
|
||||||
|
|
||||||
@ -199,7 +231,9 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
position.setInitial(in: window, on: screen)
|
position.setInitial(in: window, on: screen)
|
||||||
|
|
||||||
// Move it to the visible position since animation requires this
|
// Move it to the visible position since animation requires this
|
||||||
|
DispatchQueue.main.async {
|
||||||
window.makeKeyAndOrderFront(nil)
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Run the animation that moves our window into the proper place and makes
|
// Run the animation that moves our window into the proper place and makes
|
||||||
// it visible.
|
// it visible.
|
||||||
@ -276,6 +310,14 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
|
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
|
||||||
|
// If the window isn't on our active space then we don't animate, we just
|
||||||
|
// hide it.
|
||||||
|
if !window.isOnActiveSpace {
|
||||||
|
self.previousApp = nil
|
||||||
|
window.orderOut(self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// We always animate out to whatever screen the window is actually on.
|
// We always animate out to whatever screen the window is actually on.
|
||||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||||
|
|
||||||
@ -311,6 +353,9 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
private func syncAppearance() {
|
private func syncAppearance() {
|
||||||
guard let window else { return }
|
guard let window else { return }
|
||||||
|
|
||||||
|
// Change the collection behavior of the window depending on the configuration.
|
||||||
|
window.collectionBehavior = derivedConfig.quickTerminalSpaceBehavior.collectionBehavior
|
||||||
|
|
||||||
// If our window is not visible, then no need to sync the appearance yet.
|
// If our window is not visible, then no need to sync the appearance yet.
|
||||||
// Some APIs such as window blur have no effect unless the window is visible.
|
// Some APIs such as window blur have no effect unless the window is visible.
|
||||||
guard window.isVisible else { return }
|
guard window.isVisible else { return }
|
||||||
@ -396,6 +441,7 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
let quickTerminalScreen: QuickTerminalScreen
|
let quickTerminalScreen: QuickTerminalScreen
|
||||||
let quickTerminalAnimationDuration: Double
|
let quickTerminalAnimationDuration: Double
|
||||||
let quickTerminalAutoHide: Bool
|
let quickTerminalAutoHide: Bool
|
||||||
|
let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
|
||||||
let windowColorspace: String
|
let windowColorspace: String
|
||||||
let backgroundOpacity: Double
|
let backgroundOpacity: Double
|
||||||
|
|
||||||
@ -403,6 +449,7 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
self.quickTerminalScreen = .main
|
self.quickTerminalScreen = .main
|
||||||
self.quickTerminalAnimationDuration = 0.2
|
self.quickTerminalAnimationDuration = 0.2
|
||||||
self.quickTerminalAutoHide = true
|
self.quickTerminalAutoHide = true
|
||||||
|
self.quickTerminalSpaceBehavior = .move
|
||||||
self.windowColorspace = ""
|
self.windowColorspace = ""
|
||||||
self.backgroundOpacity = 1.0
|
self.backgroundOpacity = 1.0
|
||||||
}
|
}
|
||||||
@ -411,6 +458,7 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
self.quickTerminalScreen = config.quickTerminalScreen
|
self.quickTerminalScreen = config.quickTerminalScreen
|
||||||
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
|
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
|
||||||
self.quickTerminalAutoHide = config.quickTerminalAutoHide
|
self.quickTerminalAutoHide = config.quickTerminalAutoHide
|
||||||
|
self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
|
||||||
self.windowColorspace = config.windowColorspace
|
self.windowColorspace = config.windowColorspace
|
||||||
self.backgroundOpacity = config.backgroundOpacity
|
self.backgroundOpacity = config.backgroundOpacity
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import Foundation
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
enum QuickTerminalSpaceBehavior {
|
||||||
|
case remain
|
||||||
|
case move
|
||||||
|
|
||||||
|
init?(fromGhosttyConfig string: String) {
|
||||||
|
switch (string) {
|
||||||
|
case "move":
|
||||||
|
self = .move
|
||||||
|
|
||||||
|
case "remain":
|
||||||
|
self = .remain
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var collectionBehavior: NSWindow.CollectionBehavior {
|
||||||
|
let commonBehavior: [NSWindow.CollectionBehavior] = [
|
||||||
|
.ignoresCycle,
|
||||||
|
.fullScreenAuxiliary
|
||||||
|
]
|
||||||
|
|
||||||
|
switch (self) {
|
||||||
|
case .move:
|
||||||
|
// We want this to move the window to the active space.
|
||||||
|
return NSWindow.CollectionBehavior([.canJoinAllSpaces] + commonBehavior)
|
||||||
|
case .remain:
|
||||||
|
// We want this to remain the window in the current space.
|
||||||
|
return NSWindow.CollectionBehavior([.moveToActiveSpace] + commonBehavior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class QuickTerminalWindow: NSWindow {
|
class QuickTerminalWindow: NSPanel {
|
||||||
// Both of these must be true for windows without decorations to be able to
|
// Both of these must be true for windows without decorations to be able to
|
||||||
// still become key/main and receive events.
|
// still become key/main and receive events.
|
||||||
override var canBecomeKey: Bool { return true }
|
override var canBecomeKey: Bool { return true }
|
||||||
@ -26,6 +26,9 @@ class QuickTerminalWindow: NSWindow {
|
|||||||
// window remains resizable.
|
// window remains resizable.
|
||||||
self.styleMask.remove(.titled)
|
self.styleMask.remove(.titled)
|
||||||
|
|
||||||
|
// We don't want to activate the owning app when quick terminal is triggered.
|
||||||
|
self.styleMask.insert(.nonactivatingPanel)
|
||||||
|
|
||||||
// We need to set our window level to a high value. In testing, only
|
// We need to set our window level to a high value. In testing, only
|
||||||
// popUpMenu and above do what we want. This gets it above the menu bar
|
// popUpMenu and above do what we want. This gets it above the menu bar
|
||||||
// and lets us render off screen.
|
// and lets us render off screen.
|
||||||
@ -41,7 +44,7 @@ class QuickTerminalWindow: NSWindow {
|
|||||||
// We don't want to be part of command-tilde
|
// We don't want to be part of command-tilde
|
||||||
.ignoresCycle,
|
.ignoresCycle,
|
||||||
|
|
||||||
// We never support fullscreen
|
// We want to show the window on another space if it is visible
|
||||||
.fullScreenNone]
|
.fullScreenAuxiliary]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,6 +431,16 @@ extension Ghostty {
|
|||||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior {
|
||||||
|
guard let config = self.config else { return .move }
|
||||||
|
var v: UnsafePointer<Int8>? = nil
|
||||||
|
let key = "quick-terminal-space-behavior"
|
||||||
|
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .move }
|
||||||
|
guard let ptr = v else { return .move }
|
||||||
|
let str = String(cString: ptr)
|
||||||
|
return QuickTerminalSpaceBehavior(fromGhosttyConfig: str) ?? .move
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var resizeOverlay: ResizeOverlay {
|
var resizeOverlay: ResizeOverlay {
|
||||||
|
@ -1567,6 +1567,23 @@ keybind: Keybinds = .{},
|
|||||||
/// Set it to false for the quick terminal to remain open even when it loses focus.
|
/// Set it to false for the quick terminal to remain open even when it loses focus.
|
||||||
@"quick-terminal-autohide": bool = true,
|
@"quick-terminal-autohide": bool = true,
|
||||||
|
|
||||||
|
/// This configuration option determines the behavior of the quick terminal
|
||||||
|
/// when switching between macOS spaces. macOS spaces are virtual desktops
|
||||||
|
/// that can be manually created or are automatically created when an
|
||||||
|
/// application is in full-screen mode.
|
||||||
|
///
|
||||||
|
/// Valid values are:
|
||||||
|
///
|
||||||
|
/// * `move` - When switching to another space, the quick terminal will
|
||||||
|
/// also moved to the current space.
|
||||||
|
///
|
||||||
|
/// * `remain` - The quick terminal will stay only in the space where it
|
||||||
|
/// was originally opened and will not follow when switching to another
|
||||||
|
/// space.
|
||||||
|
///
|
||||||
|
/// The default value is `move`.
|
||||||
|
@"quick-terminal-space-behavior": QuickTerminalSpaceBehavior = .move,
|
||||||
|
|
||||||
/// Whether to enable shell integration auto-injection or not. Shell integration
|
/// Whether to enable shell integration auto-injection or not. Shell integration
|
||||||
/// greatly enhances the terminal experience by enabling a number of features:
|
/// greatly enhances the terminal experience by enabling a number of features:
|
||||||
///
|
///
|
||||||
@ -5705,6 +5722,12 @@ pub const QuickTerminalScreen = enum {
|
|||||||
@"macos-menu-bar",
|
@"macos-menu-bar",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// See quick-terminal-space-behavior
|
||||||
|
pub const QuickTerminalSpaceBehavior = enum {
|
||||||
|
remain,
|
||||||
|
move,
|
||||||
|
};
|
||||||
|
|
||||||
/// See grapheme-width-method
|
/// See grapheme-width-method
|
||||||
pub const GraphemeWidthMethod = enum {
|
pub const GraphemeWidthMethod = enum {
|
||||||
legacy,
|
legacy,
|
||||||
|
Reference in New Issue
Block a user