mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-20 00:18:53 +03:00
macos: terminal controller reacts to surface config changes
This commit is contained in:
@ -93,21 +93,37 @@ class TerminalController: BaseTerminalController {
|
|||||||
//MARK: - Methods
|
//MARK: - Methods
|
||||||
|
|
||||||
@objc private func ghosttyConfigDidChange(_ notification: Notification) {
|
@objc private func ghosttyConfigDidChange(_ notification: Notification) {
|
||||||
// We only care if the configuration is a global configuration, not a
|
|
||||||
// surface-specific one.
|
|
||||||
guard notification.object == nil else { return }
|
|
||||||
|
|
||||||
// Get our managed configuration object out
|
// Get our managed configuration object out
|
||||||
guard let config = notification.userInfo?[
|
guard let config = notification.userInfo?[
|
||||||
Notification.Name.GhosttyConfigChangeKey
|
Notification.Name.GhosttyConfigChangeKey
|
||||||
] as? Ghostty.Config else { return }
|
] as? Ghostty.Config else { return }
|
||||||
|
|
||||||
|
// If this is an app-level config update then we update some things.
|
||||||
|
if (notification.object == nil) {
|
||||||
// Update our derived config
|
// Update our derived config
|
||||||
self.derivedConfig = DerivedConfig(config)
|
self.derivedConfig = DerivedConfig(config)
|
||||||
|
|
||||||
guard let window = window as? TerminalWindow else { return }
|
guard let window = window as? TerminalWindow else { return }
|
||||||
window.focusFollowsMouse = config.focusFollowsMouse
|
window.focusFollowsMouse = config.focusFollowsMouse
|
||||||
syncAppearance(config)
|
|
||||||
|
// If we have no surfaces in our window (is that possible?) then we update
|
||||||
|
// our window appearance based on the root config. If we have surfaces, we
|
||||||
|
// don't call this because the TODO
|
||||||
|
if surfaceTree == nil {
|
||||||
|
syncAppearance(.init(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a surface-level config update. If we have the surface, we
|
||||||
|
// update our appearance based on it.
|
||||||
|
guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return }
|
||||||
|
guard surfaceTree?.contains(view: surfaceView) ?? false else { return }
|
||||||
|
|
||||||
|
// We can't use surfaceView.derivedConfig because it may not be updated
|
||||||
|
// yet since it also responds to notifications.
|
||||||
|
syncAppearance(.init(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the accessory view of each tab according to the keyboard
|
/// Update the accessory view of each tab according to the keyboard
|
||||||
@ -168,7 +184,7 @@ class TerminalController: BaseTerminalController {
|
|||||||
self.relabelTabs()
|
self.relabelTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncAppearance(_ config: Ghostty.Config) {
|
private func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
|
||||||
guard let window = self.window as? TerminalWindow else { return }
|
guard let window = self.window as? TerminalWindow else { return }
|
||||||
|
|
||||||
// If our window is not visible, then delay this. This is possible specifically
|
// If our window is not visible, then delay this. This is possible specifically
|
||||||
@ -177,19 +193,19 @@ class TerminalController: BaseTerminalController {
|
|||||||
// APIs such as window blur have no effect unless the window is visible.
|
// APIs such as window blur have no effect unless the window is visible.
|
||||||
guard window.isVisible else {
|
guard window.isVisible else {
|
||||||
// Weak window so that if the window changes or is destroyed we aren't holding a ref
|
// 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(config) }
|
DispatchQueue.main.async { [weak self] in self?.syncAppearance(surfaceConfig) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the font for the window and tab titles.
|
// Set the font for the window and tab titles.
|
||||||
if let titleFontName = config.windowTitleFontFamily {
|
if let titleFontName = surfaceConfig.windowTitleFontFamily {
|
||||||
window.titlebarFont = NSFont(name: titleFontName, size: NSFont.systemFontSize)
|
window.titlebarFont = NSFont(name: titleFontName, size: NSFont.systemFontSize)
|
||||||
} else {
|
} else {
|
||||||
window.titlebarFont = nil
|
window.titlebarFont = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have window transparency then set it transparent. Otherwise set it opaque.
|
// If we have window transparency then set it transparent. Otherwise set it opaque.
|
||||||
if (config.backgroundOpacity < 1) {
|
if (surfaceConfig.backgroundOpacity < 1) {
|
||||||
window.isOpaque = false
|
window.isOpaque = false
|
||||||
|
|
||||||
// This is weird, but we don't use ".clear" because this creates a look that
|
// This is weird, but we don't use ".clear" because this creates a look that
|
||||||
@ -203,14 +219,14 @@ class TerminalController: BaseTerminalController {
|
|||||||
window.backgroundColor = .windowBackgroundColor
|
window.backgroundColor = .windowBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
window.hasShadow = config.macosWindowShadow
|
window.hasShadow = surfaceConfig.macosWindowShadow
|
||||||
|
|
||||||
guard window.hasStyledTabs else { return }
|
guard window.hasStyledTabs else { return }
|
||||||
|
|
||||||
// The titlebar is always updated. We don't need to worry about opacity
|
// The titlebar is always updated. We don't need to worry about opacity
|
||||||
// because we handle it here.
|
// because we handle it here.
|
||||||
let backgroundColor = OSColor(config.backgroundColor)
|
let backgroundColor = OSColor(surfaceConfig.backgroundColor)
|
||||||
window.titlebarColor = backgroundColor.withAlphaComponent(config.backgroundOpacity)
|
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
|
||||||
|
|
||||||
if (window.isOpaque) {
|
if (window.isOpaque) {
|
||||||
// Bg color is only synced if we have no transparency. This is because
|
// Bg color is only synced if we have no transparency. This is because
|
||||||
@ -377,8 +393,10 @@ class TerminalController: BaseTerminalController {
|
|||||||
|
|
||||||
window.focusFollowsMouse = config.focusFollowsMouse
|
window.focusFollowsMouse = config.focusFollowsMouse
|
||||||
|
|
||||||
// Apply any additional appearance-related properties to the new window.
|
// Apply any additional appearance-related properties to the new window. We
|
||||||
syncAppearance(config)
|
// apply this based on the root config but change it later based on surface
|
||||||
|
// config (see focused surface change callback).
|
||||||
|
syncAppearance(.init(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shows the "+" button in the tab bar, responds to that click.
|
// Shows the "+" button in the tab bar, responds to that click.
|
||||||
@ -515,6 +533,15 @@ class TerminalController: BaseTerminalController {
|
|||||||
window.surfaceIsZoomed = to
|
window.surfaceIsZoomed = to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
|
||||||
|
super.focusedSurfaceDidChange(to: to)
|
||||||
|
|
||||||
|
// When our focus changes, we update our window appearance based on the
|
||||||
|
// currently focused surface.
|
||||||
|
guard let focusedSurface else { return }
|
||||||
|
syncAppearance(focusedSurface.derivedConfig)
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - Notifications
|
//MARK: - Notifications
|
||||||
|
|
||||||
@objc private func onMoveTab(notification: SwiftUI.Notification) {
|
@objc private func onMoveTab(notification: SwiftUI.Notification) {
|
||||||
|
@ -48,6 +48,9 @@ extension Ghostty {
|
|||||||
// Whether the pointer should be visible or not
|
// Whether the pointer should be visible or not
|
||||||
@Published private(set) var pointerStyle: BackportPointerStyle = .default
|
@Published private(set) var pointerStyle: BackportPointerStyle = .default
|
||||||
|
|
||||||
|
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
||||||
|
@Published private(set) var derivedConfig: DerivedConfig
|
||||||
|
|
||||||
// An initial size to request for a window. This will only affect
|
// An initial size to request for a window. This will only affect
|
||||||
// then the view is moved to a new window.
|
// then the view is moved to a new window.
|
||||||
var initialSize: NSSize? = nil
|
var initialSize: NSSize? = nil
|
||||||
@ -114,6 +117,13 @@ extension Ghostty {
|
|||||||
self.markedText = NSMutableAttributedString()
|
self.markedText = NSMutableAttributedString()
|
||||||
self.uuid = uuid ?? .init()
|
self.uuid = uuid ?? .init()
|
||||||
|
|
||||||
|
// Our initial config always is our application wide config.
|
||||||
|
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
|
||||||
|
self.derivedConfig = DerivedConfig(appDelegate.ghostty.config)
|
||||||
|
} else {
|
||||||
|
self.derivedConfig = DerivedConfig()
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize with some default frame size. The important thing is that this
|
// Initialize with some default frame size. The important thing is that this
|
||||||
// is non-zero so that our layer bounds are non-zero so that our renderer
|
// is non-zero so that our layer bounds are non-zero so that our renderer
|
||||||
// can do SOMETHING.
|
// can do SOMETHING.
|
||||||
@ -137,6 +147,11 @@ extension Ghostty {
|
|||||||
selector: #selector(ghosttyDidEndKeySequence),
|
selector: #selector(ghosttyDidEndKeySequence),
|
||||||
name: Ghostty.Notification.didEndKeySequence,
|
name: Ghostty.Notification.didEndKeySequence,
|
||||||
object: self)
|
object: self)
|
||||||
|
center.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(ghosttyConfigDidChange(_:)),
|
||||||
|
name: .ghosttyConfigDidChange,
|
||||||
|
object: self)
|
||||||
center.addObserver(
|
center.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(windowDidChangeScreen),
|
selector: #selector(windowDidChangeScreen),
|
||||||
@ -333,6 +348,16 @@ extension Ghostty {
|
|||||||
keySequence = []
|
keySequence = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func ghosttyConfigDidChange(_ notification: SwiftUI.Notification) {
|
||||||
|
// Get our managed configuration object out
|
||||||
|
guard let config = notification.userInfo?[
|
||||||
|
SwiftUI.Notification.Name.GhosttyConfigChangeKey
|
||||||
|
] as? Ghostty.Config else { return }
|
||||||
|
|
||||||
|
// Update our derived config
|
||||||
|
self.derivedConfig = DerivedConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func windowDidChangeScreen(notification: SwiftUI.Notification) {
|
@objc private func windowDidChangeScreen(notification: SwiftUI.Notification) {
|
||||||
guard let window = self.window else { return }
|
guard let window = self.window else { return }
|
||||||
guard let object = notification.object as? NSWindow, window == object else { return }
|
guard let object = notification.object as? NSWindow, window == object else { return }
|
||||||
@ -1025,6 +1050,27 @@ extension Ghostty {
|
|||||||
Ghostty.moveFocus(to: self)
|
Ghostty.moveFocus(to: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DerivedConfig {
|
||||||
|
let backgroundColor: Color
|
||||||
|
let backgroundOpacity: Double
|
||||||
|
let macosWindowShadow: Bool
|
||||||
|
let windowTitleFontFamily: String?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
||||||
|
self.backgroundOpacity = 1
|
||||||
|
self.macosWindowShadow = true
|
||||||
|
self.windowTitleFontFamily = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ config: Ghostty.Config) {
|
||||||
|
self.backgroundColor = config.backgroundColor
|
||||||
|
self.backgroundOpacity = config.backgroundOpacity
|
||||||
|
self.macosWindowShadow = config.macosWindowShadow
|
||||||
|
self.windowTitleFontFamily = config.windowTitleFontFamily
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,7 +365,6 @@ const c = @cImport({
|
|||||||
/// be fixed in a future update:
|
/// be fixed in a future update:
|
||||||
///
|
///
|
||||||
/// - macOS: titlebar tabs style is not updated when switching themes.
|
/// - macOS: titlebar tabs style is not updated when switching themes.
|
||||||
/// - macOS: native titlebar style is not supported.
|
|
||||||
///
|
///
|
||||||
theme: ?Theme = null,
|
theme: ?Theme = null,
|
||||||
|
|
||||||
@ -2756,12 +2755,6 @@ pub fn finalize(self: *Config) !void {
|
|||||||
// This setting doesn't make sense with different light/dark themes
|
// This setting doesn't make sense with different light/dark themes
|
||||||
// because it'll force the theme based on the Ghostty theme.
|
// because it'll force the theme based on the Ghostty theme.
|
||||||
if (self.@"window-theme" == .auto) self.@"window-theme" = .system;
|
if (self.@"window-theme" == .auto) self.@"window-theme" = .system;
|
||||||
|
|
||||||
// This is buggy with different light/dark themes and is noted
|
|
||||||
// in the documentation.
|
|
||||||
if (self.@"macos-titlebar-style" == .transparent) {
|
|
||||||
self.@"macos-titlebar-style" = .native;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user