From 901618cd8f944be82b47fad7efb577507d9802a7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 20 Dec 2025 19:58:24 -0800 Subject: [PATCH] macOS: hook up key table apprt action to state --- macos/Sources/Ghostty/Ghostty.Action.swift | 25 +++++++++++++++ macos/Sources/Ghostty/Ghostty.App.swift | 32 +++++++++++++++++-- macos/Sources/Ghostty/Package.swift | 4 +++ .../Sources/Ghostty/SurfaceView_AppKit.swift | 24 ++++++++++++++ macos/Sources/Ghostty/SurfaceView_UIKit.swift | 5 ++- 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/macos/Sources/Ghostty/Ghostty.Action.swift b/macos/Sources/Ghostty/Ghostty.Action.swift index 9eb7a8e46..bde3b3d69 100644 --- a/macos/Sources/Ghostty/Ghostty.Action.swift +++ b/macos/Sources/Ghostty/Ghostty.Action.swift @@ -141,6 +141,31 @@ extension Ghostty.Action { } } } + + enum KeyTable { + case activate(name: String) + case deactivate + case deactivateAll + + init?(c: ghostty_action_key_table_s) { + switch c.tag { + case GHOSTTY_KEY_TABLE_ACTIVATE: + let name = String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: c.value.activate.name), + length: c.value.activate.len, + encoding: .utf8, + freeWhenDone: false + ) ?? "" + self = .activate(name: name) + case GHOSTTY_KEY_TABLE_DEACTIVATE: + self = .deactivate + case GHOSTTY_KEY_TABLE_DEACTIVATE_ALL: + self = .deactivateAll + default: + return nil + } + } + } } // Putting the initializer in an extension preserves the automatic one. diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 3348ab714..4e9166168 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -578,7 +578,10 @@ extension Ghostty { case GHOSTTY_ACTION_KEY_SEQUENCE: keySequence(app, target: target, v: action.action.key_sequence) - + + case GHOSTTY_ACTION_KEY_TABLE: + keyTable(app, target: target, v: action.action.key_table) + case GHOSTTY_ACTION_PROGRESS_REPORT: progressReport(app, target: target, v: action.action.progress_report) @@ -1771,7 +1774,32 @@ extension Ghostty { assertionFailure() } } - + + private static func keyTable( + _ app: ghostty_app_t, + target: ghostty_target_s, + v: ghostty_action_key_table_s) { + switch (target.tag) { + case GHOSTTY_TARGET_APP: + Ghostty.logger.warning("key table 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 } + guard let action = Ghostty.Action.KeyTable(c: v) else { return } + + NotificationCenter.default.post( + name: Notification.didChangeKeyTable, + object: surfaceView, + userInfo: [Notification.KeyTableKey: action] + ) + + default: + assertionFailure() + } + } + private static func progressReport( _ app: ghostty_app_t, target: ghostty_target_s, diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 375e5c37b..aa62c16f7 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -475,6 +475,10 @@ extension Ghostty.Notification { static let didContinueKeySequence = Notification.Name("com.mitchellh.ghostty.didContinueKeySequence") static let didEndKeySequence = Notification.Name("com.mitchellh.ghostty.didEndKeySequence") static let KeySequenceKey = didContinueKeySequence.rawValue + ".key" + + /// Notifications related to key tables + static let didChangeKeyTable = Notification.Name("com.mitchellh.ghostty.didChangeKeyTable") + static let KeyTableKey = didChangeKeyTable.rawValue + ".action" } // Make the input enum hashable. diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 88a0bb6e8..455249ff4 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -65,6 +65,9 @@ extension Ghostty { // The currently active key sequence. The sequence is not active if this is empty. @Published var keySequence: [KeyboardShortcut] = [] + // The currently active key tables. Empty if no tables are active. + @Published var keyTables: [String] = [] + // The current search state. When non-nil, the search overlay should be shown. @Published var searchState: SearchState? = nil { didSet { @@ -324,6 +327,11 @@ extension Ghostty { selector: #selector(ghosttyDidEndKeySequence), name: Ghostty.Notification.didEndKeySequence, object: self) + center.addObserver( + self, + selector: #selector(ghosttyDidChangeKeyTable), + name: Ghostty.Notification.didChangeKeyTable, + object: self) center.addObserver( self, selector: #selector(ghosttyConfigDidChange(_:)), @@ -680,6 +688,22 @@ extension Ghostty { } } + @objc private func ghosttyDidChangeKeyTable(notification: SwiftUI.Notification) { + guard let action = notification.userInfo?[Ghostty.Notification.KeyTableKey] as? Ghostty.Action.KeyTable else { return } + + DispatchQueue.main.async { [weak self] in + guard let self else { return } + switch action { + case .activate(let name): + self.keyTables.append(name) + case .deactivate: + _ = self.keyTables.popLast() + case .deactivateAll: + self.keyTables.removeAll() + } + } + } + @objc private func ghosttyConfigDidChange(_ notification: SwiftUI.Notification) { // Get our managed configuration object out guard let config = notification.userInfo?[ diff --git a/macos/Sources/Ghostty/SurfaceView_UIKit.swift b/macos/Sources/Ghostty/SurfaceView_UIKit.swift index b2e429455..eb8a60fd9 100644 --- a/macos/Sources/Ghostty/SurfaceView_UIKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_UIKit.swift @@ -43,7 +43,10 @@ extension Ghostty { // The current search state. When non-nil, the search overlay should be shown. @Published var searchState: SearchState? = nil - + + // The currently active key tables. Empty if no tables are active. + @Published var keyTables: [String] = [] + /// True when the surface is in readonly mode. @Published private(set) var readonly: Bool = false