mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: listen for color change property to update window appearance
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
/// A classic, tabbed terminal experience.
|
/// A classic, tabbed terminal experience.
|
||||||
@ -23,6 +24,9 @@ class TerminalController: BaseTerminalController {
|
|||||||
/// 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
|
||||||
|
|
||||||
|
/// The notification cancellable for focused surface property changes.
|
||||||
|
private var surfaceAppearanceCancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
init(_ ghostty: Ghostty.App,
|
init(_ ghostty: Ghostty.App,
|
||||||
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||||
withSurfaceTree tree: Ghostty.SplitNode? = nil
|
withSurfaceTree tree: Ghostty.SplitNode? = nil
|
||||||
@ -225,7 +229,7 @@ class TerminalController: BaseTerminalController {
|
|||||||
|
|
||||||
// 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(surfaceConfig.backgroundColor)
|
let backgroundColor = OSColor(focusedSurface?.backgroundColor ?? surfaceConfig.backgroundColor)
|
||||||
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
|
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
|
||||||
|
|
||||||
if (window.isOpaque) {
|
if (window.isOpaque) {
|
||||||
@ -536,10 +540,31 @@ class TerminalController: BaseTerminalController {
|
|||||||
override func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
|
override func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
|
||||||
super.focusedSurfaceDidChange(to: to)
|
super.focusedSurfaceDidChange(to: to)
|
||||||
|
|
||||||
|
// We always cancel our event listener
|
||||||
|
surfaceAppearanceCancellables.removeAll()
|
||||||
|
|
||||||
// When our focus changes, we update our window appearance based on the
|
// When our focus changes, we update our window appearance based on the
|
||||||
// currently focused surface.
|
// currently focused surface.
|
||||||
guard let focusedSurface else { return }
|
guard let focusedSurface else { return }
|
||||||
syncAppearance(focusedSurface.derivedConfig)
|
syncAppearance(focusedSurface.derivedConfig)
|
||||||
|
|
||||||
|
// We also want to get notified of certain changes to update our appearance.
|
||||||
|
focusedSurface.$derivedConfig
|
||||||
|
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
|
||||||
|
.store(in: &surfaceAppearanceCancellables)
|
||||||
|
focusedSurface.$backgroundColor
|
||||||
|
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
|
||||||
|
.store(in: &surfaceAppearanceCancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func syncAppearanceOnPropertyChange(_ surface: Ghostty.SurfaceView?) {
|
||||||
|
guard let surface else { return }
|
||||||
|
DispatchQueue.main.async { [weak self, weak surface] in
|
||||||
|
guard let surface else { return }
|
||||||
|
guard let self else { return }
|
||||||
|
guard self.focusedSurface == surface else { return }
|
||||||
|
self.syncAppearance(surface.derivedConfig)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - Notifications
|
//MARK: - Notifications
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import SwiftUI
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
@ -5,6 +6,33 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Ghostty.Action {
|
extension Ghostty.Action {
|
||||||
|
struct ColorChange {
|
||||||
|
let kind: Kind
|
||||||
|
let color: Color
|
||||||
|
|
||||||
|
enum Kind {
|
||||||
|
case foreground
|
||||||
|
case background
|
||||||
|
case cursor
|
||||||
|
case palette(index: UInt8)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(c: ghostty_action_color_change_s) {
|
||||||
|
switch (c.kind) {
|
||||||
|
case GHOSTTY_ACTION_COLOR_KIND_FOREGROUND:
|
||||||
|
self.kind = .foreground
|
||||||
|
case GHOSTTY_ACTION_COLOR_KIND_BACKGROUND:
|
||||||
|
self.kind = .background
|
||||||
|
case GHOSTTY_ACTION_COLOR_KIND_CURSOR:
|
||||||
|
self.kind = .cursor
|
||||||
|
default:
|
||||||
|
self.kind = .palette(index: UInt8(c.kind.rawValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.color = Color(red: Double(c.r) / 255, green: Double(c.g) / 255, blue: Double(c.b) / 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct MoveTab {
|
struct MoveTab {
|
||||||
let amount: Int
|
let amount: Int
|
||||||
|
|
||||||
|
@ -515,7 +515,8 @@ extension Ghostty {
|
|||||||
configChange(app, target: target, v: action.action.config_change)
|
configChange(app, target: target, v: action.action.config_change)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_COLOR_CHANGE:
|
case GHOSTTY_ACTION_COLOR_CHANGE:
|
||||||
fallthrough
|
colorChange(app, target: target, change: action.action.color_change)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
||||||
fallthrough
|
fallthrough
|
||||||
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
|
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
|
||||||
@ -1188,6 +1189,32 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func colorChange(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
change: ghostty_action_color_change_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("color change does nothing with an app target")
|
||||||
|
return
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .ghosttyColorDidChange,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
SwiftUI.Notification.Name.GhosttyColorChangeKey: Action.ColorChange(c: change)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: User Notifications
|
// MARK: User Notifications
|
||||||
|
|
||||||
/// Handle a received user notification. This is called when a user notification is clicked or dismissed by the user
|
/// Handle a received user notification. This is called when a user notification is clicked or dismissed by the user
|
||||||
|
@ -210,6 +210,10 @@ extension Notification.Name {
|
|||||||
static let ghosttyConfigDidChange = Notification.Name("com.mitchellh.ghostty.configDidChange")
|
static let ghosttyConfigDidChange = Notification.Name("com.mitchellh.ghostty.configDidChange")
|
||||||
static let GhosttyConfigChangeKey = ghosttyConfigDidChange.rawValue
|
static let GhosttyConfigChangeKey = ghosttyConfigDidChange.rawValue
|
||||||
|
|
||||||
|
/// Color change. Object is the surface changing.
|
||||||
|
static let ghosttyColorDidChange = Notification.Name("com.mitchellh.ghostty.ghosttyColorDidChange")
|
||||||
|
static let GhosttyColorChangeKey = ghosttyColorDidChange.rawValue
|
||||||
|
|
||||||
/// Goto tab. Has tab index in the userinfo.
|
/// Goto tab. Has tab index in the userinfo.
|
||||||
static let ghosttyMoveTab = Notification.Name("com.mitchellh.ghostty.moveTab")
|
static let ghosttyMoveTab = Notification.Name("com.mitchellh.ghostty.moveTab")
|
||||||
static let GhosttyMoveTabKey = ghosttyMoveTab.rawValue
|
static let GhosttyMoveTabKey = ghosttyMoveTab.rawValue
|
||||||
|
@ -51,6 +51,10 @@ extension Ghostty {
|
|||||||
/// 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.
|
||||||
@Published private(set) var derivedConfig: DerivedConfig
|
@Published private(set) var derivedConfig: DerivedConfig
|
||||||
|
|
||||||
|
/// The background color within the color palette of the surface. This is only set if it is
|
||||||
|
/// dynamically updated. Otherwise, the background color is the default background color.
|
||||||
|
@Published private(set) var backgroundColor: Color? = nil
|
||||||
|
|
||||||
// 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
|
||||||
@ -152,6 +156,11 @@ extension Ghostty {
|
|||||||
selector: #selector(ghosttyConfigDidChange(_:)),
|
selector: #selector(ghosttyConfigDidChange(_:)),
|
||||||
name: .ghosttyConfigDidChange,
|
name: .ghosttyConfigDidChange,
|
||||||
object: self)
|
object: self)
|
||||||
|
center.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(ghosttyColorDidChange(_:)),
|
||||||
|
name: .ghosttyColorDidChange,
|
||||||
|
object: self)
|
||||||
center.addObserver(
|
center.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(windowDidChangeScreen),
|
selector: #selector(windowDidChangeScreen),
|
||||||
@ -358,6 +367,21 @@ extension Ghostty {
|
|||||||
self.derivedConfig = DerivedConfig(config)
|
self.derivedConfig = DerivedConfig(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func ghosttyColorDidChange(_ notification: SwiftUI.Notification) {
|
||||||
|
guard let change = notification.userInfo?[
|
||||||
|
SwiftUI.Notification.Name.GhosttyColorChangeKey
|
||||||
|
] as? Ghostty.Action.ColorChange else { return }
|
||||||
|
|
||||||
|
switch (change.kind) {
|
||||||
|
case .background:
|
||||||
|
self.backgroundColor = change.color
|
||||||
|
|
||||||
|
default:
|
||||||
|
// We don't do anything for the other colors yet.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@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 }
|
||||||
|
Reference in New Issue
Block a user