From 52396304ff30fff1376ed38add32eaf9a1b5658d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 30 Aug 2023 22:45:29 -0700 Subject: [PATCH] macos: begin syncing menuitem key equivalents --- macos/Ghostty.xcodeproj/project.pbxproj | 4 + macos/Sources/AppDelegate.swift | 40 ++++-- macos/Sources/Ghostty/Ghostty.Input.swift | 158 ++++++++++++++++++++++ macos/Sources/Ghostty/SurfaceView.swift | 83 ------------ macos/Sources/MainMenu.xib | 7 +- 5 files changed, 198 insertions(+), 94 deletions(-) create mode 100644 macos/Sources/Ghostty/Ghostty.Input.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index e29c09cc2..1a9d21e83 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 85102A1C2A6E32890084AB3E /* PrimaryWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85102A1B2A6E32890084AB3E /* PrimaryWindowController.swift */; }; 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; }; 85DE1C922A6A3DCA00493853 /* PrimaryWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DE1C912A6A3DCA00493853 /* PrimaryWindow.swift */; }; + A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */; }; A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; }; A53426392A7DC55C00EBB7A2 /* PrimaryWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426382A7DC55C00EBB7A2 /* PrimaryWindowManager.swift */; }; A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; }; @@ -37,6 +38,7 @@ 85102A1B2A6E32890084AB3E /* PrimaryWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryWindowController.swift; sourceTree = ""; }; 857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 85DE1C912A6A3DCA00493853 /* PrimaryWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryWindow.swift; sourceTree = ""; }; + A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Input.swift; sourceTree = ""; }; A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A53426382A7DC55C00EBB7A2 /* PrimaryWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryWindowManager.swift; sourceTree = ""; }; A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; @@ -132,6 +134,7 @@ A55B7BB729B6F53A0055DE60 /* Package.swift */, A55B7BB529B6F47F0055DE60 /* AppState.swift */, A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */, + A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */, A55B7BBD29B701360055DE60 /* Ghostty.SplitView.swift */, A55685DF29A03A9F004303CE /* AppError.swift */, ); @@ -260,6 +263,7 @@ files = ( A53426392A7DC55C00EBB7A2 /* PrimaryWindowManager.swift in Sources */, 85DE1C922A6A3DCA00493853 /* PrimaryWindow.swift in Sources */, + A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */, A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */, A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */, A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */, diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index b13bd0aa9..5473accba 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -14,6 +14,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { // confirmQuit published so other views can check whether quit needs to be confirmed. @Published var confirmQuit: Bool = false + /// Various menu items so that we can programmatically sync the keyboard shortcut with the Ghostty config. + @IBOutlet private var menuPreviousSplit: NSMenuItem? + @IBOutlet private var menuNextSplit: NSMenuItem? + /// The ghostty global state. Only one per process. private var ghostty: Ghostty.AppState = Ghostty.AppState() @@ -33,6 +37,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { "ApplePressAndHoldEnabled": false, ]) + // Sync our menu shortcuts with our Ghostty config + syncMenuShortcuts() + // Let's launch our first window. // TODO: we should detect if we restored windows and if so not launch a new window. windowManager.addInitialWindow() @@ -76,6 +83,29 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { return .terminateLater } + private func syncMenuShortcuts() { + guard let cfg = ghostty.config else { return } + + if let menu = self.menuPreviousSplit { + let action = "goto_split:previous" + let trigger = ghostty_config_trigger(cfg, action, UInt(action.count)) + if let equiv = Ghostty.keyEquivalent(key: trigger.key) { + menu.keyEquivalent = equiv + menu.keyEquivalentModifierMask = Ghostty.eventModifierFlags(mods: trigger.mods) + } + } + } + + private func focusedSurface() -> ghostty_surface_t? { + guard let window = NSApp.keyWindow as? PrimaryWindow else { return nil } + return window.focusedSurfaceWrapper.surface + } + + private func splitMoveFocus(direction: Ghostty.SplitFocusDirection) { + guard let surface = focusedSurface() else { return } + ghostty.splitMoveFocus(surface: surface, direction: direction) + } + @IBAction func newWindow(_ sender: Any?) { windowManager.newWindow() } @@ -98,11 +128,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { ghostty.requestClose(surface: surface) } - private func focusedSurface() -> ghostty_surface_t? { - guard let window = NSApp.keyWindow as? PrimaryWindow else { return nil } - return window.focusedSurfaceWrapper.surface - } - @IBAction func splitHorizontally(_ sender: Any) { guard let surface = focusedSurface() else { return } ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_RIGHT) @@ -137,11 +162,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { splitMoveFocus(direction: .right) } - func splitMoveFocus(direction: Ghostty.SplitFocusDirection) { - guard let surface = focusedSurface() else { return } - ghostty.splitMoveFocus(surface: surface, direction: direction) - } - @IBAction func showHelp(_ sender: Any) { guard let url = URL(string: "https://github.com/mitchellh/ghostty") else { return } NSWorkspace.shared.open(url) diff --git a/macos/Sources/Ghostty/Ghostty.Input.swift b/macos/Sources/Ghostty/Ghostty.Input.swift new file mode 100644 index 000000000..858a1f7a0 --- /dev/null +++ b/macos/Sources/Ghostty/Ghostty.Input.swift @@ -0,0 +1,158 @@ +import Cocoa +import GhosttyKit + +extension Ghostty { + /// Returns the "keyEquivalent" string for a given input key. This doesn't always have a corresponding key. + static func keyEquivalent(key: ghostty_input_key_e) -> String? { + guard let byte = Self.keyToAscii[key] else { return nil } + return String(bytes: [byte], encoding: .utf8) + } + + /// 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] = []; + if (mods.rawValue & GHOSTTY_MODS_SHIFT.rawValue != 0) { flags.append(.shift) } + if (mods.rawValue & GHOSTTY_MODS_CTRL.rawValue != 0) { flags.append(.control) } + if (mods.rawValue & GHOSTTY_MODS_ALT.rawValue != 0) { flags.append(.option) } + if (mods.rawValue & GHOSTTY_MODS_SUPER.rawValue != 0) { flags.append(.command) } + return NSEvent.ModifierFlags(flags) + } + + static let keyToAscii: [ghostty_input_key_e : UInt8] = [ + // 0-9 + GHOSTTY_KEY_ZERO: 0x30, + GHOSTTY_KEY_ONE: 0x31, + GHOSTTY_KEY_TWO: 0x32, + GHOSTTY_KEY_THREE: 0x33, + GHOSTTY_KEY_FOUR: 0x34, + GHOSTTY_KEY_FIVE: 0x35, + GHOSTTY_KEY_SIX: 0x36, + GHOSTTY_KEY_SEVEN: 0x37, + GHOSTTY_KEY_EIGHT: 0x38, + GHOSTTY_KEY_NINE: 0x39, + + // a-z + GHOSTTY_KEY_A: 0x61, + GHOSTTY_KEY_B: 0x62, + GHOSTTY_KEY_C: 0x63, + GHOSTTY_KEY_D: 0x64, + GHOSTTY_KEY_E: 0x65, + GHOSTTY_KEY_F: 0x66, + GHOSTTY_KEY_G: 0x67, + GHOSTTY_KEY_H: 0x68, + GHOSTTY_KEY_I: 0x69, + GHOSTTY_KEY_J: 0x6A, + GHOSTTY_KEY_K: 0x6B, + GHOSTTY_KEY_L: 0x6C, + GHOSTTY_KEY_M: 0x6D, + GHOSTTY_KEY_N: 0x6E, + GHOSTTY_KEY_O: 0x6F, + GHOSTTY_KEY_P: 0x70, + GHOSTTY_KEY_Q: 0x71, + GHOSTTY_KEY_R: 0x72, + GHOSTTY_KEY_S: 0x73, + GHOSTTY_KEY_T: 0x74, + GHOSTTY_KEY_U: 0x75, + GHOSTTY_KEY_V: 0x76, + GHOSTTY_KEY_W: 0x77, + GHOSTTY_KEY_X: 0x78, + GHOSTTY_KEY_Y: 0x79, + GHOSTTY_KEY_Z: 0x7A, + + // Symbols + GHOSTTY_KEY_APOSTROPHE: 0x27, + GHOSTTY_KEY_BACKSLASH: 0x5C, + GHOSTTY_KEY_COMMA: 0x2C, + GHOSTTY_KEY_EQUAL: 0x3D, + GHOSTTY_KEY_GRAVE_ACCENT: 0x60, + GHOSTTY_KEY_LEFT_BRACKET: 0x5B, + GHOSTTY_KEY_MINUS: 0x2D, + GHOSTTY_KEY_PERIOD: 0x2E, + GHOSTTY_KEY_RIGHT_BRACKET: 0x5D, + GHOSTTY_KEY_SEMICOLON: 0x3B, + GHOSTTY_KEY_SLASH: 0x2F, + ] + + static let asciiToKey: [UInt8 : ghostty_input_key_e] = [ + // 0-9 + 0x30: GHOSTTY_KEY_ZERO, + 0x31: GHOSTTY_KEY_ONE, + 0x32: GHOSTTY_KEY_TWO, + 0x33: GHOSTTY_KEY_THREE, + 0x34: GHOSTTY_KEY_FOUR, + 0x35: GHOSTTY_KEY_FIVE, + 0x36: GHOSTTY_KEY_SIX, + 0x37: GHOSTTY_KEY_SEVEN, + 0x38: GHOSTTY_KEY_EIGHT, + 0x39: GHOSTTY_KEY_NINE, + + // A-Z + 0x41: GHOSTTY_KEY_A, + 0x42: GHOSTTY_KEY_B, + 0x43: GHOSTTY_KEY_C, + 0x44: GHOSTTY_KEY_D, + 0x45: GHOSTTY_KEY_E, + 0x46: GHOSTTY_KEY_F, + 0x47: GHOSTTY_KEY_G, + 0x48: GHOSTTY_KEY_H, + 0x49: GHOSTTY_KEY_I, + 0x4A: GHOSTTY_KEY_J, + 0x4B: GHOSTTY_KEY_K, + 0x4C: GHOSTTY_KEY_L, + 0x4D: GHOSTTY_KEY_M, + 0x4E: GHOSTTY_KEY_N, + 0x4F: GHOSTTY_KEY_O, + 0x50: GHOSTTY_KEY_P, + 0x51: GHOSTTY_KEY_Q, + 0x52: GHOSTTY_KEY_R, + 0x53: GHOSTTY_KEY_S, + 0x54: GHOSTTY_KEY_T, + 0x55: GHOSTTY_KEY_U, + 0x56: GHOSTTY_KEY_V, + 0x57: GHOSTTY_KEY_W, + 0x58: GHOSTTY_KEY_X, + 0x59: GHOSTTY_KEY_Y, + 0x5A: GHOSTTY_KEY_Z, + + // a-z + 0x61: GHOSTTY_KEY_A, + 0x62: GHOSTTY_KEY_B, + 0x63: GHOSTTY_KEY_C, + 0x64: GHOSTTY_KEY_D, + 0x65: GHOSTTY_KEY_E, + 0x66: GHOSTTY_KEY_F, + 0x67: GHOSTTY_KEY_G, + 0x68: GHOSTTY_KEY_H, + 0x69: GHOSTTY_KEY_I, + 0x6A: GHOSTTY_KEY_J, + 0x6B: GHOSTTY_KEY_K, + 0x6C: GHOSTTY_KEY_L, + 0x6D: GHOSTTY_KEY_M, + 0x6E: GHOSTTY_KEY_N, + 0x6F: GHOSTTY_KEY_O, + 0x70: GHOSTTY_KEY_P, + 0x71: GHOSTTY_KEY_Q, + 0x72: GHOSTTY_KEY_R, + 0x73: GHOSTTY_KEY_S, + 0x74: GHOSTTY_KEY_T, + 0x75: GHOSTTY_KEY_U, + 0x76: GHOSTTY_KEY_V, + 0x77: GHOSTTY_KEY_W, + 0x78: GHOSTTY_KEY_X, + 0x79: GHOSTTY_KEY_Y, + 0x7A: GHOSTTY_KEY_Z, + + // Symbols + 0x27: GHOSTTY_KEY_APOSTROPHE, + 0x5C: GHOSTTY_KEY_BACKSLASH, + 0x2C: GHOSTTY_KEY_COMMA, + 0x3D: GHOSTTY_KEY_EQUAL, + 0x60: GHOSTTY_KEY_GRAVE_ACCENT, + 0x5B: GHOSTTY_KEY_LEFT_BRACKET, + 0x2D: GHOSTTY_KEY_MINUS, + 0x2E: GHOSTTY_KEY_PERIOD, + 0x5D: GHOSTTY_KEY_RIGHT_BRACKET, + 0x3B: GHOSTTY_KEY_SEMICOLON, + 0x2F: GHOSTTY_KEY_SLASH, + ] +} diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 60d653bd5..91f2f5263 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -624,89 +624,6 @@ extension Ghostty { 0x43: GHOSTTY_KEY_KP_MULTIPLY, 0x4E: GHOSTTY_KEY_KP_SUBTRACT, ]; - - static let ascii: [UInt8 : ghostty_input_key_e] = [ - // 0-9 - 0x30: GHOSTTY_KEY_ZERO, - 0x31: GHOSTTY_KEY_ONE, - 0x32: GHOSTTY_KEY_TWO, - 0x33: GHOSTTY_KEY_THREE, - 0x34: GHOSTTY_KEY_FOUR, - 0x35: GHOSTTY_KEY_FIVE, - 0x36: GHOSTTY_KEY_SIX, - 0x37: GHOSTTY_KEY_SEVEN, - 0x38: GHOSTTY_KEY_EIGHT, - 0x39: GHOSTTY_KEY_NINE, - - // A-Z - 0x41: GHOSTTY_KEY_A, - 0x42: GHOSTTY_KEY_B, - 0x43: GHOSTTY_KEY_C, - 0x44: GHOSTTY_KEY_D, - 0x45: GHOSTTY_KEY_E, - 0x46: GHOSTTY_KEY_F, - 0x47: GHOSTTY_KEY_G, - 0x48: GHOSTTY_KEY_H, - 0x49: GHOSTTY_KEY_I, - 0x4A: GHOSTTY_KEY_J, - 0x4B: GHOSTTY_KEY_K, - 0x4C: GHOSTTY_KEY_L, - 0x4D: GHOSTTY_KEY_M, - 0x4E: GHOSTTY_KEY_N, - 0x4F: GHOSTTY_KEY_O, - 0x50: GHOSTTY_KEY_P, - 0x51: GHOSTTY_KEY_Q, - 0x52: GHOSTTY_KEY_R, - 0x53: GHOSTTY_KEY_S, - 0x54: GHOSTTY_KEY_T, - 0x55: GHOSTTY_KEY_U, - 0x56: GHOSTTY_KEY_V, - 0x57: GHOSTTY_KEY_W, - 0x58: GHOSTTY_KEY_X, - 0x59: GHOSTTY_KEY_Y, - 0x5A: GHOSTTY_KEY_Z, - - // a-z - 0x61: GHOSTTY_KEY_A, - 0x62: GHOSTTY_KEY_B, - 0x63: GHOSTTY_KEY_C, - 0x64: GHOSTTY_KEY_D, - 0x65: GHOSTTY_KEY_E, - 0x66: GHOSTTY_KEY_F, - 0x67: GHOSTTY_KEY_G, - 0x68: GHOSTTY_KEY_H, - 0x69: GHOSTTY_KEY_I, - 0x6A: GHOSTTY_KEY_J, - 0x6B: GHOSTTY_KEY_K, - 0x6C: GHOSTTY_KEY_L, - 0x6D: GHOSTTY_KEY_M, - 0x6E: GHOSTTY_KEY_N, - 0x6F: GHOSTTY_KEY_O, - 0x70: GHOSTTY_KEY_P, - 0x71: GHOSTTY_KEY_Q, - 0x72: GHOSTTY_KEY_R, - 0x73: GHOSTTY_KEY_S, - 0x74: GHOSTTY_KEY_T, - 0x75: GHOSTTY_KEY_U, - 0x76: GHOSTTY_KEY_V, - 0x77: GHOSTTY_KEY_W, - 0x78: GHOSTTY_KEY_X, - 0x79: GHOSTTY_KEY_Y, - 0x7A: GHOSTTY_KEY_Z, - - // Symbols - 0x27: GHOSTTY_KEY_APOSTROPHE, - 0x5C: GHOSTTY_KEY_BACKSLASH, - 0x2C: GHOSTTY_KEY_COMMA, - 0x3D: GHOSTTY_KEY_EQUAL, - 0x60: GHOSTTY_KEY_GRAVE_ACCENT, - 0x5B: GHOSTTY_KEY_LEFT_BRACKET, - 0x2D: GHOSTTY_KEY_MINUS, - 0x2E: GHOSTTY_KEY_PERIOD, - 0x5D: GHOSTTY_KEY_RIGHT_BRACKET, - 0x3B: GHOSTTY_KEY_SEMICOLON, - 0x2F: GHOSTTY_KEY_SLASH, - ] } } diff --git a/macos/Sources/MainMenu.xib b/macos/Sources/MainMenu.xib index f65bf2e24..db1a70a8a 100644 --- a/macos/Sources/MainMenu.xib +++ b/macos/Sources/MainMenu.xib @@ -12,7 +12,12 @@ - + + + + + +