import AppIntents import Cocoa import SwiftUI import GhosttyKit extension Ghostty { struct Input {} // MARK: Keyboard Shortcuts /// Return the key equivalent for the given trigger. /// /// Returns nil if the trigger doesn't have an equivalent KeyboardShortcut. This is possible /// because Ghostty input triggers are a superset of what can be represented by a macOS /// KeyboardShortcut. For example, macOS doesn't have any way to represent function keys /// (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? { let key: KeyEquivalent switch (trigger.tag) { case GHOSTTY_TRIGGER_PHYSICAL: // Only functional keys can be converted to a KeyboardShortcut. Other physical // mappings cannot because KeyboardShortcut in Swift is inherently layout-dependent. if let equiv = Self.keyToEquivalent[trigger.key.physical] { key = equiv } else { return nil } case GHOSTTY_TRIGGER_UNICODE: guard let scalar = UnicodeScalar(trigger.key.unicode) else { return nil } key = KeyEquivalent(Character(scalar)) default: return nil } return KeyboardShortcut( key, modifiers: EventModifiers(nsFlags: Ghostty.eventModifierFlags(mods: trigger.mods))) } // MARK: Mods /// 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); if (mods.rawValue & GHOSTTY_MODS_SHIFT.rawValue != 0) { flags.insert(.shift) } if (mods.rawValue & GHOSTTY_MODS_CTRL.rawValue != 0) { flags.insert(.control) } if (mods.rawValue & GHOSTTY_MODS_ALT.rawValue != 0) { flags.insert(.option) } if (mods.rawValue & GHOSTTY_MODS_SUPER.rawValue != 0) { flags.insert(.command) } return flags } /// Translate event modifier flags to a ghostty mods enum. static func ghosttyMods(_ flags: NSEvent.ModifierFlags) -> ghostty_input_mods_e { var mods: UInt32 = GHOSTTY_MODS_NONE.rawValue if (flags.contains(.shift)) { mods |= GHOSTTY_MODS_SHIFT.rawValue } if (flags.contains(.control)) { mods |= GHOSTTY_MODS_CTRL.rawValue } if (flags.contains(.option)) { mods |= GHOSTTY_MODS_ALT.rawValue } if (flags.contains(.command)) { mods |= GHOSTTY_MODS_SUPER.rawValue } if (flags.contains(.capsLock)) { mods |= GHOSTTY_MODS_CAPS.rawValue } // Handle sided input. We can't tell that both are pressed in the // Ghostty structure but thats okay -- we don't use that information. let rawFlags = flags.rawValue if (rawFlags & UInt(NX_DEVICERSHIFTKEYMASK) != 0) { mods |= GHOSTTY_MODS_SHIFT_RIGHT.rawValue } if (rawFlags & UInt(NX_DEVICERCTLKEYMASK) != 0) { mods |= GHOSTTY_MODS_CTRL_RIGHT.rawValue } if (rawFlags & UInt(NX_DEVICERALTKEYMASK) != 0) { mods |= GHOSTTY_MODS_ALT_RIGHT.rawValue } if (rawFlags & UInt(NX_DEVICERCMDKEYMASK) != 0) { mods |= GHOSTTY_MODS_SUPER_RIGHT.rawValue } return ghostty_input_mods_e(mods) } /// A map from the Ghostty key enum to the keyEquivalent string for shortcuts. Note that /// not all ghostty key enum values are represented here because not all of them can be /// mapped to a KeyEquivalent. static let keyToEquivalent: [ghostty_input_key_e : KeyEquivalent] = [ // Function keys GHOSTTY_KEY_ARROW_UP: .upArrow, GHOSTTY_KEY_ARROW_DOWN: .downArrow, GHOSTTY_KEY_ARROW_LEFT: .leftArrow, GHOSTTY_KEY_ARROW_RIGHT: .rightArrow, GHOSTTY_KEY_HOME: .home, GHOSTTY_KEY_END: .end, GHOSTTY_KEY_DELETE: .delete, GHOSTTY_KEY_PAGE_UP: .pageUp, GHOSTTY_KEY_PAGE_DOWN: .pageDown, GHOSTTY_KEY_ESCAPE: .escape, GHOSTTY_KEY_ENTER: .return, GHOSTTY_KEY_TAB: .tab, GHOSTTY_KEY_BACKSPACE: .delete, GHOSTTY_KEY_SPACE: .space, ] } // MARK: Ghostty.Input.KeyEvent extension Ghostty.Input { /// `ghostty_input_key_s` struct KeyEvent { let action: Action let key: Key let text: String? let composing: Bool let mods: Mods let consumedMods: Mods let unshiftedCodepoint: UInt32 init( key: Key, action: Action = .press, text: String? = nil, composing: Bool = false, mods: Mods = [], consumedMods: Mods = [], unshiftedCodepoint: UInt32 = 0 ) { self.key = key self.action = action self.text = text self.composing = composing self.mods = mods self.consumedMods = consumedMods self.unshiftedCodepoint = unshiftedCodepoint } init?(cValue: ghostty_input_key_s) { // Convert action switch cValue.action { case GHOSTTY_ACTION_PRESS: self.action = .press case GHOSTTY_ACTION_RELEASE: self.action = .release case GHOSTTY_ACTION_REPEAT: self.action = .repeat default: self.action = .press } // Convert key from keycode guard let key = Key(keyCode: UInt16(cValue.keycode)) else { return nil } self.key = key // Convert text if let textPtr = cValue.text { self.text = String(cString: textPtr) } else { self.text = nil } // Set composing state self.composing = cValue.composing // Convert modifiers self.mods = Mods(cMods: cValue.mods) self.consumedMods = Mods(cMods: cValue.consumed_mods) // Set unshifted codepoint self.unshiftedCodepoint = cValue.unshifted_codepoint } /// Executes a closure with a temporary C representation of this KeyEvent. /// /// This method safely converts the Swift KeyEntity to a C `ghostty_input_key_s` struct /// and passes it to the provided closure. The C struct is only valid within the closure's /// execution scope. The text field's C string pointer is managed automatically and will /// be invalid after the closure returns. /// /// - Parameter execute: A closure that receives the C struct and returns a value /// - Returns: The value returned by the closure @discardableResult func withCValue(execute: (ghostty_input_key_s) -> T) -> T { var keyEvent = ghostty_input_key_s() keyEvent.action = action.cAction keyEvent.keycode = UInt32(key.keyCode ?? 0) keyEvent.composing = composing keyEvent.mods = mods.cMods keyEvent.consumed_mods = consumedMods.cMods keyEvent.unshifted_codepoint = unshiftedCodepoint // Handle text with proper memory management if let text = text { return text.withCString { textPtr in keyEvent.text = textPtr return execute(keyEvent) } } else { keyEvent.text = nil return execute(keyEvent) } } } } // MARK: Ghostty.Input.Action extension Ghostty.Input { /// `ghostty_input_action_e` enum Action: String, CaseIterable { case release case press case `repeat` var cAction: ghostty_input_action_e { switch self { case .release: GHOSTTY_ACTION_RELEASE case .press: GHOSTTY_ACTION_PRESS case .repeat: GHOSTTY_ACTION_REPEAT } } } } extension Ghostty.Input.Action: AppEnum { static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Key Action") static var caseDisplayRepresentations: [Ghostty.Input.Action : DisplayRepresentation] = [ .release: "Release", .press: "Press", .repeat: "Repeat" ] } // MARK: Ghostty.Input.MouseEvent extension Ghostty.Input { /// Represents a mouse input event with button state, button type, and modifier keys. struct MouseButtonEvent { let action: MouseState let button: MouseButton let mods: Mods init( action: MouseState, button: MouseButton, mods: Mods = [] ) { self.action = action self.button = button self.mods = mods } /// Creates a MouseEvent from C enum values. /// /// This initializer converts C-style mouse input enums to Swift types. /// Returns nil if any of the C enum values are invalid or unsupported. /// /// - Parameters: /// - state: The mouse button state (press/release) /// - button: The mouse button that was pressed/released /// - mods: The modifier keys held during the mouse event init?(state: ghostty_input_mouse_state_e, button: ghostty_input_mouse_button_e, mods: ghostty_input_mods_e) { // Convert state switch state { case GHOSTTY_MOUSE_RELEASE: self.action = .release case GHOSTTY_MOUSE_PRESS: self.action = .press default: return nil } // Convert button switch button { case GHOSTTY_MOUSE_UNKNOWN: self.button = .unknown case GHOSTTY_MOUSE_LEFT: self.button = .left case GHOSTTY_MOUSE_RIGHT: self.button = .right case GHOSTTY_MOUSE_MIDDLE: self.button = .middle default: return nil } // Convert modifiers self.mods = Mods(cMods: mods) } } /// Represents a mouse position/movement event with coordinates and modifier keys. struct MousePosEvent { let x: Double let y: Double let mods: Mods init( x: Double, y: Double, mods: Mods = [] ) { self.x = x self.y = y self.mods = mods } } /// Represents a mouse scroll event with scroll deltas and modifier keys. struct MouseScrollEvent { let x: Double let y: Double let mods: ScrollMods init( x: Double, y: Double, mods: ScrollMods = .init(rawValue: 0) ) { self.x = x self.y = y self.mods = mods } } } // MARK: Ghostty.Input.MouseState extension Ghostty.Input { /// `ghostty_input_mouse_state_e` enum MouseState: String, CaseIterable { case release case press var cMouseState: ghostty_input_mouse_state_e { switch self { case .release: GHOSTTY_MOUSE_RELEASE case .press: GHOSTTY_MOUSE_PRESS } } } } extension Ghostty.Input.MouseState: AppEnum { static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Mouse State") static var caseDisplayRepresentations: [Ghostty.Input.MouseState : DisplayRepresentation] = [ .release: "Release", .press: "Press" ] } // MARK: Ghostty.Input.MouseButton extension Ghostty.Input { /// `ghostty_input_mouse_button_e` enum MouseButton: String, CaseIterable { case unknown case left case right case middle var cMouseButton: ghostty_input_mouse_button_e { switch self { case .unknown: GHOSTTY_MOUSE_UNKNOWN case .left: GHOSTTY_MOUSE_LEFT case .right: GHOSTTY_MOUSE_RIGHT case .middle: GHOSTTY_MOUSE_MIDDLE } } } } extension Ghostty.Input.MouseButton: AppEnum { static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Mouse Button") static var caseDisplayRepresentations: [Ghostty.Input.MouseButton : DisplayRepresentation] = [ .unknown: "Unknown", .left: "Left", .right: "Right", .middle: "Middle" ] static var allCases: [Ghostty.Input.MouseButton] = [ .left, .right, .middle, ] } // MARK: Ghostty.Input.ScrollMods extension Ghostty.Input { /// `ghostty_input_scroll_mods_t` - Scroll event modifiers /// /// This is a packed bitmask that contains precision and momentum information /// for scroll events, matching the Zig `ScrollMods` packed struct. struct ScrollMods { let rawValue: Int32 /// True if this is a high-precision scroll event (e.g., trackpad, Magic Mouse) var precision: Bool { rawValue & 0b0000_0001 != 0 } /// The momentum phase of the scroll event for inertial scrolling var momentum: Momentum { let momentumBits = (rawValue >> 1) & 0b0000_0111 return Momentum(rawValue: UInt8(momentumBits)) ?? .none } init(precision: Bool = false, momentum: Momentum = .none) { var value: Int32 = 0 if precision { value |= 0b0000_0001 } value |= Int32(momentum.rawValue) << 1 self.rawValue = value } init(rawValue: Int32) { self.rawValue = rawValue } var cScrollMods: ghostty_input_scroll_mods_t { rawValue } } } // MARK: Ghostty.Input.Momentum extension Ghostty.Input { /// `ghostty_input_mouse_momentum_e` - Momentum phase for scroll events enum Momentum: UInt8, CaseIterable { case none = 0 case began = 1 case stationary = 2 case changed = 3 case ended = 4 case cancelled = 5 case mayBegin = 6 var cMomentum: ghostty_input_mouse_momentum_e { switch self { case .none: GHOSTTY_MOUSE_MOMENTUM_NONE case .began: GHOSTTY_MOUSE_MOMENTUM_BEGAN case .stationary: GHOSTTY_MOUSE_MOMENTUM_STATIONARY case .changed: GHOSTTY_MOUSE_MOMENTUM_CHANGED case .ended: GHOSTTY_MOUSE_MOMENTUM_ENDED case .cancelled: GHOSTTY_MOUSE_MOMENTUM_CANCELLED case .mayBegin: GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN } } } } #if canImport(AppKit) import AppKit extension Ghostty.Input.Momentum { /// Create a Momentum from an NSEvent.Phase init(_ phase: NSEvent.Phase) { switch phase { case .began: self = .began case .stationary: self = .stationary case .changed: self = .changed case .ended: self = .ended case .cancelled: self = .cancelled case .mayBegin: self = .mayBegin default: self = .none } } } #endif // MARK: Ghostty.Input.Mods extension Ghostty.Input { /// `ghostty_input_mods_e` struct Mods: OptionSet { let rawValue: UInt32 static let none = Mods(rawValue: GHOSTTY_MODS_NONE.rawValue) static let shift = Mods(rawValue: GHOSTTY_MODS_SHIFT.rawValue) static let ctrl = Mods(rawValue: GHOSTTY_MODS_CTRL.rawValue) static let alt = Mods(rawValue: GHOSTTY_MODS_ALT.rawValue) static let `super` = Mods(rawValue: GHOSTTY_MODS_SUPER.rawValue) static let caps = Mods(rawValue: GHOSTTY_MODS_CAPS.rawValue) static let shiftRight = Mods(rawValue: GHOSTTY_MODS_SHIFT_RIGHT.rawValue) static let ctrlRight = Mods(rawValue: GHOSTTY_MODS_CTRL_RIGHT.rawValue) static let altRight = Mods(rawValue: GHOSTTY_MODS_ALT_RIGHT.rawValue) static let superRight = Mods(rawValue: GHOSTTY_MODS_SUPER_RIGHT.rawValue) var cMods: ghostty_input_mods_e { ghostty_input_mods_e(rawValue) } init(rawValue: UInt32) { self.rawValue = rawValue } init(cMods: ghostty_input_mods_e) { self.rawValue = cMods.rawValue } init(nsFlags: NSEvent.ModifierFlags) { self.init(cMods: Ghostty.ghosttyMods(nsFlags)) } var nsFlags: NSEvent.ModifierFlags { Ghostty.eventModifierFlags(mods: cMods) } } } // MARK: Ghostty.Input.Key extension Ghostty.Input { /// `ghostty_input_key_e` enum Key: String { // Writing System Keys case backquote case backslash case bracketLeft case bracketRight case comma case digit0 case digit1 case digit2 case digit3 case digit4 case digit5 case digit6 case digit7 case digit8 case digit9 case equal case intlBackslash case intlRo case intlYen case a case b case c case d case e case f case g case h case i case j case k case l case m case n case o case p case q case r case s case t case u case v case w case x case y case z case minus case period case quote case semicolon case slash // Functional Keys case altLeft case altRight case backspace case capsLock case contextMenu case controlLeft case controlRight case enter case metaLeft case metaRight case shiftLeft case shiftRight case space case tab case convert case kanaMode case nonConvert // Control Pad Section case delete case end case help case home case insert case pageDown case pageUp // Arrow Pad Section case arrowDown case arrowLeft case arrowRight case arrowUp // Numpad Section case numLock case numpad0 case numpad1 case numpad2 case numpad3 case numpad4 case numpad5 case numpad6 case numpad7 case numpad8 case numpad9 case numpadAdd case numpadBackspace case numpadClear case numpadClearEntry case numpadComma case numpadDecimal case numpadDivide case numpadEnter case numpadEqual case numpadMemoryAdd case numpadMemoryClear case numpadMemoryRecall case numpadMemoryStore case numpadMemorySubtract case numpadMultiply case numpadParenLeft case numpadParenRight case numpadSubtract case numpadSeparator case numpadUp case numpadDown case numpadRight case numpadLeft case numpadBegin case numpadHome case numpadEnd case numpadInsert case numpadDelete case numpadPageUp case numpadPageDown // Function Section case escape case f1 case f2 case f3 case f4 case f5 case f6 case f7 case f8 case f9 case f10 case f11 case f12 case f13 case f14 case f15 case f16 case f17 case f18 case f19 case f20 case f21 case f22 case f23 case f24 case f25 case fn case fnLock case printScreen case scrollLock case pause // Media Keys case browserBack case browserFavorites case browserForward case browserHome case browserRefresh case browserSearch case browserStop case eject case launchApp1 case launchApp2 case launchMail case mediaPlayPause case mediaSelect case mediaStop case mediaTrackNext case mediaTrackPrevious case power case sleep case audioVolumeDown case audioVolumeMute case audioVolumeUp case wakeUp // Legacy, Non-standard, and Special Keys case copy case cut case paste /// Get a key from a keycode init?(keyCode: UInt16) { if let key = Key.allCases.first(where: { $0.keyCode == keyCode }) { self = key return } return nil } var cKey: ghostty_input_key_e { switch self { // Writing System Keys case .backquote: GHOSTTY_KEY_BACKQUOTE case .backslash: GHOSTTY_KEY_BACKSLASH case .bracketLeft: GHOSTTY_KEY_BRACKET_LEFT case .bracketRight: GHOSTTY_KEY_BRACKET_RIGHT case .comma: GHOSTTY_KEY_COMMA case .digit0: GHOSTTY_KEY_DIGIT_0 case .digit1: GHOSTTY_KEY_DIGIT_1 case .digit2: GHOSTTY_KEY_DIGIT_2 case .digit3: GHOSTTY_KEY_DIGIT_3 case .digit4: GHOSTTY_KEY_DIGIT_4 case .digit5: GHOSTTY_KEY_DIGIT_5 case .digit6: GHOSTTY_KEY_DIGIT_6 case .digit7: GHOSTTY_KEY_DIGIT_7 case .digit8: GHOSTTY_KEY_DIGIT_8 case .digit9: GHOSTTY_KEY_DIGIT_9 case .equal: GHOSTTY_KEY_EQUAL case .intlBackslash: GHOSTTY_KEY_INTL_BACKSLASH case .intlRo: GHOSTTY_KEY_INTL_RO case .intlYen: GHOSTTY_KEY_INTL_YEN case .a: GHOSTTY_KEY_A case .b: GHOSTTY_KEY_B case .c: GHOSTTY_KEY_C case .d: GHOSTTY_KEY_D case .e: GHOSTTY_KEY_E case .f: GHOSTTY_KEY_F case .g: GHOSTTY_KEY_G case .h: GHOSTTY_KEY_H case .i: GHOSTTY_KEY_I case .j: GHOSTTY_KEY_J case .k: GHOSTTY_KEY_K case .l: GHOSTTY_KEY_L case .m: GHOSTTY_KEY_M case .n: GHOSTTY_KEY_N case .o: GHOSTTY_KEY_O case .p: GHOSTTY_KEY_P case .q: GHOSTTY_KEY_Q case .r: GHOSTTY_KEY_R case .s: GHOSTTY_KEY_S case .t: GHOSTTY_KEY_T case .u: GHOSTTY_KEY_U case .v: GHOSTTY_KEY_V case .w: GHOSTTY_KEY_W case .x: GHOSTTY_KEY_X case .y: GHOSTTY_KEY_Y case .z: GHOSTTY_KEY_Z case .minus: GHOSTTY_KEY_MINUS case .period: GHOSTTY_KEY_PERIOD case .quote: GHOSTTY_KEY_QUOTE case .semicolon: GHOSTTY_KEY_SEMICOLON case .slash: GHOSTTY_KEY_SLASH // Functional Keys case .altLeft: GHOSTTY_KEY_ALT_LEFT case .altRight: GHOSTTY_KEY_ALT_RIGHT case .backspace: GHOSTTY_KEY_BACKSPACE case .capsLock: GHOSTTY_KEY_CAPS_LOCK case .contextMenu: GHOSTTY_KEY_CONTEXT_MENU case .controlLeft: GHOSTTY_KEY_CONTROL_LEFT case .controlRight: GHOSTTY_KEY_CONTROL_RIGHT case .enter: GHOSTTY_KEY_ENTER case .metaLeft: GHOSTTY_KEY_META_LEFT case .metaRight: GHOSTTY_KEY_META_RIGHT case .shiftLeft: GHOSTTY_KEY_SHIFT_LEFT case .shiftRight: GHOSTTY_KEY_SHIFT_RIGHT case .space: GHOSTTY_KEY_SPACE case .tab: GHOSTTY_KEY_TAB case .convert: GHOSTTY_KEY_CONVERT case .kanaMode: GHOSTTY_KEY_KANA_MODE case .nonConvert: GHOSTTY_KEY_NON_CONVERT // Control Pad Section case .delete: GHOSTTY_KEY_DELETE case .end: GHOSTTY_KEY_END case .help: GHOSTTY_KEY_HELP case .home: GHOSTTY_KEY_HOME case .insert: GHOSTTY_KEY_INSERT case .pageDown: GHOSTTY_KEY_PAGE_DOWN case .pageUp: GHOSTTY_KEY_PAGE_UP // Arrow Pad Section case .arrowDown: GHOSTTY_KEY_ARROW_DOWN case .arrowLeft: GHOSTTY_KEY_ARROW_LEFT case .arrowRight: GHOSTTY_KEY_ARROW_RIGHT case .arrowUp: GHOSTTY_KEY_ARROW_UP // Numpad Section case .numLock: GHOSTTY_KEY_NUM_LOCK case .numpad0: GHOSTTY_KEY_NUMPAD_0 case .numpad1: GHOSTTY_KEY_NUMPAD_1 case .numpad2: GHOSTTY_KEY_NUMPAD_2 case .numpad3: GHOSTTY_KEY_NUMPAD_3 case .numpad4: GHOSTTY_KEY_NUMPAD_4 case .numpad5: GHOSTTY_KEY_NUMPAD_5 case .numpad6: GHOSTTY_KEY_NUMPAD_6 case .numpad7: GHOSTTY_KEY_NUMPAD_7 case .numpad8: GHOSTTY_KEY_NUMPAD_8 case .numpad9: GHOSTTY_KEY_NUMPAD_9 case .numpadAdd: GHOSTTY_KEY_NUMPAD_ADD case .numpadBackspace: GHOSTTY_KEY_NUMPAD_BACKSPACE case .numpadClear: GHOSTTY_KEY_NUMPAD_CLEAR case .numpadClearEntry: GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY case .numpadComma: GHOSTTY_KEY_NUMPAD_COMMA case .numpadDecimal: GHOSTTY_KEY_NUMPAD_DECIMAL case .numpadDivide: GHOSTTY_KEY_NUMPAD_DIVIDE case .numpadEnter: GHOSTTY_KEY_NUMPAD_ENTER case .numpadEqual: GHOSTTY_KEY_NUMPAD_EQUAL case .numpadMemoryAdd: GHOSTTY_KEY_NUMPAD_MEMORY_ADD case .numpadMemoryClear: GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR case .numpadMemoryRecall: GHOSTTY_KEY_NUMPAD_MEMORY_RECALL case .numpadMemoryStore: GHOSTTY_KEY_NUMPAD_MEMORY_STORE case .numpadMemorySubtract: GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT case .numpadMultiply: GHOSTTY_KEY_NUMPAD_MULTIPLY case .numpadParenLeft: GHOSTTY_KEY_NUMPAD_PAREN_LEFT case .numpadParenRight: GHOSTTY_KEY_NUMPAD_PAREN_RIGHT case .numpadSubtract: GHOSTTY_KEY_NUMPAD_SUBTRACT case .numpadSeparator: GHOSTTY_KEY_NUMPAD_SEPARATOR case .numpadUp: GHOSTTY_KEY_NUMPAD_UP case .numpadDown: GHOSTTY_KEY_NUMPAD_DOWN case .numpadRight: GHOSTTY_KEY_NUMPAD_RIGHT case .numpadLeft: GHOSTTY_KEY_NUMPAD_LEFT case .numpadBegin: GHOSTTY_KEY_NUMPAD_BEGIN case .numpadHome: GHOSTTY_KEY_NUMPAD_HOME case .numpadEnd: GHOSTTY_KEY_NUMPAD_END case .numpadInsert: GHOSTTY_KEY_NUMPAD_INSERT case .numpadDelete: GHOSTTY_KEY_NUMPAD_DELETE case .numpadPageUp: GHOSTTY_KEY_NUMPAD_PAGE_UP case .numpadPageDown: GHOSTTY_KEY_NUMPAD_PAGE_DOWN // Function Section case .escape: GHOSTTY_KEY_ESCAPE case .f1: GHOSTTY_KEY_F1 case .f2: GHOSTTY_KEY_F2 case .f3: GHOSTTY_KEY_F3 case .f4: GHOSTTY_KEY_F4 case .f5: GHOSTTY_KEY_F5 case .f6: GHOSTTY_KEY_F6 case .f7: GHOSTTY_KEY_F7 case .f8: GHOSTTY_KEY_F8 case .f9: GHOSTTY_KEY_F9 case .f10: GHOSTTY_KEY_F10 case .f11: GHOSTTY_KEY_F11 case .f12: GHOSTTY_KEY_F12 case .f13: GHOSTTY_KEY_F13 case .f14: GHOSTTY_KEY_F14 case .f15: GHOSTTY_KEY_F15 case .f16: GHOSTTY_KEY_F16 case .f17: GHOSTTY_KEY_F17 case .f18: GHOSTTY_KEY_F18 case .f19: GHOSTTY_KEY_F19 case .f20: GHOSTTY_KEY_F20 case .f21: GHOSTTY_KEY_F21 case .f22: GHOSTTY_KEY_F22 case .f23: GHOSTTY_KEY_F23 case .f24: GHOSTTY_KEY_F24 case .f25: GHOSTTY_KEY_F25 case .fn: GHOSTTY_KEY_FN case .fnLock: GHOSTTY_KEY_FN_LOCK case .printScreen: GHOSTTY_KEY_PRINT_SCREEN case .scrollLock: GHOSTTY_KEY_SCROLL_LOCK case .pause: GHOSTTY_KEY_PAUSE // Media Keys case .browserBack: GHOSTTY_KEY_BROWSER_BACK case .browserFavorites: GHOSTTY_KEY_BROWSER_FAVORITES case .browserForward: GHOSTTY_KEY_BROWSER_FORWARD case .browserHome: GHOSTTY_KEY_BROWSER_HOME case .browserRefresh: GHOSTTY_KEY_BROWSER_REFRESH case .browserSearch: GHOSTTY_KEY_BROWSER_SEARCH case .browserStop: GHOSTTY_KEY_BROWSER_STOP case .eject: GHOSTTY_KEY_EJECT case .launchApp1: GHOSTTY_KEY_LAUNCH_APP_1 case .launchApp2: GHOSTTY_KEY_LAUNCH_APP_2 case .launchMail: GHOSTTY_KEY_LAUNCH_MAIL case .mediaPlayPause: GHOSTTY_KEY_MEDIA_PLAY_PAUSE case .mediaSelect: GHOSTTY_KEY_MEDIA_SELECT case .mediaStop: GHOSTTY_KEY_MEDIA_STOP case .mediaTrackNext: GHOSTTY_KEY_MEDIA_TRACK_NEXT case .mediaTrackPrevious: GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS case .power: GHOSTTY_KEY_POWER case .sleep: GHOSTTY_KEY_SLEEP case .audioVolumeDown: GHOSTTY_KEY_AUDIO_VOLUME_DOWN case .audioVolumeMute: GHOSTTY_KEY_AUDIO_VOLUME_MUTE case .audioVolumeUp: GHOSTTY_KEY_AUDIO_VOLUME_UP case .wakeUp: GHOSTTY_KEY_WAKE_UP // Legacy, Non-standard, and Special Keys case .copy: GHOSTTY_KEY_COPY case .cut: GHOSTTY_KEY_CUT case .paste: GHOSTTY_KEY_PASTE } } // Based on src/input/keycodes.zig var keyCode: UInt16? { switch self { // Writing System Keys case .backquote: return 0x0032 case .backslash: return 0x002a case .bracketLeft: return 0x0021 case .bracketRight: return 0x001e case .comma: return 0x002b case .digit0: return 0x001d case .digit1: return 0x0012 case .digit2: return 0x0013 case .digit3: return 0x0014 case .digit4: return 0x0015 case .digit5: return 0x0017 case .digit6: return 0x0016 case .digit7: return 0x001a case .digit8: return 0x001c case .digit9: return 0x0019 case .equal: return 0x0018 case .intlBackslash: return 0x000a case .intlRo: return 0x005e case .intlYen: return 0x005d case .a: return 0x0000 case .b: return 0x000b case .c: return 0x0008 case .d: return 0x0002 case .e: return 0x000e case .f: return 0x0003 case .g: return 0x0005 case .h: return 0x0004 case .i: return 0x0022 case .j: return 0x0026 case .k: return 0x0028 case .l: return 0x0025 case .m: return 0x002e case .n: return 0x002d case .o: return 0x001f case .p: return 0x0023 case .q: return 0x000c case .r: return 0x000f case .s: return 0x0001 case .t: return 0x0011 case .u: return 0x0020 case .v: return 0x0009 case .w: return 0x000d case .x: return 0x0007 case .y: return 0x0010 case .z: return 0x0006 case .minus: return 0x001b case .period: return 0x002f case .quote: return 0x0027 case .semicolon: return 0x0029 case .slash: return 0x002c // Functional Keys case .altLeft: return 0x003a case .altRight: return 0x003d case .backspace: return 0x0033 case .capsLock: return 0x0039 case .contextMenu: return 0x006e case .controlLeft: return 0x003b case .controlRight: return 0x003e case .enter: return 0x0024 case .metaLeft: return 0x0037 case .metaRight: return 0x0036 case .shiftLeft: return 0x0038 case .shiftRight: return 0x003c case .space: return 0x0031 case .tab: return 0x0030 case .convert: return nil // No Mac keycode case .kanaMode: return nil // No Mac keycode case .nonConvert: return nil // No Mac keycode // Control Pad Section case .delete: return 0x0075 case .end: return 0x0077 case .help: return nil // No Mac keycode case .home: return 0x0073 case .insert: return 0x0072 case .pageDown: return 0x0079 case .pageUp: return 0x0074 // Arrow Pad Section case .arrowDown: return 0x007d case .arrowLeft: return 0x007b case .arrowRight: return 0x007c case .arrowUp: return 0x007e // Numpad Section case .numLock: return 0x0047 case .numpad0: return 0x0052 case .numpad1: return 0x0053 case .numpad2: return 0x0054 case .numpad3: return 0x0055 case .numpad4: return 0x0056 case .numpad5: return 0x0057 case .numpad6: return 0x0058 case .numpad7: return 0x0059 case .numpad8: return 0x005b case .numpad9: return 0x005c case .numpadAdd: return 0x0045 case .numpadBackspace: return nil // No Mac keycode case .numpadClear: return nil // No Mac keycode case .numpadClearEntry: return nil // No Mac keycode case .numpadComma: return 0x005f case .numpadDecimal: return 0x0041 case .numpadDivide: return 0x004b case .numpadEnter: return 0x004c case .numpadEqual: return 0x0051 case .numpadMemoryAdd: return nil // No Mac keycode case .numpadMemoryClear: return nil // No Mac keycode case .numpadMemoryRecall: return nil // No Mac keycode case .numpadMemoryStore: return nil // No Mac keycode case .numpadMemorySubtract: return nil // No Mac keycode case .numpadMultiply: return 0x0043 case .numpadParenLeft: return nil // No Mac keycode case .numpadParenRight: return nil // No Mac keycode case .numpadSubtract: return 0x004e case .numpadSeparator: return nil // No Mac keycode case .numpadUp: return nil // No Mac keycode case .numpadDown: return nil // No Mac keycode case .numpadRight: return nil // No Mac keycode case .numpadLeft: return nil // No Mac keycode case .numpadBegin: return nil // No Mac keycode case .numpadHome: return nil // No Mac keycode case .numpadEnd: return nil // No Mac keycode case .numpadInsert: return nil // No Mac keycode case .numpadDelete: return nil // No Mac keycode case .numpadPageUp: return nil // No Mac keycode case .numpadPageDown: return nil // No Mac keycode // Function Section case .escape: return 0x0035 case .f1: return 0x007a case .f2: return 0x0078 case .f3: return 0x0063 case .f4: return 0x0076 case .f5: return 0x0060 case .f6: return 0x0061 case .f7: return 0x0062 case .f8: return 0x0064 case .f9: return 0x0065 case .f10: return 0x006d case .f11: return 0x0067 case .f12: return 0x006f case .f13: return 0x0069 case .f14: return 0x006b case .f15: return 0x0071 case .f16: return 0x006a case .f17: return 0x0040 case .f18: return 0x004f case .f19: return 0x0050 case .f20: return 0x005a case .f21: return nil // No Mac keycode case .f22: return nil // No Mac keycode case .f23: return nil // No Mac keycode case .f24: return nil // No Mac keycode case .f25: return nil // No Mac keycode case .fn: return nil // No Mac keycode case .fnLock: return nil // No Mac keycode case .printScreen: return nil // No Mac keycode case .scrollLock: return nil // No Mac keycode case .pause: return nil // No Mac keycode // Media Keys case .browserBack: return nil // No Mac keycode case .browserFavorites: return nil // No Mac keycode case .browserForward: return nil // No Mac keycode case .browserHome: return nil // No Mac keycode case .browserRefresh: return nil // No Mac keycode case .browserSearch: return nil // No Mac keycode case .browserStop: return nil // No Mac keycode case .eject: return nil // No Mac keycode case .launchApp1: return nil // No Mac keycode case .launchApp2: return nil // No Mac keycode case .launchMail: return nil // No Mac keycode case .mediaPlayPause: return nil // No Mac keycode case .mediaSelect: return nil // No Mac keycode case .mediaStop: return nil // No Mac keycode case .mediaTrackNext: return nil // No Mac keycode case .mediaTrackPrevious: return nil // No Mac keycode case .power: return nil // No Mac keycode case .sleep: return nil // No Mac keycode case .audioVolumeDown: return 0x0049 case .audioVolumeMute: return 0x004a case .audioVolumeUp: return 0x0048 case .wakeUp: return nil // No Mac keycode // Legacy, Non-standard, and Special Keys case .copy: return nil // No Mac keycode case .cut: return nil // No Mac keycode case .paste: return nil // No Mac keycode } } } } extension Ghostty.Input.Key: AppEnum { static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Key") // Only include keys that have Mac keycodes for App Intents static var allCases: [Ghostty.Input.Key] { return [ // Letters (A-Z) .a, .b, .c, .d, .e, .f, .g, .h, .i, .j, .k, .l, .m, .n, .o, .p, .q, .r, .s, .t, .u, .v, .w, .x, .y, .z, // Numbers (0-9) .digit0, .digit1, .digit2, .digit3, .digit4, .digit5, .digit6, .digit7, .digit8, .digit9, // Common Control Keys .space, .enter, .tab, .backspace, .escape, .delete, // Arrow Keys .arrowUp, .arrowDown, .arrowLeft, .arrowRight, // Navigation Keys .home, .end, .pageUp, .pageDown, .insert, // Function Keys (F1-F20) .f1, .f2, .f3, .f4, .f5, .f6, .f7, .f8, .f9, .f10, .f11, .f12, .f13, .f14, .f15, .f16, .f17, .f18, .f19, .f20, // Modifier Keys .shiftLeft, .shiftRight, .controlLeft, .controlRight, .altLeft, .altRight, .metaLeft, .metaRight, .capsLock, // Punctuation & Symbols .minus, .equal, .backquote, .bracketLeft, .bracketRight, .backslash, .semicolon, .quote, .comma, .period, .slash, // Numpad .numLock, .numpad0, .numpad1, .numpad2, .numpad3, .numpad4, .numpad5, .numpad6, .numpad7, .numpad8, .numpad9, .numpadAdd, .numpadSubtract, .numpadMultiply, .numpadDivide, .numpadDecimal, .numpadEqual, .numpadEnter, .numpadComma, // Media Keys .audioVolumeUp, .audioVolumeDown, .audioVolumeMute, // International Keys .intlBackslash, .intlRo, .intlYen, // Other .contextMenu ] } static var caseDisplayRepresentations: [Ghostty.Input.Key : DisplayRepresentation] = [ // Letters (A-Z) .a: "A", .b: "B", .c: "C", .d: "D", .e: "E", .f: "F", .g: "G", .h: "H", .i: "I", .j: "J", .k: "K", .l: "L", .m: "M", .n: "N", .o: "O", .p: "P", .q: "Q", .r: "R", .s: "S", .t: "T", .u: "U", .v: "V", .w: "W", .x: "X", .y: "Y", .z: "Z", // Numbers (0-9) .digit0: "0", .digit1: "1", .digit2: "2", .digit3: "3", .digit4: "4", .digit5: "5", .digit6: "6", .digit7: "7", .digit8: "8", .digit9: "9", // Common Control Keys .space: "Space", .enter: "Enter", .tab: "Tab", .backspace: "Backspace", .escape: "Escape", .delete: "Delete", // Arrow Keys .arrowUp: "Up Arrow", .arrowDown: "Down Arrow", .arrowLeft: "Left Arrow", .arrowRight: "Right Arrow", // Navigation Keys .home: "Home", .end: "End", .pageUp: "Page Up", .pageDown: "Page Down", .insert: "Insert", // Function Keys (F1-F20) .f1: "F1", .f2: "F2", .f3: "F3", .f4: "F4", .f5: "F5", .f6: "F6", .f7: "F7", .f8: "F8", .f9: "F9", .f10: "F10", .f11: "F11", .f12: "F12", .f13: "F13", .f14: "F14", .f15: "F15", .f16: "F16", .f17: "F17", .f18: "F18", .f19: "F19", .f20: "F20", // Modifier Keys .shiftLeft: "Left Shift", .shiftRight: "Right Shift", .controlLeft: "Left Control", .controlRight: "Right Control", .altLeft: "Left Alt", .altRight: "Right Alt", .metaLeft: "Left Command", .metaRight: "Right Command", .capsLock: "Caps Lock", // Punctuation & Symbols .minus: "Minus (-)", .equal: "Equal (=)", .backquote: "Backtick (`)", .bracketLeft: "Left Bracket ([)", .bracketRight: "Right Bracket (])", .backslash: "Backslash (\\)", .semicolon: "Semicolon (;)", .quote: "Quote (')", .comma: "Comma (,)", .period: "Period (.)", .slash: "Slash (/)", // Numpad .numLock: "Num Lock", .numpad0: "Numpad 0", .numpad1: "Numpad 1", .numpad2: "Numpad 2", .numpad3: "Numpad 3", .numpad4: "Numpad 4", .numpad5: "Numpad 5", .numpad6: "Numpad 6", .numpad7: "Numpad 7", .numpad8: "Numpad 8", .numpad9: "Numpad 9", .numpadAdd: "Numpad Add (+)", .numpadSubtract: "Numpad Subtract (-)", .numpadMultiply: "Numpad Multiply (×)", .numpadDivide: "Numpad Divide (÷)", .numpadDecimal: "Numpad Decimal", .numpadEqual: "Numpad Equal", .numpadEnter: "Numpad Enter", .numpadComma: "Numpad Comma", // Media Keys .audioVolumeUp: "Volume Up", .audioVolumeDown: "Volume Down", .audioVolumeMute: "Volume Mute", // International Keys .intlBackslash: "International Backslash", .intlRo: "International Ro", .intlYen: "International Yen", // Other .contextMenu: "Context Menu" ] }