From eba3d5414d863d559b9073e1fbdbf7dd61ac8ab1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 14 Jan 2024 15:52:23 -0800 Subject: [PATCH] macos: Ghostty.Config to store all config-related operations --- macos/Ghostty.xcodeproj/project.pbxproj | 6 + macos/Sources/App/macOS/AppDelegate.swift | 19 +- .../Terminal/TerminalController.swift | 10 +- .../Features/Terminal/TerminalManager.swift | 4 +- .../Terminal/TerminalRestorable.swift | 2 +- macos/Sources/Ghostty/AppState.swift | 170 +------------ macos/Sources/Ghostty/Ghostty.Config.swift | 238 ++++++++++++++++++ macos/Sources/Ghostty/Ghostty.Input.swift | 15 -- macos/Sources/Ghostty/SurfaceView.swift | 32 +-- 9 files changed, 274 insertions(+), 222 deletions(-) create mode 100644 macos/Sources/Ghostty/Ghostty.Config.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 43f3af015..88c49ce83 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; }; 8503D7C72A549C66006CFF3D /* FullScreenHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */; }; 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; }; + A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; }; + A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; }; A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */; }; A51BFC1E2B2FB5CE00E92F16 /* About.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51BFC1D2B2FB5CE00E92F16 /* About.xib */; }; A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */; }; @@ -65,6 +67,7 @@ 552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = ""; }; 8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenHandler.swift; sourceTree = ""; }; 857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; + A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = ""; }; A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = ""; }; A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = ""; }; A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = ""; }; @@ -237,6 +240,7 @@ A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */, A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */, A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */, + A514C8D52B54A16400493A16 /* Ghostty.Config.swift */, A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */, A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */, A59630A32AF059BB00D64628 /* Ghostty.SplitNode.swift */, @@ -443,6 +447,7 @@ buildActionMask = 2147483647; files = ( A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */, + A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */, A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */, A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */, A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */, @@ -483,6 +488,7 @@ buildActionMask = 2147483647; files = ( A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */, + A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */, A53D0C9C2B543F7B00305CE6 /* Package.swift in Sources */, A53D0C9B2B543F3B00305CE6 /* Ghostty.App.swift in Sources */, ); diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 4415dbf3f..50f7f5599 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -143,7 +143,7 @@ class AppDelegate: NSObject, } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return ghostty.shouldQuitAfterLastWindowClosed + return ghostty.config.shouldQuitAfterLastWindowClosed } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -242,7 +242,7 @@ class AppDelegate: NSObject, /// Sync all of our menu item keyboard shortcuts with the Ghostty configuration. private func syncMenuShortcuts() { - guard ghostty.config != nil else { return } + guard ghostty.readiness == .ready else { return } syncMenuShortcut(action: "open_config", menuItem: self.menuOpenConfig) syncMenuShortcut(action: "reload_config", menuItem: self.menuReloadConfig) @@ -286,19 +286,16 @@ class AppDelegate: NSObject, /// Syncs a single menu shortcut for the given action. The action string is the same /// action string used for the Ghostty configuration. private func syncMenuShortcut(action: String, menuItem: NSMenuItem?) { - guard let cfg = ghostty.config else { return } guard let menu = menuItem else { return } - - let trigger = ghostty_config_trigger(cfg, action, UInt(action.count)) - guard let equiv = Ghostty.keyEquivalent(key: trigger.key) else { + guard let equiv = ghostty.config.keyEquivalent(for: action) else { // No shortcut, clear the menu item menu.keyEquivalent = "" menu.keyEquivalentModifierMask = [] return } - menu.keyEquivalent = equiv - menu.keyEquivalentModifierMask = Ghostty.eventModifierFlags(mods: trigger.mods) + menu.keyEquivalent = equiv.key + menu.keyEquivalentModifierMask = equiv.modifiers } private func focusedSurface() -> ghostty_surface_t? { @@ -357,7 +354,7 @@ class AppDelegate: NSObject, // Depending on the "window-save-state" setting we have to set the NSQuitAlwaysKeepsWindows // configuration. This is the only way to carefully control whether macOS invokes the // state restoration system. - switch (ghostty.windowSaveState) { + switch (ghostty.config.windowSaveState) { case "never": UserDefaults.standard.setValue(false, forKey: "NSQuitAlwaysKeepsWindows") case "always": UserDefaults.standard.setValue(true, forKey: "NSQuitAlwaysKeepsWindows") case "default": fallthrough @@ -373,7 +370,7 @@ class AppDelegate: NSObject, // If we have configuration errors, we need to show them. let c = ConfigurationErrorsController.sharedInstance - c.errors = state.configErrors() + c.errors = state.config.errors if (c.errors.count > 0) { if (c.window == nil || !c.window!.isVisible) { c.showWindow(self) @@ -383,7 +380,7 @@ class AppDelegate: NSObject, /// Sync the appearance of our app with the theme specified in the config. private func syncAppearance() { - guard let theme = ghostty.windowTheme else { return } + guard let theme = ghostty.config.windowTheme else { return } switch (theme) { case "dark": let appearance = NSAppearance(named: .darkAqua) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index b49e502e8..4db4d715b 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -101,7 +101,6 @@ class TerminalController: NSWindowController, NSWindowDelegate, tabListenForFrame = false guard let windows = self.window?.tabbedWindows else { return } - guard let cfg = ghostty.config else { return } // We only listen for frame changes if we have more than 1 window, // otherwise the accessory view doesn't matter. @@ -109,8 +108,7 @@ class TerminalController: NSWindowController, NSWindowDelegate, for (index, window) in windows.enumerated().prefix(9) { let action = "goto_tab:\(index + 1)" - let trigger = ghostty_config_trigger(cfg, action, UInt(action.count)) - guard let equiv = Ghostty.keyEquivalentLabel(key: trigger.key, mods: trigger.mods) else { + guard let equiv = ghostty.config.keyEquivalent(for: action) else { continue } @@ -157,13 +155,13 @@ class TerminalController: NSWindowController, NSWindowDelegate, window.identifier = .init(String(describing: TerminalWindowRestoration.self)) // If window decorations are disabled, remove our title - if (!ghostty.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 thie GitHub issue: https://github.com/mitchellh/ghostty/pull/376 // Ghostty defaults to sRGB but this can be overridden. - switch (ghostty.windowColorspace) { + switch (ghostty.config.windowColorspace) { case "display-p3": window.colorSpace = .displayP3 case "srgb": @@ -462,7 +460,7 @@ class TerminalController: NSWindowController, NSWindowDelegate, } func cellSizeDidChange(to: NSSize) { - guard ghostty.windowStepResize else { return } + guard ghostty.config.windowStepResize else { return } self.window?.contentResizeIncrements = to } diff --git a/macos/Sources/Features/Terminal/TerminalManager.swift b/macos/Sources/Features/Terminal/TerminalManager.swift index b919d5282..361ee2feb 100644 --- a/macos/Sources/Features/Terminal/TerminalManager.swift +++ b/macos/Sources/Features/Terminal/TerminalManager.swift @@ -66,7 +66,7 @@ class TerminalManager { let window = c.window! // We want to go fullscreen if we're configured for new windows to go fullscreen - var toggleFullScreen = ghostty.windowFullscreen + var toggleFullScreen = ghostty.config.windowFullscreen // If the previous focused window prior to creating this window is fullscreen, // then this window also becomes fullscreen. @@ -130,7 +130,7 @@ class TerminalManager { controller.showWindow(self) // Add the window to the tab group and show it. - switch ghostty.windowNewTabPosition { + switch ghostty.config.windowNewTabPosition { case "end": // If we already have a tab group and we want the new tab to open at the end, // then we use the last window in the tab group as the parent. diff --git a/macos/Sources/Features/Terminal/TerminalRestorable.swift b/macos/Sources/Features/Terminal/TerminalRestorable.swift index 7b70220b4..b808e5701 100644 --- a/macos/Sources/Features/Terminal/TerminalRestorable.swift +++ b/macos/Sources/Features/Terminal/TerminalRestorable.swift @@ -66,7 +66,7 @@ class TerminalWindowRestoration: NSObject, NSWindowRestoration { // If our configuration is "never" then we never restore the state // no matter what. - if (appDelegate.terminalManager.ghostty.windowSaveState == "never") { + if (appDelegate.terminalManager.ghostty.config.windowSaveState == "never") { completionHandler(nil, nil) return } diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index e9c15ee95..c59f39795 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -36,16 +36,10 @@ extension Ghostty { /// Optional delegate weak var delegate: GhosttyAppStateDelegate? - /// The ghostty global configuration. This should only be changed when it is definitely - /// safe to change. It is definite safe to change only when the embedded app runtime - /// in Ghostty says so (usually, only in a reload configuration callback). - @Published var config: ghostty_config_t? = nil { - didSet { - // Free the old value whenever we change - guard let old = oldValue else { return } - ghostty_config_free(old) - } - } + /// The global app configuration. This defines the app level configuration plus any behavior + /// for new windows, tabs, etc. Note that when creating a new window, it may inherit some + /// configuration (i.e. font size) from the previously focused window. This would override this. + private(set) var config: Config /// The ghostty app instance. We only have one of these for the entire app, although I guess /// in theory you can have multiple... I don't know why you would... @@ -55,45 +49,6 @@ extension Ghostty { ghostty_app_free(old) } } - - /// True if we should quit when the last window is closed. - var shouldQuitAfterLastWindowClosed: Bool { - guard let config = self.config else { return true } - var v = false; - let key = "quit-after-last-window-closed" - _ = ghostty_config_get(config, &v, key, UInt(key.count)) - return v - } - - /// window-colorspace - var windowColorspace: String { - guard let config = self.config else { return "" } - var v: UnsafePointer? = 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) - } - - /// window-save-state - var windowSaveState: String { - guard let config = self.config else { return "" } - var v: UnsafePointer? = nil - let key = "window-save-state" - guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" } - guard let ptr = v else { return "" } - return String(cString: ptr) - } - - /// window-new-tab-position - var windowNewTabPosition: String { - guard let config = self.config else { return "" } - var v: UnsafePointer? = nil - let key = "window-new-tab-position" - guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" } - guard let ptr = v else { return "" } - return String(cString: ptr) - } /// True if we need to confirm before quitting. var needsConfirmQuit: Bool { @@ -113,66 +68,19 @@ extension Ghostty { return Info(mode: raw.build_mode, version: String(version)) } - /// True if we want to render window decorations - var windowDecorations: Bool { - guard let config = self.config else { return true } - var v = false; - let key = "window-decoration" - _ = ghostty_config_get(config, &v, key, UInt(key.count)) - return v; - } - - /// The window theme as a string. - var windowTheme: String? { - guard let config = self.config else { return nil } - var v: UnsafePointer? = nil - let key = "window-theme" - guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil } - guard let ptr = v else { return nil } - return String(cString: ptr) - } - - /// Whether to resize windows in discrete steps or use "fluid" resizing - var windowStepResize: Bool { - guard let config = self.config else { return true } - var v = false - let key = "window-step-resize" - _ = ghostty_config_get(config, &v, key, UInt(key.count)) - return v - } - - /// Whether to open new windows in fullscreen. - var windowFullscreen: Bool { - guard let config = self.config else { return true } - var v = false - let key = "fullscreen" - _ = ghostty_config_get(config, &v, key, UInt(key.count)) - return v - } - - /// The background opacity. - var backgroundOpacity: Double { - guard let config = self.config else { return 1 } - var v: Double = 1 - let key = "background-opacity" - _ = ghostty_config_get(config, &v, key, UInt(key.count)) - return v; - } - init() { // Initialize ghostty global state. This happens once per process. - guard ghostty_init() == GHOSTTY_SUCCESS else { - AppDelegate.logger.critical("ghostty_init failed") + if ghostty_init() != GHOSTTY_SUCCESS { + AppDelegate.logger.critical("ghostty_init failed, weird things may happen") readiness = .error - return } // Initialize the global configuration. - guard let cfg = Self.loadConfig() else { + self.config = Config() + if self.config.config == nil { readiness = .error return } - self.config = cfg; // Create our "runtime" config. The "runtime" is the configuration that ghostty // uses to interface with the application runtime environment. @@ -210,7 +118,7 @@ extension Ghostty { ) // Create the ghostty app. - guard let app = ghostty_app_new(&runtime_cfg, cfg) else { + guard let app = ghostty_app_new(&runtime_cfg, config.config) else { AppDelegate.logger.critical("ghostty_app_new failed") readiness = .error return @@ -230,7 +138,6 @@ extension Ghostty { deinit { // This will force the didSet callbacks to run which free. self.app = nil - self.config = nil // Remove our observer NotificationCenter.default.removeObserver( @@ -239,58 +146,6 @@ extension Ghostty { object: nil) } - /// Initializes a new configuration and loads all the values. - static func loadConfig() -> ghostty_config_t? { - // Initialize the global configuration. - guard let cfg = ghostty_config_new() else { - AppDelegate.logger.critical("ghostty_config_new failed") - return nil - } - - // Load our configuration files from the home directory. - ghostty_config_load_default_files(cfg); - ghostty_config_load_cli_args(cfg); - ghostty_config_load_recursive_files(cfg); - - // TODO: we'd probably do some config loading here... for now we'd - // have to do this synchronously. When we support config updating we can do - // this async and update later. - - // Finalize will make our defaults available. - ghostty_config_finalize(cfg) - - // Log any configuration errors. These will be automatically shown in a - // pop-up window too. - let errCount = ghostty_config_errors_count(cfg) - if errCount > 0 { - AppDelegate.logger.warning("config error: \(errCount) configuration errors on reload") - var errors: [String] = []; - for i in 0.. [String] { - guard let cfg = self.config else { return [] } - - var errors: [String] = []; - let errCount = ghostty_config_errors_count(cfg) - for i in 0.. ghostty_config_t? { - guard let newConfig = Self.loadConfig() else { + let newConfig = Config() + guard newConfig.loaded else { AppDelegate.logger.warning("failed to reload configuration") return nil } @@ -549,7 +405,7 @@ extension Ghostty { delegate.configDidReload(state) } - return newConfig + return newConfig.config } static func wakeup(_ userdata: UnsafeMutableRawPointer?) { @@ -662,7 +518,7 @@ extension Ghostty { let surface = self.surfaceUserdata(from: userdata) guard let appState = self.appState(fromView: surface) else { return } - guard appState.windowDecorations else { + guard appState.config.windowDecorations else { let alert = NSAlert() alert.messageText = "Tabs are disabled" alert.informativeText = "Enable window decorations to use tabs" diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift new file mode 100644 index 000000000..5cf05694b --- /dev/null +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -0,0 +1,238 @@ +import SwiftUI +import GhosttyKit + +extension Ghostty { + /// Maps to a `ghostty_config_t` and the various operations on that. + class Config: ObservableObject { + // The underlying C pointer to the Ghostty config structure. This + // should never be accessed directly. Any operations on this should + // be called from the functions on this or another class. + private(set) var config: ghostty_config_t? = nil { + didSet { + // Free the old value whenever we change + guard let old = oldValue else { return } + ghostty_config_free(old) + } + } + + /// True if the configuration is loaded + var loaded: Bool { config != nil } + + /// Return the errors found while loading the configuration. + var errors: [String] { + guard let cfg = self.config else { return [] } + + var errors: [String] = []; + let errCount = ghostty_config_errors_count(cfg) + for i in 0.. ghostty_config_t? { + // Initialize the global configuration. + guard let cfg = ghostty_config_new() else { + logger.critical("ghostty_config_new failed") + return nil + } + + // Load our configuration from files, CLI args, and then any referenced files. + // We only do this on macOS because other Apple platforms do not have the + // same filesystem concept. +#if os(macOS) + ghostty_config_load_default_files(cfg); + ghostty_config_load_cli_args(cfg); + ghostty_config_load_recursive_files(cfg); +#endif + + // TODO: we'd probably do some config loading here... for now we'd + // have to do this synchronously. When we support config updating we can do + // this async and update later. + + // Finalize will make our defaults available. + ghostty_config_finalize(cfg) + + // Log any configuration errors. These will be automatically shown in a + // pop-up window too. + let errCount = ghostty_config_errors_count(cfg) + if errCount > 0 { + logger.warning("config error: \(errCount) configuration errors on reload") + var errors: [String] = []; + for i in 0.. KeyEquivalent? { + guard let cfg = self.config else { return nil } + + let trigger = ghostty_config_trigger(cfg, action, UInt(action.count)) + guard let equiv = Ghostty.keyEquivalent(key: trigger.key) else { return nil } + + return KeyEquivalent( + key: equiv, + modifiers: Ghostty.eventModifierFlags(mods: trigger.mods) + ) + } +#endif + + // MARK: - Configuration Values + + /// For all of the configuration values below, see the associated Ghostty documentation for + /// details on what each means. We only add documentation if there is a strange conversion + /// due to the embedded library and Swift. + + var shouldQuitAfterLastWindowClosed: Bool { + guard let config = self.config else { return true } + var v = false; + let key = "quit-after-last-window-closed" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v + } + + var windowColorspace: String { + guard let config = self.config else { return "" } + var v: UnsafePointer? = 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) + } + + var windowSaveState: String { + guard let config = self.config else { return "" } + var v: UnsafePointer? = nil + let key = "window-save-state" + guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" } + guard let ptr = v else { return "" } + return String(cString: ptr) + } + + var windowNewTabPosition: String { + guard let config = self.config else { return "" } + var v: UnsafePointer? = nil + let key = "window-new-tab-position" + guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" } + guard let ptr = v else { return "" } + return String(cString: ptr) + } + + var windowDecorations: Bool { + guard let config = self.config else { return true } + var v = false; + let key = "window-decoration" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v; + } + + var windowTheme: String? { + guard let config = self.config else { return nil } + var v: UnsafePointer? = nil + let key = "window-theme" + guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil } + guard let ptr = v else { return nil } + return String(cString: ptr) + } + + var windowStepResize: Bool { + guard let config = self.config else { return true } + var v = false + let key = "window-step-resize" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v + } + + var windowFullscreen: Bool { + guard let config = self.config else { return true } + var v = false + let key = "fullscreen" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v + } + + var backgroundOpacity: Double { + guard let config = self.config else { return 1 } + var v: Double = 1 + let key = "background-opacity" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v; + } + + var unfocusedSplitOpacity: Double { + guard let config = self.config else { return 1 } + var opacity: Double = 0.85 + let key = "unfocused-split-opacity" + _ = ghostty_config_get(config, &opacity, key, UInt(key.count)) + return 1 - opacity + } + + var unfocusedSplitFill: Color { + guard let config = self.config else { return .white } + + var rgb: UInt32 = 16777215 // white default + let key = "unfocused-split-fill" + if (!ghostty_config_get(config, &rgb, key, UInt(key.count))) { + let bg_key = "background" + _ = ghostty_config_get(config, &rgb, bg_key, UInt(bg_key.count)); + } + + let red = Double(rgb & 0xff) + let green = Double((rgb >> 8) & 0xff) + let blue = Double((rgb >> 16) & 0xff) + + return Color( + red: red / 255, + green: green / 255, + blue: blue / 255 + ) + } + } +} diff --git a/macos/Sources/Ghostty/Ghostty.Input.swift b/macos/Sources/Ghostty/Ghostty.Input.swift index dd71d2ed2..182e0dad1 100644 --- a/macos/Sources/Ghostty/Ghostty.Input.swift +++ b/macos/Sources/Ghostty/Ghostty.Input.swift @@ -7,21 +7,6 @@ extension Ghostty { return Self.keyToEquivalent[key] } - /// Returns the keyEquivalent label that includes the mods. - static func keyEquivalentLabel(key: ghostty_input_key_e, mods: ghostty_input_mods_e) -> String? { - guard var key = Self.keyEquivalent(key: key) else { return nil } - let flags = Self.eventModifierFlags(mods: mods) - - // Note: the order below matters; it matches the ordering modifiers show for - // macOS menu shortcut labels. - if flags.contains(.command) { key = "⌘\(key)" } - if flags.contains(.shift) { key = "⇧\(key)" } - if flags.contains(.option) { key = "⌥\(key)" } - if flags.contains(.control) { key = "⌃\(key)" } - - return key - } - /// Returns the event modifier flags set for the Ghostty mods enum. static func eventModifierFlags(mods: ghostty_input_mods_e) -> NSEvent.ModifierFlags { var flags = NSEvent.ModifierFlags(rawValue: 0); diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 473a3a884..f9bc0f027 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -55,34 +55,6 @@ extension Ghostty { // it is both individually focused and the containing window is key. private var hasFocus: Bool { surfaceFocus && windowFocus } - // The opacity of the rectangle when unfocused. - private var unfocusedOpacity: Double { - var opacity: Double = 0.85 - let key = "unfocused-split-opacity" - _ = ghostty_config_get(ghostty.config, &opacity, key, UInt(key.count)) - return 1 - opacity - } - - // The color for the rectangle overlay when unfocused. - private var unfocusedFill: Color { - var rgb: UInt32 = 16777215 // white default - let key = "unfocused-split-fill" - if (!ghostty_config_get(ghostty.config, &rgb, key, UInt(key.count))) { - let bg_key = "background" - _ = ghostty_config_get(ghostty.config, &rgb, bg_key, UInt(bg_key.count)); - } - - let red = Double(rgb & 0xff) - let green = Double((rgb >> 8) & 0xff) - let blue = Double((rgb >> 16) & 0xff) - - return Color( - red: red / 255, - green: green / 255, - blue: blue / 255 - ) - } - var body: some View { ZStack { // We use a GeometryReader to get the frame bounds so that our metal surface @@ -175,10 +147,10 @@ extension Ghostty { // because we want to keep our focused surface dark even if we don't have window // focus. if (isSplit && !surfaceFocus) { - let overlayOpacity = unfocusedOpacity; + let overlayOpacity = ghostty.config.unfocusedSplitOpacity; if (overlayOpacity > 0) { Rectangle() - .fill(unfocusedFill) + .fill(ghostty.config.unfocusedSplitFill) .allowsHitTesting(false) .opacity(overlayOpacity) }