diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 495dd8232..943120413 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -1985,19 +1985,21 @@ extension Ghostty { case GHOSTTY_TARGET_SURFACE: guard let surface = target.target.surface else { return } guard let surfaceView = self.surfaceView(from: surface) else { return } - if v.active { - NotificationCenter.default.post( - name: Notification.didContinueKeySequence, - object: surfaceView, - userInfo: [ - Notification.KeySequenceKey: keyboardShortcut(for: v.trigger) as Any - ] - ) - } else { - NotificationCenter.default.post( - name: Notification.didEndKeySequence, - object: surfaceView - ) + DispatchQueue.main.async { + if v.active { + NotificationCenter.default.post( + name: Notification.didContinueKeySequence, + object: surfaceView, + userInfo: [ + Notification.KeySequenceKey: keyboardShortcut(for: v.trigger) as Any + ] + ) + } else { + NotificationCenter.default.post( + name: Notification.didEndKeySequence, + object: surfaceView + ) + } } default: diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 10be99c76..8881e09ca 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -111,7 +111,7 @@ extension Ghostty { /// configuration would be "quit" action. /// /// Returns nil if there is no key equivalent for the given action. - func keyboardShortcut(for action: String) -> KeyboardShortcut? { + @MainActor func keyboardShortcut(for action: String) -> KeyboardShortcut? { guard let trigger = keybindTrigger(for: action) else { return nil } return Ghostty.keyboardShortcut(for: trigger) } diff --git a/macos/Sources/Ghostty/Ghostty.Input.swift b/macos/Sources/Ghostty/Ghostty.Input.swift index 3791e52a7..570043832 100644 --- a/macos/Sources/Ghostty/Ghostty.Input.swift +++ b/macos/Sources/Ghostty/Ghostty.Input.swift @@ -16,7 +16,7 @@ extension Ghostty { /// (F1, F2, ...) with a KeyboardShortcut. This doesn't represent a practical issue because input /// handling for Ghostty is handled at a lower level (usually). This function should generally only /// be used for things like NSMenu that only support keyboard shortcuts anyways. - static func keyboardShortcut(for trigger: ghostty_input_trigger_s) -> KeyboardShortcut? { + @MainActor static func keyboardShortcut(for trigger: ghostty_input_trigger_s) -> KeyboardShortcut? { let modifierFlags = Self.eventModifierFlags(mods: trigger.mods) let key: KeyEquivalent switch trigger.tag { diff --git a/macos/Sources/Helpers/KeyboardLayout.swift b/macos/Sources/Helpers/KeyboardLayout.swift index e6c53d2a1..30bcfab0e 100644 --- a/macos/Sources/Helpers/KeyboardLayout.swift +++ b/macos/Sources/Helpers/KeyboardLayout.swift @@ -16,7 +16,7 @@ class KeyboardLayout { /// Translate a physical keycode for use as a menu key equivalent. /// /// Must be called on the main thread because Text Input Sources APIs are not thread-safe. - static func character( + @MainActor static func character( for keyCode: UInt16, modifiers: NSEvent.ModifierFlags ) -> Character? { diff --git a/macos/Tests/Ghostty/ConfigTests.swift b/macos/Tests/Ghostty/ConfigTests.swift index a4b8472ac..a8fc52be4 100644 --- a/macos/Tests/Ghostty/ConfigTests.swift +++ b/macos/Tests/Ghostty/ConfigTests.swift @@ -223,8 +223,8 @@ struct ConfigTests { // MARK: - Keybind - @Test - func uppercasedLetterShouldBeNormalized() async throws { + @MainActor @Test + func uppercasedLetterShouldBeNormalized() throws { let config = try TemporaryConfig(""" keybind=cmd+L=goto_split:left """) @@ -238,8 +238,8 @@ struct ConfigTests { #expect(shortcut2 == .init("รค", modifiers: [.command])) } - @Test - func emptyConfigShouldBeHaveDefaultShortcut() async throws { + @MainActor @Test + func emptyConfigShouldBeHaveDefaultShortcut() throws { let config = try TemporaryConfig("") let newWindow = try #require(config.keyboardShortcut(for: "new_window")) #expect(newWindow == .init("n", modifiers: [.command])) diff --git a/macos/Tests/Ghostty/MenuShortcutManagerTests.swift b/macos/Tests/Ghostty/MenuShortcutManagerTests.swift index d5a4cef00..5961498ff 100644 --- a/macos/Tests/Ghostty/MenuShortcutManagerTests.swift +++ b/macos/Tests/Ghostty/MenuShortcutManagerTests.swift @@ -26,14 +26,14 @@ struct MenuShortcutManagerTests { #expect(item.keyEquivalentModifierMask == .command) } - @Test func physicalBackquoteUsesCurrentKeyboardLayout() async throws { + @MainActor @Test func physicalBackquoteUsesCurrentKeyboardLayout() throws { let config = try TemporaryConfig("keybind=super+backquote=toggle_quick_terminal") let expected = try #require(KeyboardLayout.character(for: 0x32, modifiers: .command)) let item = NSMenuItem(title: "Quick Terminal", action: nil, keyEquivalent: "") - let manager = await Ghostty.MenuShortcutManager() + let manager = Ghostty.MenuShortcutManager() - await manager.reset() - await manager.syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: item) + manager.reset() + manager.syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: item) #expect(item.keyEquivalent == String(expected)) #expect(item.keyEquivalentModifierMask == .command)