Merge pull request #2552 from ghostty-org/push-ryrokukruoxr

macos: setup colorspace in base terminal controller
This commit is contained in:
Mitchell Hashimoto
2024-10-30 20:48:30 -04:00
committed by GitHub
5 changed files with 111 additions and 105 deletions

View File

@ -496,11 +496,6 @@ class AppDelegate: NSObject,
// AppKit mutex on the appearance.
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.
let c = ConfigurationErrorsController.sharedInstance
c.errors = state.config.errors

View File

@ -33,11 +33,6 @@ class QuickTerminalController: BaseTerminalController {
selector: #selector(onToggleFullscreen),
name: Ghostty.Notification.ghosttyToggleFullscreen,
object: nil)
center.addObserver(
self,
selector: #selector(ghosttyDidReloadConfig),
name: Ghostty.Notification.ghosttyDidReloadConfig,
object: nil)
}
required init?(coder: NSCoder) {
@ -53,6 +48,8 @@ class QuickTerminalController: BaseTerminalController {
// MARK: NSWindowController
override func windowDidLoad() {
super.windowDidLoad()
guard let window = self.window else { return }
// 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.
window.isRestorable = false
// Setup our configured appearance that we support.
syncAppearance()
// Setup our initial size based on our configured position
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
@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
toggleFullscreen(mode: .nonNative)
}
@objc private func ghosttyDidReloadConfig(notification: SwiftUI.Notification) {
syncAppearance()
}
}
extension Notification.Name {

View File

@ -93,6 +93,11 @@ class BaseTerminalController: NSWindowController,
selector: #selector(didChangeScreenParametersNotification),
name: NSApplication.didChangeScreenParametersNotification,
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
// 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
/// 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 }
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
private func windowFrameDidChange() {
// 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)
}
// 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
@objc private func didChangeScreenParametersNotification(_ notification: Notification) {
@ -191,6 +246,10 @@ class BaseTerminalController: NSWindowController,
window.setFrame(newFrame, display: true)
}
@objc private func ghosttyDidReloadConfigNotification(notification: SwiftUI.Notification) {
ghosttyDidReloadConfig()
}
// MARK: Local Events
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()
// is called directly). performClose is called primarily when UI elements such

View File

@ -78,14 +78,16 @@ class TerminalController: BaseTerminalController {
}
}
//MARK: - Methods
override func ghosttyDidReloadConfig() {
super.ghosttyDidReloadConfig()
func configDidReload() {
guard let window = window as? TerminalWindow else { return }
window.focusFollowsMouse = ghostty.config.focusFollowsMouse
syncAppearance()
}
//MARK: - Methods
/// Update the accessory view of each tab according to the keyboard
/// shortcut that activates it (if any). This is called when the key window
/// changes, when a window is closed, and when tabs are reordered
@ -164,21 +166,6 @@ class TerminalController: BaseTerminalController {
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
guard window.hasStyledTabs else { return }
@ -208,31 +195,20 @@ class TerminalController: BaseTerminalController {
}
override func windowDidLoad() {
super.windowDidLoad()
guard let window = window as? TerminalWindow else { return }
// Setting all three of these is required for restoration to work.
window.isRestorable = restorable
if (restorable) {
window.restorationClass = TerminalWindowRestoration.self
window.identifier = .init(String(describing: TerminalWindowRestoration.self))
}
// If window decorations are disabled, remove our title
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
// an initial size then we set it here now.
if case let .leaf(leaf) = surfaceTree {
@ -245,21 +221,21 @@ class TerminalController: BaseTerminalController {
frame.size.height -= leaf.surface.frame.size.height
frame.size.width += min(initialSize.width, screen.frame.width)
frame.size.height += min(initialSize.height, screen.frame.height)
// We have no tabs and we are not a split, so set the initial size of the window.
window.setFrame(frame, display: true)
}
}
// Center the window to start, we'll move the window frame automatically
// when cascading.
window.center()
// Make sure our theme is set on the window so styling is correct.
if let windowTheme = ghostty.config.windowTheme {
window.windowTheme = .init(rawValue: windowTheme)
}
// Handle titlebar tabs config option. Something about what we do while setting up the
// titlebar tabs interferes with the window restore process unless window.tabbingMode
// is set to .preferred, so we set it, and switch back to automatic as soon as we can.
@ -272,50 +248,50 @@ class TerminalController: BaseTerminalController {
} else if (ghostty.config.macosTitlebarStyle == "transparent") {
window.transparentTabs = true
}
if window.hasStyledTabs {
// Set the background color of the window
let backgroundColor = NSColor(ghostty.config.backgroundColor)
window.backgroundColor = backgroundColor
// This makes sure our titlebar renders correctly when there is a transparent background
window.titlebarColor = backgroundColor.withAlphaComponent(ghostty.config.backgroundOpacity)
}
// Initialize our content view to the SwiftUI root
window.contentView = NSHostingView(rootView: TerminalView(
ghostty: self.ghostty,
viewModel: self,
delegate: self
))
// If our titlebar style is "hidden" we adjust the style appropriately
if (ghostty.config.macosTitlebarStyle == "hidden") {
window.styleMask = [
// We need `titled` in the mask to get the normal window frame
.titled,
// Full size content view so we can extend
// content in to the hidden titlebar's area
.fullSizeContentView,
.resizable,
.fullSizeContentView,
.resizable,
.closable,
.miniaturizable,
]
// Hide the title
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
// Hide the traffic lights (window control buttons)
window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
// Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar.
window.tabbingMode = .disallowed
// Nuke it from orbit -- hide the titlebar container entirely, just in case. There are
// some operations that appear to bring back the titlebar visibility so this ensures
// it is gone forever.
@ -324,7 +300,7 @@ class TerminalController: BaseTerminalController {
titleBarContainer.isHidden = true
}
}
// In various situations, macOS automatically tabs new windows. Ghostty handles
// its own tabbing so we DONT want this behavior. This detects this scenario and undoes
// it.
@ -344,9 +320,9 @@ class TerminalController: BaseTerminalController {
window.tabGroup?.removeWindow(window)
}
}
window.focusFollowsMouse = ghostty.config.focusFollowsMouse
// Apply any additional appearance-related properties to the new window.
syncAppearance()
}

View File

@ -128,13 +128,14 @@ extension Ghostty {
return v
}
var windowColorspace: String {
guard let config = self.config else { return "" }
var windowColorspace: WindowColorspace {
guard let config = self.config else { return .srgb }
var v: UnsafePointer<Int8>? = nil
let key = "window-colorspace"
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
guard let ptr = v else { return "" }
return String(cString: ptr)
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .srgb }
guard let ptr = v else { return .srgb }
let str = String(cString: ptr)
return WindowColorspace(rawValue: str) ?? .srgb
}
var windowSaveState: String {
@ -474,4 +475,9 @@ extension Ghostty.Config {
}
}
}
enum WindowColorspace : String {
case srgb
case displayP3 = "display-p3"
}
}