macos: input keyboard event can send modifiers and actions now
parent
93619ad420
commit
71b6e223af
|
|
@ -44,10 +44,25 @@ struct KeyEventIntent: AppIntent {
|
||||||
static var description = IntentDescription("Simulate a keyboard event. This will not handle text encoding; use the 'Input Text' action for that.")
|
static var description = IntentDescription("Simulate a keyboard event. This will not handle text encoding; use the 'Input Text' action for that.")
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
title: "Text",
|
title: "Key",
|
||||||
description: "The key to send to the terminal."
|
description: "The key to send to the terminal.",
|
||||||
|
default: .enter
|
||||||
)
|
)
|
||||||
var key: Ghostty.Key
|
var key: Ghostty.Input.Key
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
title: "Modifier(s)",
|
||||||
|
description: "The modifiers to send with the key event.",
|
||||||
|
default: []
|
||||||
|
)
|
||||||
|
var mods: [KeyEventMods]
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
title: "Event Type",
|
||||||
|
description: "A key press or release.",
|
||||||
|
default: .press
|
||||||
|
)
|
||||||
|
var action: Ghostty.Input.Action
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
title: "Terminal",
|
title: "Terminal",
|
||||||
|
|
@ -64,6 +79,45 @@ struct KeyEventIntent: AppIntent {
|
||||||
throw GhosttyIntentError.surfaceNotFound
|
throw GhosttyIntentError.surfaceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert KeyEventMods array to Ghostty.Input.Mods
|
||||||
|
let ghosttyMods = mods.reduce(Ghostty.Input.Mods()) { result, mod in
|
||||||
|
result.union(mod.ghosttyMod)
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyEvent = Ghostty.Input.KeyEvent(
|
||||||
|
key: key,
|
||||||
|
action: action,
|
||||||
|
mods: ghosttyMods
|
||||||
|
)
|
||||||
|
surface.sendKeyEvent(keyEvent)
|
||||||
|
|
||||||
return .result()
|
return .result()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Mods
|
||||||
|
|
||||||
|
enum KeyEventMods: String, AppEnum, CaseIterable {
|
||||||
|
case shift
|
||||||
|
case control
|
||||||
|
case option
|
||||||
|
case command
|
||||||
|
|
||||||
|
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Modifier Key")
|
||||||
|
|
||||||
|
static var caseDisplayRepresentations: [KeyEventMods : DisplayRepresentation] = [
|
||||||
|
.shift: "Shift",
|
||||||
|
.control: "Control",
|
||||||
|
.option: "Option",
|
||||||
|
.command: "Command"
|
||||||
|
]
|
||||||
|
|
||||||
|
var ghosttyMod: Ghostty.Input.Mods {
|
||||||
|
switch self {
|
||||||
|
case .shift: .shift
|
||||||
|
case .control: .ctrl
|
||||||
|
case .option: .alt
|
||||||
|
case .command: .super
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import SwiftUI
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
|
struct Input {}
|
||||||
|
|
||||||
// MARK: Keyboard Shortcuts
|
// MARK: Keyboard Shortcuts
|
||||||
|
|
||||||
/// Return the key equivalent for the given trigger.
|
/// Return the key equivalent for the given trigger.
|
||||||
|
|
@ -92,7 +94,175 @@ extension Ghostty {
|
||||||
GHOSTTY_KEY_BACKSPACE: .delete,
|
GHOSTTY_KEY_BACKSPACE: .delete,
|
||||||
GHOSTTY_KEY_SPACE: .space,
|
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<T>(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.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`
|
/// `ghostty_input_key_e`
|
||||||
enum Key: String {
|
enum Key: String {
|
||||||
// Writing System Keys
|
// Writing System Keys
|
||||||
|
|
@ -146,7 +316,7 @@ extension Ghostty {
|
||||||
case quote
|
case quote
|
||||||
case semicolon
|
case semicolon
|
||||||
case slash
|
case slash
|
||||||
|
|
||||||
// Functional Keys
|
// Functional Keys
|
||||||
case altLeft
|
case altLeft
|
||||||
case altRight
|
case altRight
|
||||||
|
|
@ -165,7 +335,7 @@ extension Ghostty {
|
||||||
case convert
|
case convert
|
||||||
case kanaMode
|
case kanaMode
|
||||||
case nonConvert
|
case nonConvert
|
||||||
|
|
||||||
// Control Pad Section
|
// Control Pad Section
|
||||||
case delete
|
case delete
|
||||||
case end
|
case end
|
||||||
|
|
@ -174,13 +344,13 @@ extension Ghostty {
|
||||||
case insert
|
case insert
|
||||||
case pageDown
|
case pageDown
|
||||||
case pageUp
|
case pageUp
|
||||||
|
|
||||||
// Arrow Pad Section
|
// Arrow Pad Section
|
||||||
case arrowDown
|
case arrowDown
|
||||||
case arrowLeft
|
case arrowLeft
|
||||||
case arrowRight
|
case arrowRight
|
||||||
case arrowUp
|
case arrowUp
|
||||||
|
|
||||||
// Numpad Section
|
// Numpad Section
|
||||||
case numLock
|
case numLock
|
||||||
case numpad0
|
case numpad0
|
||||||
|
|
@ -223,7 +393,7 @@ extension Ghostty {
|
||||||
case numpadDelete
|
case numpadDelete
|
||||||
case numpadPageUp
|
case numpadPageUp
|
||||||
case numpadPageDown
|
case numpadPageDown
|
||||||
|
|
||||||
// Function Section
|
// Function Section
|
||||||
case escape
|
case escape
|
||||||
case f1
|
case f1
|
||||||
|
|
@ -256,7 +426,7 @@ extension Ghostty {
|
||||||
case printScreen
|
case printScreen
|
||||||
case scrollLock
|
case scrollLock
|
||||||
case pause
|
case pause
|
||||||
|
|
||||||
// Media Keys
|
// Media Keys
|
||||||
case browserBack
|
case browserBack
|
||||||
case browserFavorites
|
case browserFavorites
|
||||||
|
|
@ -280,7 +450,7 @@ extension Ghostty {
|
||||||
case audioVolumeMute
|
case audioVolumeMute
|
||||||
case audioVolumeUp
|
case audioVolumeUp
|
||||||
case wakeUp
|
case wakeUp
|
||||||
|
|
||||||
// Legacy, Non-standard, and Special Keys
|
// Legacy, Non-standard, and Special Keys
|
||||||
case copy
|
case copy
|
||||||
case cut
|
case cut
|
||||||
|
|
@ -349,7 +519,7 @@ extension Ghostty {
|
||||||
case .quote: GHOSTTY_KEY_QUOTE
|
case .quote: GHOSTTY_KEY_QUOTE
|
||||||
case .semicolon: GHOSTTY_KEY_SEMICOLON
|
case .semicolon: GHOSTTY_KEY_SEMICOLON
|
||||||
case .slash: GHOSTTY_KEY_SLASH
|
case .slash: GHOSTTY_KEY_SLASH
|
||||||
|
|
||||||
// Functional Keys
|
// Functional Keys
|
||||||
case .altLeft: GHOSTTY_KEY_ALT_LEFT
|
case .altLeft: GHOSTTY_KEY_ALT_LEFT
|
||||||
case .altRight: GHOSTTY_KEY_ALT_RIGHT
|
case .altRight: GHOSTTY_KEY_ALT_RIGHT
|
||||||
|
|
@ -368,7 +538,7 @@ extension Ghostty {
|
||||||
case .convert: GHOSTTY_KEY_CONVERT
|
case .convert: GHOSTTY_KEY_CONVERT
|
||||||
case .kanaMode: GHOSTTY_KEY_KANA_MODE
|
case .kanaMode: GHOSTTY_KEY_KANA_MODE
|
||||||
case .nonConvert: GHOSTTY_KEY_NON_CONVERT
|
case .nonConvert: GHOSTTY_KEY_NON_CONVERT
|
||||||
|
|
||||||
// Control Pad Section
|
// Control Pad Section
|
||||||
case .delete: GHOSTTY_KEY_DELETE
|
case .delete: GHOSTTY_KEY_DELETE
|
||||||
case .end: GHOSTTY_KEY_END
|
case .end: GHOSTTY_KEY_END
|
||||||
|
|
@ -377,13 +547,13 @@ extension Ghostty {
|
||||||
case .insert: GHOSTTY_KEY_INSERT
|
case .insert: GHOSTTY_KEY_INSERT
|
||||||
case .pageDown: GHOSTTY_KEY_PAGE_DOWN
|
case .pageDown: GHOSTTY_KEY_PAGE_DOWN
|
||||||
case .pageUp: GHOSTTY_KEY_PAGE_UP
|
case .pageUp: GHOSTTY_KEY_PAGE_UP
|
||||||
|
|
||||||
// Arrow Pad Section
|
// Arrow Pad Section
|
||||||
case .arrowDown: GHOSTTY_KEY_ARROW_DOWN
|
case .arrowDown: GHOSTTY_KEY_ARROW_DOWN
|
||||||
case .arrowLeft: GHOSTTY_KEY_ARROW_LEFT
|
case .arrowLeft: GHOSTTY_KEY_ARROW_LEFT
|
||||||
case .arrowRight: GHOSTTY_KEY_ARROW_RIGHT
|
case .arrowRight: GHOSTTY_KEY_ARROW_RIGHT
|
||||||
case .arrowUp: GHOSTTY_KEY_ARROW_UP
|
case .arrowUp: GHOSTTY_KEY_ARROW_UP
|
||||||
|
|
||||||
// Numpad Section
|
// Numpad Section
|
||||||
case .numLock: GHOSTTY_KEY_NUM_LOCK
|
case .numLock: GHOSTTY_KEY_NUM_LOCK
|
||||||
case .numpad0: GHOSTTY_KEY_NUMPAD_0
|
case .numpad0: GHOSTTY_KEY_NUMPAD_0
|
||||||
|
|
@ -426,7 +596,7 @@ extension Ghostty {
|
||||||
case .numpadDelete: GHOSTTY_KEY_NUMPAD_DELETE
|
case .numpadDelete: GHOSTTY_KEY_NUMPAD_DELETE
|
||||||
case .numpadPageUp: GHOSTTY_KEY_NUMPAD_PAGE_UP
|
case .numpadPageUp: GHOSTTY_KEY_NUMPAD_PAGE_UP
|
||||||
case .numpadPageDown: GHOSTTY_KEY_NUMPAD_PAGE_DOWN
|
case .numpadPageDown: GHOSTTY_KEY_NUMPAD_PAGE_DOWN
|
||||||
|
|
||||||
// Function Section
|
// Function Section
|
||||||
case .escape: GHOSTTY_KEY_ESCAPE
|
case .escape: GHOSTTY_KEY_ESCAPE
|
||||||
case .f1: GHOSTTY_KEY_F1
|
case .f1: GHOSTTY_KEY_F1
|
||||||
|
|
@ -459,7 +629,7 @@ extension Ghostty {
|
||||||
case .printScreen: GHOSTTY_KEY_PRINT_SCREEN
|
case .printScreen: GHOSTTY_KEY_PRINT_SCREEN
|
||||||
case .scrollLock: GHOSTTY_KEY_SCROLL_LOCK
|
case .scrollLock: GHOSTTY_KEY_SCROLL_LOCK
|
||||||
case .pause: GHOSTTY_KEY_PAUSE
|
case .pause: GHOSTTY_KEY_PAUSE
|
||||||
|
|
||||||
// Media Keys
|
// Media Keys
|
||||||
case .browserBack: GHOSTTY_KEY_BROWSER_BACK
|
case .browserBack: GHOSTTY_KEY_BROWSER_BACK
|
||||||
case .browserFavorites: GHOSTTY_KEY_BROWSER_FAVORITES
|
case .browserFavorites: GHOSTTY_KEY_BROWSER_FAVORITES
|
||||||
|
|
@ -483,7 +653,7 @@ extension Ghostty {
|
||||||
case .audioVolumeMute: GHOSTTY_KEY_AUDIO_VOLUME_MUTE
|
case .audioVolumeMute: GHOSTTY_KEY_AUDIO_VOLUME_MUTE
|
||||||
case .audioVolumeUp: GHOSTTY_KEY_AUDIO_VOLUME_UP
|
case .audioVolumeUp: GHOSTTY_KEY_AUDIO_VOLUME_UP
|
||||||
case .wakeUp: GHOSTTY_KEY_WAKE_UP
|
case .wakeUp: GHOSTTY_KEY_WAKE_UP
|
||||||
|
|
||||||
// Legacy, Non-standard, and Special Keys
|
// Legacy, Non-standard, and Special Keys
|
||||||
case .copy: GHOSTTY_KEY_COPY
|
case .copy: GHOSTTY_KEY_COPY
|
||||||
case .cut: GHOSTTY_KEY_CUT
|
case .cut: GHOSTTY_KEY_CUT
|
||||||
|
|
@ -545,7 +715,7 @@ extension Ghostty {
|
||||||
case .quote: return 0x0027
|
case .quote: return 0x0027
|
||||||
case .semicolon: return 0x0029
|
case .semicolon: return 0x0029
|
||||||
case .slash: return 0x002c
|
case .slash: return 0x002c
|
||||||
|
|
||||||
// Functional Keys
|
// Functional Keys
|
||||||
case .altLeft: return 0x003a
|
case .altLeft: return 0x003a
|
||||||
case .altRight: return 0x003d
|
case .altRight: return 0x003d
|
||||||
|
|
@ -564,7 +734,7 @@ extension Ghostty {
|
||||||
case .convert: return nil // No Mac keycode
|
case .convert: return nil // No Mac keycode
|
||||||
case .kanaMode: return nil // No Mac keycode
|
case .kanaMode: return nil // No Mac keycode
|
||||||
case .nonConvert: return nil // No Mac keycode
|
case .nonConvert: return nil // No Mac keycode
|
||||||
|
|
||||||
// Control Pad Section
|
// Control Pad Section
|
||||||
case .delete: return 0x0075
|
case .delete: return 0x0075
|
||||||
case .end: return 0x0077
|
case .end: return 0x0077
|
||||||
|
|
@ -573,13 +743,13 @@ extension Ghostty {
|
||||||
case .insert: return 0x0072
|
case .insert: return 0x0072
|
||||||
case .pageDown: return 0x0079
|
case .pageDown: return 0x0079
|
||||||
case .pageUp: return 0x0074
|
case .pageUp: return 0x0074
|
||||||
|
|
||||||
// Arrow Pad Section
|
// Arrow Pad Section
|
||||||
case .arrowDown: return 0x007d
|
case .arrowDown: return 0x007d
|
||||||
case .arrowLeft: return 0x007b
|
case .arrowLeft: return 0x007b
|
||||||
case .arrowRight: return 0x007c
|
case .arrowRight: return 0x007c
|
||||||
case .arrowUp: return 0x007e
|
case .arrowUp: return 0x007e
|
||||||
|
|
||||||
// Numpad Section
|
// Numpad Section
|
||||||
case .numLock: return 0x0047
|
case .numLock: return 0x0047
|
||||||
case .numpad0: return 0x0052
|
case .numpad0: return 0x0052
|
||||||
|
|
@ -622,7 +792,7 @@ extension Ghostty {
|
||||||
case .numpadDelete: return nil // No Mac keycode
|
case .numpadDelete: return nil // No Mac keycode
|
||||||
case .numpadPageUp: return nil // No Mac keycode
|
case .numpadPageUp: return nil // No Mac keycode
|
||||||
case .numpadPageDown: return nil // No Mac keycode
|
case .numpadPageDown: return nil // No Mac keycode
|
||||||
|
|
||||||
// Function Section
|
// Function Section
|
||||||
case .escape: return 0x0035
|
case .escape: return 0x0035
|
||||||
case .f1: return 0x007a
|
case .f1: return 0x007a
|
||||||
|
|
@ -655,7 +825,7 @@ extension Ghostty {
|
||||||
case .printScreen: return nil // No Mac keycode
|
case .printScreen: return nil // No Mac keycode
|
||||||
case .scrollLock: return nil // No Mac keycode
|
case .scrollLock: return nil // No Mac keycode
|
||||||
case .pause: return nil // No Mac keycode
|
case .pause: return nil // No Mac keycode
|
||||||
|
|
||||||
// Media Keys
|
// Media Keys
|
||||||
case .browserBack: return nil // No Mac keycode
|
case .browserBack: return nil // No Mac keycode
|
||||||
case .browserFavorites: return nil // No Mac keycode
|
case .browserFavorites: return nil // No Mac keycode
|
||||||
|
|
@ -679,7 +849,7 @@ extension Ghostty {
|
||||||
case .audioVolumeMute: return 0x004a
|
case .audioVolumeMute: return 0x004a
|
||||||
case .audioVolumeUp: return 0x0048
|
case .audioVolumeUp: return 0x0048
|
||||||
case .wakeUp: return nil // No Mac keycode
|
case .wakeUp: return nil // No Mac keycode
|
||||||
|
|
||||||
// Legacy, Non-standard, and Special Keys
|
// Legacy, Non-standard, and Special Keys
|
||||||
case .copy: return nil // No Mac keycode
|
case .copy: return nil // No Mac keycode
|
||||||
case .cut: return nil // No Mac keycode
|
case .cut: return nil // No Mac keycode
|
||||||
|
|
@ -689,201 +859,142 @@ extension Ghostty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Ghostty.Key AppEnum
|
extension Ghostty.Input.Key: AppEnum {
|
||||||
|
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Key")
|
||||||
|
|
||||||
extension Ghostty.Key: AppEnum {
|
// Only include keys that have Mac keycodes for App Intents
|
||||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Key"
|
static var allCases: [Ghostty.Input.Key] {
|
||||||
|
return [
|
||||||
static var caseDisplayRepresentations: [Ghostty.Key : DisplayRepresentation] = [
|
// Letters (A-Z)
|
||||||
// Writing System Keys
|
.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,
|
||||||
.backquote: "Backtick (`)",
|
|
||||||
.backslash: "Backslash (\\)",
|
// Numbers (0-9)
|
||||||
.bracketLeft: "Left Bracket ([)",
|
.digit0, .digit1, .digit2, .digit3, .digit4, .digit5, .digit6, .digit7, .digit8, .digit9,
|
||||||
.bracketRight: "Right Bracket (])",
|
|
||||||
.comma: "Comma (,)",
|
// Common Control Keys
|
||||||
.digit0: "0",
|
.space, .enter, .tab, .backspace, .escape, .delete,
|
||||||
.digit1: "1",
|
|
||||||
.digit2: "2",
|
// Arrow Keys
|
||||||
.digit3: "3",
|
.arrowUp, .arrowDown, .arrowLeft, .arrowRight,
|
||||||
.digit4: "4",
|
|
||||||
.digit5: "5",
|
// Navigation Keys
|
||||||
.digit6: "6",
|
.home, .end, .pageUp, .pageDown, .insert,
|
||||||
.digit7: "7",
|
|
||||||
.digit8: "8",
|
// Function Keys (F1-F20)
|
||||||
.digit9: "9",
|
.f1, .f2, .f3, .f4, .f5, .f6, .f7, .f8, .f9, .f10, .f11, .f12,
|
||||||
.equal: "Equal (=)",
|
.f13, .f14, .f15, .f16, .f17, .f18, .f19, .f20,
|
||||||
.intlBackslash: "International Backslash",
|
|
||||||
.intlRo: "International Ro",
|
// Modifier Keys
|
||||||
.intlYen: "International Yen",
|
.shiftLeft, .shiftRight, .controlLeft, .controlRight, .altLeft, .altRight,
|
||||||
.a: "A",
|
.metaLeft, .metaRight, .capsLock,
|
||||||
.b: "B",
|
|
||||||
.c: "C",
|
// Punctuation & Symbols
|
||||||
.d: "D",
|
.minus, .equal, .backquote, .bracketLeft, .bracketRight, .backslash,
|
||||||
.e: "E",
|
.semicolon, .quote, .comma, .period, .slash,
|
||||||
.f: "F",
|
|
||||||
.g: "G",
|
// Numpad
|
||||||
.h: "H",
|
.numLock, .numpad0, .numpad1, .numpad2, .numpad3, .numpad4, .numpad5,
|
||||||
.i: "I",
|
.numpad6, .numpad7, .numpad8, .numpad9, .numpadAdd, .numpadSubtract,
|
||||||
.j: "J",
|
.numpadMultiply, .numpadDivide, .numpadDecimal, .numpadEqual,
|
||||||
.k: "K",
|
.numpadEnter, .numpadComma,
|
||||||
.l: "L",
|
|
||||||
.m: "M",
|
// Media Keys
|
||||||
.n: "N",
|
.audioVolumeUp, .audioVolumeDown, .audioVolumeMute,
|
||||||
.o: "O",
|
|
||||||
.p: "P",
|
// International Keys
|
||||||
.q: "Q",
|
.intlBackslash, .intlRo, .intlYen,
|
||||||
.r: "R",
|
|
||||||
.s: "S",
|
// Other
|
||||||
.t: "T",
|
.contextMenu
|
||||||
.u: "U",
|
]
|
||||||
.v: "V",
|
}
|
||||||
.w: "W",
|
|
||||||
.x: "X",
|
static var caseDisplayRepresentations: [Ghostty.Input.Key : DisplayRepresentation] = [
|
||||||
.y: "Y",
|
// Letters (A-Z)
|
||||||
.z: "Z",
|
.a: "A", .b: "B", .c: "C", .d: "D", .e: "E", .f: "F", .g: "G", .h: "H", .i: "I", .j: "J",
|
||||||
.minus: "Minus (-)",
|
.k: "K", .l: "L", .m: "M", .n: "N", .o: "O", .p: "P", .q: "Q", .r: "R", .s: "S", .t: "T",
|
||||||
.period: "Period (.)",
|
.u: "U", .v: "V", .w: "W", .x: "X", .y: "Y", .z: "Z",
|
||||||
.quote: "Quote (')",
|
|
||||||
.semicolon: "Semicolon (;)",
|
|
||||||
.slash: "Slash (/)",
|
|
||||||
|
|
||||||
// Functional Keys
|
// Numbers (0-9)
|
||||||
.altLeft: "Left Alt",
|
.digit0: "0", .digit1: "1", .digit2: "2", .digit3: "3", .digit4: "4",
|
||||||
.altRight: "Right Alt",
|
.digit5: "5", .digit6: "6", .digit7: "7", .digit8: "8", .digit9: "9",
|
||||||
.backspace: "Backspace",
|
|
||||||
.capsLock: "Caps Lock",
|
// Common Control Keys
|
||||||
.contextMenu: "Context Menu",
|
|
||||||
.controlLeft: "Left Control",
|
|
||||||
.controlRight: "Right Control",
|
|
||||||
.enter: "Enter",
|
|
||||||
.metaLeft: "Left Command",
|
|
||||||
.metaRight: "Right Command",
|
|
||||||
.shiftLeft: "Left Shift",
|
|
||||||
.shiftRight: "Right Shift",
|
|
||||||
.space: "Space",
|
.space: "Space",
|
||||||
|
.enter: "Enter",
|
||||||
.tab: "Tab",
|
.tab: "Tab",
|
||||||
.convert: "Convert",
|
.backspace: "Backspace",
|
||||||
.kanaMode: "Kana Mode",
|
.escape: "Escape",
|
||||||
.nonConvert: "Non Convert",
|
|
||||||
|
|
||||||
// Control Pad Section
|
|
||||||
.delete: "Delete",
|
.delete: "Delete",
|
||||||
.end: "End",
|
|
||||||
.help: "Help",
|
|
||||||
.home: "Home",
|
|
||||||
.insert: "Insert",
|
|
||||||
.pageDown: "Page Down",
|
|
||||||
.pageUp: "Page Up",
|
|
||||||
|
|
||||||
// Arrow Pad Section
|
// Arrow Keys
|
||||||
|
.arrowUp: "Up Arrow",
|
||||||
.arrowDown: "Down Arrow",
|
.arrowDown: "Down Arrow",
|
||||||
.arrowLeft: "Left Arrow",
|
.arrowLeft: "Left Arrow",
|
||||||
.arrowRight: "Right Arrow",
|
.arrowRight: "Right Arrow",
|
||||||
.arrowUp: "Up Arrow",
|
|
||||||
|
|
||||||
// Numpad Section
|
// 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",
|
.numLock: "Num Lock",
|
||||||
.numpad0: "Numpad 0",
|
.numpad0: "Numpad 0", .numpad1: "Numpad 1", .numpad2: "Numpad 2",
|
||||||
.numpad1: "Numpad 1",
|
.numpad3: "Numpad 3", .numpad4: "Numpad 4", .numpad5: "Numpad 5",
|
||||||
.numpad2: "Numpad 2",
|
.numpad6: "Numpad 6", .numpad7: "Numpad 7", .numpad8: "Numpad 8", .numpad9: "Numpad 9",
|
||||||
.numpad3: "Numpad 3",
|
|
||||||
.numpad4: "Numpad 4",
|
|
||||||
.numpad5: "Numpad 5",
|
|
||||||
.numpad6: "Numpad 6",
|
|
||||||
.numpad7: "Numpad 7",
|
|
||||||
.numpad8: "Numpad 8",
|
|
||||||
.numpad9: "Numpad 9",
|
|
||||||
.numpadAdd: "Numpad Add (+)",
|
.numpadAdd: "Numpad Add (+)",
|
||||||
.numpadBackspace: "Numpad Backspace",
|
|
||||||
.numpadClear: "Numpad Clear",
|
|
||||||
.numpadClearEntry: "Numpad Clear Entry",
|
|
||||||
.numpadComma: "Numpad Comma",
|
|
||||||
.numpadDecimal: "Numpad Decimal",
|
|
||||||
.numpadDivide: "Numpad Divide (÷)",
|
|
||||||
.numpadEnter: "Numpad Enter",
|
|
||||||
.numpadEqual: "Numpad Equal",
|
|
||||||
.numpadMemoryAdd: "Numpad Memory Add",
|
|
||||||
.numpadMemoryClear: "Numpad Memory Clear",
|
|
||||||
.numpadMemoryRecall: "Numpad Memory Recall",
|
|
||||||
.numpadMemoryStore: "Numpad Memory Store",
|
|
||||||
.numpadMemorySubtract: "Numpad Memory Subtract",
|
|
||||||
.numpadMultiply: "Numpad Multiply (×)",
|
|
||||||
.numpadParenLeft: "Numpad Left Parenthesis",
|
|
||||||
.numpadParenRight: "Numpad Right Parenthesis",
|
|
||||||
.numpadSubtract: "Numpad Subtract (-)",
|
.numpadSubtract: "Numpad Subtract (-)",
|
||||||
.numpadSeparator: "Numpad Separator",
|
.numpadMultiply: "Numpad Multiply (×)",
|
||||||
.numpadUp: "Numpad Up",
|
.numpadDivide: "Numpad Divide (÷)",
|
||||||
.numpadDown: "Numpad Down",
|
.numpadDecimal: "Numpad Decimal",
|
||||||
.numpadRight: "Numpad Right",
|
.numpadEqual: "Numpad Equal",
|
||||||
.numpadLeft: "Numpad Left",
|
.numpadEnter: "Numpad Enter",
|
||||||
.numpadBegin: "Numpad Begin",
|
.numpadComma: "Numpad Comma",
|
||||||
.numpadHome: "Numpad Home",
|
|
||||||
.numpadEnd: "Numpad End",
|
|
||||||
.numpadInsert: "Numpad Insert",
|
|
||||||
.numpadDelete: "Numpad Delete",
|
|
||||||
.numpadPageUp: "Numpad Page Up",
|
|
||||||
.numpadPageDown: "Numpad Page Down",
|
|
||||||
|
|
||||||
// Function Section
|
|
||||||
.escape: "Escape",
|
|
||||||
.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",
|
|
||||||
.f21: "F21",
|
|
||||||
.f22: "F22",
|
|
||||||
.f23: "F23",
|
|
||||||
.f24: "F24",
|
|
||||||
.f25: "F25",
|
|
||||||
.fn: "Fn",
|
|
||||||
.fnLock: "Fn Lock",
|
|
||||||
.printScreen: "Print Screen",
|
|
||||||
.scrollLock: "Scroll Lock",
|
|
||||||
.pause: "Pause",
|
|
||||||
|
|
||||||
// Media Keys
|
// Media Keys
|
||||||
.browserBack: "Browser Back",
|
.audioVolumeUp: "Volume Up",
|
||||||
.browserFavorites: "Browser Favorites",
|
|
||||||
.browserForward: "Browser Forward",
|
|
||||||
.browserHome: "Browser Home",
|
|
||||||
.browserRefresh: "Browser Refresh",
|
|
||||||
.browserSearch: "Browser Search",
|
|
||||||
.browserStop: "Browser Stop",
|
|
||||||
.eject: "Eject",
|
|
||||||
.launchApp1: "Launch App 1",
|
|
||||||
.launchApp2: "Launch App 2",
|
|
||||||
.launchMail: "Launch Mail",
|
|
||||||
.mediaPlayPause: "Media Play/Pause",
|
|
||||||
.mediaSelect: "Media Select",
|
|
||||||
.mediaStop: "Media Stop",
|
|
||||||
.mediaTrackNext: "Media Next Track",
|
|
||||||
.mediaTrackPrevious: "Media Previous Track",
|
|
||||||
.power: "Power",
|
|
||||||
.sleep: "Sleep",
|
|
||||||
.audioVolumeDown: "Volume Down",
|
.audioVolumeDown: "Volume Down",
|
||||||
.audioVolumeMute: "Volume Mute",
|
.audioVolumeMute: "Volume Mute",
|
||||||
.audioVolumeUp: "Volume Up",
|
|
||||||
.wakeUp: "Wake Up",
|
|
||||||
|
|
||||||
// Legacy, Non-standard, and Special Keys
|
// International Keys
|
||||||
.copy: "Copy",
|
.intlBackslash: "International Backslash",
|
||||||
.cut: "Cut",
|
.intlRo: "International Ro",
|
||||||
.paste: "Paste"
|
.intlYen: "International Yen",
|
||||||
|
|
||||||
|
// Other
|
||||||
|
.contextMenu: "Context Menu"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,20 @@ extension Ghostty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a key event to the terminal.
|
||||||
|
///
|
||||||
|
/// This sends the full key event including modifiers, action type, and text to the terminal.
|
||||||
|
/// Unlike `sendText`, this method processes keyboard shortcuts, key bindings, and terminal
|
||||||
|
/// encoding based on the complete key event information.
|
||||||
|
///
|
||||||
|
/// - Parameter event: The key event to send to the terminal
|
||||||
|
@MainActor
|
||||||
|
func sendKeyEvent(_ event: Input.KeyEvent) {
|
||||||
|
event.withCValue { cEvent in
|
||||||
|
ghostty_surface_key(surface, cEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform a keybinding action.
|
/// Perform a keybinding action.
|
||||||
///
|
///
|
||||||
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`
|
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ extension Ghostty {
|
||||||
|
|
||||||
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
|
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
|
||||||
guard let inspector = self.inspector else { return }
|
guard let inspector = self.inspector else { return }
|
||||||
guard let key = Ghostty.Key(keyCode: event.keyCode) else { return }
|
guard let key = Ghostty.Input.Key(keyCode: event.keyCode) else { return }
|
||||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
||||||
ghostty_inspector_key(inspector, action, key.cKey, mods)
|
ghostty_inspector_key(inspector, action, key.cKey, mods)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue