mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-20 20:48:06 +00:00
Translate printable physical keybindings through the current macOS keyboard layout before assigning menu key equivalents. Previously these bindings could not be represented because SwiftUI shortcuts are character-based, so actions such as super+backquote had no native menu shortcut. Keep native keycodes as dispatch identity so translated display characters do not change physical semantics or precedence over Unicode bindings. Refresh shortcuts when the input source changes, and prevent AppKit from transforming equivalents that are already localized.
58 lines
2.1 KiB
Swift
58 lines
2.1 KiB
Swift
import AppKit
|
|
import Carbon
|
|
|
|
class KeyboardLayout {
|
|
/// Return a string ID of the current keyboard input source.
|
|
static var id: String? {
|
|
if let source = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue(),
|
|
let sourceIdPointer = TISGetInputSourceProperty(source, kTISPropertyInputSourceID) {
|
|
let sourceId = unsafeBitCast(sourceIdPointer, to: CFString.self)
|
|
return sourceId as String
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/// 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(
|
|
for keyCode: UInt16,
|
|
modifiers: NSEvent.ModifierFlags
|
|
) -> Character? {
|
|
guard
|
|
let source = TISCopyCurrentKeyboardLayoutInputSource()?.takeRetainedValue(),
|
|
let dataPointer = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)
|
|
else { return nil }
|
|
|
|
let data = unsafeBitCast(dataPointer, to: CFData.self)
|
|
guard let bytes = CFDataGetBytePtr(data) else { return nil }
|
|
|
|
// Command can select a distinct layout table. Other modifiers remain
|
|
// separate in the menu's modifier mask and must not affect this character.
|
|
let carbonModifiers = modifiers.contains(.command) ? UInt32(cmdKey) >> 8 : 0
|
|
|
|
var deadKeyState: UInt32 = 0
|
|
var characters = [UniChar](repeating: 0, count: 4)
|
|
var length = 0
|
|
let status = bytes.withMemoryRebound(to: UCKeyboardLayout.self, capacity: 1) { layout in
|
|
UCKeyTranslate(
|
|
layout,
|
|
keyCode,
|
|
UInt16(kUCKeyActionDisplay),
|
|
carbonModifiers,
|
|
UInt32(LMGetKbdType()),
|
|
UInt32(kUCKeyTranslateNoDeadKeysMask),
|
|
&deadKeyState,
|
|
characters.count,
|
|
&length,
|
|
&characters)
|
|
}
|
|
guard status == noErr else { return nil }
|
|
|
|
let result = String(utf16CodeUnits: characters, count: length)
|
|
guard result.count == 1 else { return nil }
|
|
return result.first
|
|
}
|
|
}
|