mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: setup colorspace in base terminal controller
Fixes #2519 This sets up the colorspace for terminal windows in the base controller. This also modifies some of our logic so its easier for subclasses of base controllers to specify custom logic when the configuration reloads, since that's likely to be a common thing.
This commit is contained in:
@ -496,11 +496,6 @@ class AppDelegate: NSObject,
|
|||||||
// AppKit mutex on the appearance.
|
// AppKit mutex on the appearance.
|
||||||
DispatchQueue.main.async { self.syncAppearance() }
|
DispatchQueue.main.async { self.syncAppearance() }
|
||||||
|
|
||||||
// Update all of our windows
|
|
||||||
terminalManager.windows.forEach { window in
|
|
||||||
window.controller.configDidReload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have configuration errors, we need to show them.
|
// If we have configuration errors, we need to show them.
|
||||||
let c = ConfigurationErrorsController.sharedInstance
|
let c = ConfigurationErrorsController.sharedInstance
|
||||||
c.errors = state.config.errors
|
c.errors = state.config.errors
|
||||||
|
@ -33,11 +33,6 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
selector: #selector(onToggleFullscreen),
|
selector: #selector(onToggleFullscreen),
|
||||||
name: Ghostty.Notification.ghosttyToggleFullscreen,
|
name: Ghostty.Notification.ghosttyToggleFullscreen,
|
||||||
object: nil)
|
object: nil)
|
||||||
center.addObserver(
|
|
||||||
self,
|
|
||||||
selector: #selector(ghosttyDidReloadConfig),
|
|
||||||
name: Ghostty.Notification.ghosttyDidReloadConfig,
|
|
||||||
object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -53,6 +48,8 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
// MARK: NSWindowController
|
// MARK: NSWindowController
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
|
super.windowDidLoad()
|
||||||
|
|
||||||
guard let window = self.window else { return }
|
guard let window = self.window else { return }
|
||||||
|
|
||||||
// The controller is the window delegate so we can detect events such as
|
// The controller is the window delegate so we can detect events such as
|
||||||
@ -63,9 +60,6 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
// make this restorable, but it isn't currently implemented.
|
// make this restorable, but it isn't currently implemented.
|
||||||
window.isRestorable = false
|
window.isRestorable = false
|
||||||
|
|
||||||
// Setup our configured appearance that we support.
|
|
||||||
syncAppearance()
|
|
||||||
|
|
||||||
// Setup our initial size based on our configured position
|
// Setup our initial size based on our configured position
|
||||||
position.setLoaded(window)
|
position.setLoaded(window)
|
||||||
|
|
||||||
@ -297,35 +291,6 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncAppearance() {
|
|
||||||
guard let window else { return }
|
|
||||||
|
|
||||||
// If our window is not visible, then delay this. This is possible specifically
|
|
||||||
// during state restoration but probably in other scenarios as well. To delay,
|
|
||||||
// we just loop directly on the dispatch queue. We have to delay because some
|
|
||||||
// APIs such as window blur have no effect unless the window is visible.
|
|
||||||
guard window.isVisible else {
|
|
||||||
// Weak window so that if the window changes or is destroyed we aren't holding a ref
|
|
||||||
DispatchQueue.main.async { [weak self] in self?.syncAppearance() }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have window transparency then set it transparent. Otherwise set it opaque.
|
|
||||||
if (ghostty.config.backgroundOpacity < 1) {
|
|
||||||
window.isOpaque = false
|
|
||||||
|
|
||||||
// This is weird, but we don't use ".clear" because this creates a look that
|
|
||||||
// matches Terminal.app much more closer. This lets users transition from
|
|
||||||
// Terminal.app more easily.
|
|
||||||
window.backgroundColor = .white.withAlphaComponent(0.001)
|
|
||||||
|
|
||||||
ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque())
|
|
||||||
} else {
|
|
||||||
window.isOpaque = true
|
|
||||||
window.backgroundColor = .windowBackgroundColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: First Responder
|
// MARK: First Responder
|
||||||
|
|
||||||
@IBAction override func closeWindow(_ sender: Any) {
|
@IBAction override func closeWindow(_ sender: Any) {
|
||||||
@ -357,10 +322,6 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
// We ignore the requested mode and always use non-native for the quick terminal
|
// We ignore the requested mode and always use non-native for the quick terminal
|
||||||
toggleFullscreen(mode: .nonNative)
|
toggleFullscreen(mode: .nonNative)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func ghosttyDidReloadConfig(notification: SwiftUI.Notification) {
|
|
||||||
syncAppearance()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
|
@ -93,6 +93,11 @@ class BaseTerminalController: NSWindowController,
|
|||||||
selector: #selector(didChangeScreenParametersNotification),
|
selector: #selector(didChangeScreenParametersNotification),
|
||||||
name: NSApplication.didChangeScreenParametersNotification,
|
name: NSApplication.didChangeScreenParametersNotification,
|
||||||
object: nil)
|
object: nil)
|
||||||
|
center.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(ghosttyDidReloadConfigNotification),
|
||||||
|
name: Ghostty.Notification.ghosttyDidReloadConfig,
|
||||||
|
object: nil)
|
||||||
|
|
||||||
// Listen for local events that we need to know of outside of
|
// Listen for local events that we need to know of outside of
|
||||||
// single surface handlers.
|
// single surface handlers.
|
||||||
@ -123,7 +128,7 @@ class BaseTerminalController: NSWindowController,
|
|||||||
|
|
||||||
/// Update all surfaces with the focus state. This ensures that libghostty has an accurate view about
|
/// Update all surfaces with the focus state. This ensures that libghostty has an accurate view about
|
||||||
/// what surface is focused. This must be called whenever a surface OR window changes focus.
|
/// what surface is focused. This must be called whenever a surface OR window changes focus.
|
||||||
func syncFocusToSurfaceTree() {
|
private func syncFocusToSurfaceTree() {
|
||||||
guard let tree = self.surfaceTree else { return }
|
guard let tree = self.surfaceTree else { return }
|
||||||
|
|
||||||
for leaf in tree {
|
for leaf in tree {
|
||||||
@ -136,6 +141,48 @@ class BaseTerminalController: NSWindowController,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call this whenever we want to setup our appearance parameters based on
|
||||||
|
// configuration changes.
|
||||||
|
private func syncAppearance() {
|
||||||
|
guard let window else { return }
|
||||||
|
|
||||||
|
// If our window is not visible, then delay this. This is possible specifically
|
||||||
|
// during state restoration but probably in other scenarios as well. To delay,
|
||||||
|
// we just loop directly on the dispatch queue. We have to delay because some
|
||||||
|
// APIs such as window blur have no effect unless the window is visible.
|
||||||
|
guard window.isVisible else {
|
||||||
|
// Weak window so that if the window changes or is destroyed we aren't holding a ref
|
||||||
|
DispatchQueue.main.async { [weak self] in self?.syncAppearance() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have window transparency then set it transparent. Otherwise set it opaque.
|
||||||
|
if (ghostty.config.backgroundOpacity < 1) {
|
||||||
|
window.isOpaque = false
|
||||||
|
|
||||||
|
// This is weird, but we don't use ".clear" because this creates a look that
|
||||||
|
// matches Terminal.app much more closer. This lets users transition from
|
||||||
|
// Terminal.app more easily.
|
||||||
|
window.backgroundColor = .white.withAlphaComponent(0.001)
|
||||||
|
|
||||||
|
ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque())
|
||||||
|
} else {
|
||||||
|
window.isOpaque = true
|
||||||
|
window.backgroundColor = .windowBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminals typically operate in sRGB color space and macOS defaults
|
||||||
|
// to "native" which is typically P3. There is a lot more resources
|
||||||
|
// covered in this GitHub issue: https://github.com/mitchellh/ghostty/pull/376
|
||||||
|
// Ghostty defaults to sRGB but this can be overridden.
|
||||||
|
switch (ghostty.config.windowColorspace) {
|
||||||
|
case .displayP3:
|
||||||
|
window.colorSpace = .displayP3
|
||||||
|
case .srgb:
|
||||||
|
window.colorSpace = .sRGB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Call this whenever the frame changes
|
// Call this whenever the frame changes
|
||||||
private func windowFrameDidChange() {
|
private func windowFrameDidChange() {
|
||||||
// We need to update our saved frame information in case of monitor
|
// We need to update our saved frame information in case of monitor
|
||||||
@ -145,6 +192,14 @@ class BaseTerminalController: NSWindowController,
|
|||||||
savedFrame = .init(window: window.frame, screen: screen.visibleFrame)
|
savedFrame = .init(window: window.frame, screen: screen.visibleFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Overridable Callbacks
|
||||||
|
|
||||||
|
/// Called whenever Ghostty reloads the configuration. Callers should call super.
|
||||||
|
open func ghosttyDidReloadConfig() {
|
||||||
|
// Whenever the config changes we setup our appearance.
|
||||||
|
syncAppearance()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Notifications
|
// MARK: Notifications
|
||||||
|
|
||||||
@objc private func didChangeScreenParametersNotification(_ notification: Notification) {
|
@objc private func didChangeScreenParametersNotification(_ notification: Notification) {
|
||||||
@ -191,6 +246,10 @@ class BaseTerminalController: NSWindowController,
|
|||||||
window.setFrame(newFrame, display: true)
|
window.setFrame(newFrame, display: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func ghosttyDidReloadConfigNotification(notification: SwiftUI.Notification) {
|
||||||
|
ghosttyDidReloadConfig()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Local Events
|
// MARK: Local Events
|
||||||
|
|
||||||
private func localEventHandler(_ event: NSEvent) -> NSEvent? {
|
private func localEventHandler(_ event: NSEvent) -> NSEvent? {
|
||||||
@ -381,7 +440,16 @@ class BaseTerminalController: NSWindowController,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - NSWindowDelegate
|
// MARK: NSWindowController
|
||||||
|
|
||||||
|
override func windowDidLoad() {
|
||||||
|
super.windowDidLoad()
|
||||||
|
|
||||||
|
// Setup our configured appearance that we support.
|
||||||
|
syncAppearance()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: NSWindowDelegate
|
||||||
|
|
||||||
// This is called when performClose is called on a window (NOT when close()
|
// This is called when performClose is called on a window (NOT when close()
|
||||||
// is called directly). performClose is called primarily when UI elements such
|
// is called directly). performClose is called primarily when UI elements such
|
||||||
|
@ -78,14 +78,16 @@ class TerminalController: BaseTerminalController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - Methods
|
override func ghosttyDidReloadConfig() {
|
||||||
|
super.ghosttyDidReloadConfig()
|
||||||
|
|
||||||
func configDidReload() {
|
|
||||||
guard let window = window as? TerminalWindow else { return }
|
guard let window = window as? TerminalWindow else { return }
|
||||||
window.focusFollowsMouse = ghostty.config.focusFollowsMouse
|
window.focusFollowsMouse = ghostty.config.focusFollowsMouse
|
||||||
syncAppearance()
|
syncAppearance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - Methods
|
||||||
|
|
||||||
/// Update the accessory view of each tab according to the keyboard
|
/// Update the accessory view of each tab according to the keyboard
|
||||||
/// shortcut that activates it (if any). This is called when the key window
|
/// shortcut that activates it (if any). This is called when the key window
|
||||||
/// changes, when a window is closed, and when tabs are reordered
|
/// changes, when a window is closed, and when tabs are reordered
|
||||||
@ -164,21 +166,6 @@ class TerminalController: BaseTerminalController {
|
|||||||
window.titlebarFont = nil
|
window.titlebarFont = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have window transparency then set it transparent. Otherwise set it opaque.
|
|
||||||
if (ghostty.config.backgroundOpacity < 1) {
|
|
||||||
window.isOpaque = false
|
|
||||||
|
|
||||||
// This is weird, but we don't use ".clear" because this creates a look that
|
|
||||||
// matches Terminal.app much more closer. This lets users transition from
|
|
||||||
// Terminal.app more easily.
|
|
||||||
window.backgroundColor = .white.withAlphaComponent(0.001)
|
|
||||||
|
|
||||||
ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque())
|
|
||||||
} else {
|
|
||||||
window.isOpaque = true
|
|
||||||
window.backgroundColor = .windowBackgroundColor
|
|
||||||
}
|
|
||||||
|
|
||||||
window.hasShadow = ghostty.config.macosWindowShadow
|
window.hasShadow = ghostty.config.macosWindowShadow
|
||||||
|
|
||||||
guard window.hasStyledTabs else { return }
|
guard window.hasStyledTabs else { return }
|
||||||
@ -208,6 +195,8 @@ class TerminalController: BaseTerminalController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
|
super.windowDidLoad()
|
||||||
|
|
||||||
guard let window = window as? TerminalWindow else { return }
|
guard let window = window as? TerminalWindow else { return }
|
||||||
|
|
||||||
// Setting all three of these is required for restoration to work.
|
// Setting all three of these is required for restoration to work.
|
||||||
@ -220,19 +209,6 @@ class TerminalController: BaseTerminalController {
|
|||||||
// If window decorations are disabled, remove our title
|
// If window decorations are disabled, remove our title
|
||||||
if (!ghostty.config.windowDecorations) { window.styleMask.remove(.titled) }
|
if (!ghostty.config.windowDecorations) { window.styleMask.remove(.titled) }
|
||||||
|
|
||||||
// Terminals typically operate in sRGB color space and macOS defaults
|
|
||||||
// to "native" which is typically P3. There is a lot more resources
|
|
||||||
// covered in this GitHub issue: https://github.com/mitchellh/ghostty/pull/376
|
|
||||||
// Ghostty defaults to sRGB but this can be overridden.
|
|
||||||
switch (ghostty.config.windowColorspace) {
|
|
||||||
case "display-p3":
|
|
||||||
window.colorSpace = .displayP3
|
|
||||||
case "srgb":
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
window.colorSpace = .sRGB
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have only a single surface (no splits) and that surface requested
|
// If we have only a single surface (no splits) and that surface requested
|
||||||
// an initial size then we set it here now.
|
// an initial size then we set it here now.
|
||||||
if case let .leaf(leaf) = surfaceTree {
|
if case let .leaf(leaf) = surfaceTree {
|
||||||
@ -297,9 +273,9 @@ class TerminalController: BaseTerminalController {
|
|||||||
|
|
||||||
// Full size content view so we can extend
|
// Full size content view so we can extend
|
||||||
// content in to the hidden titlebar's area
|
// content in to the hidden titlebar's area
|
||||||
.fullSizeContentView,
|
.fullSizeContentView,
|
||||||
|
|
||||||
.resizable,
|
.resizable,
|
||||||
.closable,
|
.closable,
|
||||||
.miniaturizable,
|
.miniaturizable,
|
||||||
]
|
]
|
||||||
|
@ -128,13 +128,14 @@ extension Ghostty {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
var windowColorspace: String {
|
var windowColorspace: WindowColorspace {
|
||||||
guard let config = self.config else { return "" }
|
guard let config = self.config else { return .srgb }
|
||||||
var v: UnsafePointer<Int8>? = nil
|
var v: UnsafePointer<Int8>? = nil
|
||||||
let key = "window-colorspace"
|
let key = "window-colorspace"
|
||||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .srgb }
|
||||||
guard let ptr = v else { return "" }
|
guard let ptr = v else { return .srgb }
|
||||||
return String(cString: ptr)
|
let str = String(cString: ptr)
|
||||||
|
return WindowColorspace(rawValue: str) ?? .srgb
|
||||||
}
|
}
|
||||||
|
|
||||||
var windowSaveState: String {
|
var windowSaveState: String {
|
||||||
@ -474,4 +475,9 @@ extension Ghostty.Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum WindowColorspace : String {
|
||||||
|
case srgb
|
||||||
|
case displayP3 = "display-p3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user