Post real modifier key presses around typed keys in hands
This commit is contained in:
@@ -12,10 +12,20 @@ let keyCodes: [String: CGKeyCode] = [
|
|||||||
"forwarddelete": 117, "end": 119, "pagedown": 121, "left": 123, "right": 124, "down": 125, "up": 126,
|
"forwarddelete": 117, "end": 119, "pagedown": 121, "left": 123, "right": 124, "down": 125, "up": 126,
|
||||||
]
|
]
|
||||||
|
|
||||||
let modifierFlags: [String: CGEventFlags] = [
|
struct Modifier: Equatable {
|
||||||
"cmd": .maskCommand, "command": .maskCommand, "shift": .maskShift, "alt": .maskAlternate,
|
let code: CGKeyCode
|
||||||
"opt": .maskAlternate, "option": .maskAlternate, "ctrl": .maskControl, "control": .maskControl,
|
let flag: CGEventFlags
|
||||||
"fn": .maskSecondaryFn,
|
}
|
||||||
|
|
||||||
|
// Each left-hand key's flag also carries its device-dependent bit (NX_DEVICEL*KEYMASK), as a real keyboard's does.
|
||||||
|
let shiftKey = Modifier(code: 56, flag: CGEventFlags(rawValue: CGEventFlags.maskShift.rawValue | 0x2))
|
||||||
|
let commandKey = Modifier(code: 55, flag: CGEventFlags(rawValue: CGEventFlags.maskCommand.rawValue | 0x8))
|
||||||
|
let optionKey = Modifier(code: 58, flag: CGEventFlags(rawValue: CGEventFlags.maskAlternate.rawValue | 0x20))
|
||||||
|
let controlKey = Modifier(code: 59, flag: CGEventFlags(rawValue: CGEventFlags.maskControl.rawValue | 0x1))
|
||||||
|
|
||||||
|
let modifiers: [String: Modifier] = [
|
||||||
|
"cmd": commandKey, "command": commandKey, "shift": shiftKey, "alt": optionKey, "opt": optionKey,
|
||||||
|
"option": optionKey, "ctrl": controlKey, "control": controlKey, "fn": Modifier(code: 63, flag: .maskSecondaryFn),
|
||||||
]
|
]
|
||||||
|
|
||||||
func pause(_ ms: Int) async throws {
|
func pause(_ ms: Int) async throws {
|
||||||
@@ -29,15 +39,15 @@ let shifted: [Character: String] = [
|
|||||||
|
|
||||||
let named: [Character: String] = [" ": "space", "\n": "return", "\t": "tab"]
|
let named: [Character: String] = [" ": "space", "\n": "return", "\t": "tab"]
|
||||||
|
|
||||||
func keystroke(_ ch: Character) -> (CGKeyCode, CGEventFlags)? {
|
func keystroke(_ ch: Character) -> (CGKeyCode, [Modifier])? {
|
||||||
if let name = named[ch], let c = keyCodes[name] {
|
if let name = named[ch], let c = keyCodes[name] {
|
||||||
return (c, [])
|
return (c, [])
|
||||||
}
|
}
|
||||||
if let base = shifted[ch], let c = keyCodes[base] {
|
if let base = shifted[ch], let c = keyCodes[base] {
|
||||||
return (c, .maskShift)
|
return (c, [shiftKey])
|
||||||
}
|
}
|
||||||
if ch.isUppercase, let c = keyCodes[ch.lowercased()] {
|
if ch.isUppercase, let c = keyCodes[ch.lowercased()] {
|
||||||
return (c, .maskShift)
|
return (c, [shiftKey])
|
||||||
}
|
}
|
||||||
if let c = keyCodes[String(ch)] {
|
if let c = keyCodes[String(ch)] {
|
||||||
return (c, [])
|
return (c, [])
|
||||||
@@ -45,6 +55,30 @@ func keystroke(_ ch: Character) -> (CGKeyCode, CGEventFlags)? {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func postModifier(_ m: Modifier, down: Bool, _ flags: CGEventFlags) {
|
||||||
|
let e = CGEvent(keyboardEventSource: nil, virtualKey: m.code, keyDown: down)
|
||||||
|
e?.type = .flagsChanged
|
||||||
|
e?.flags = flags
|
||||||
|
post(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func press(_ code: CGKeyCode, _ mods: [Modifier]) {
|
||||||
|
var flags: CGEventFlags = []
|
||||||
|
for m in mods {
|
||||||
|
flags.insert(m.flag)
|
||||||
|
postModifier(m, down: true, flags)
|
||||||
|
}
|
||||||
|
for down in [true, false] {
|
||||||
|
let e = CGEvent(keyboardEventSource: nil, virtualKey: code, keyDown: down)
|
||||||
|
e?.flags = flags
|
||||||
|
post(e)
|
||||||
|
}
|
||||||
|
for m in mods.reversed() {
|
||||||
|
flags.remove(m.flag)
|
||||||
|
postModifier(m, down: false, flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func lockInput() async throws -> Int32 {
|
func lockInput() async throws -> Int32 {
|
||||||
let fd = open(NSTemporaryDirectory() + "byre-hands-input.lock", O_CREAT | O_RDWR, 0o600)
|
let fd = open(NSTemporaryDirectory() + "byre-hands-input.lock", O_CREAT | O_RDWR, 0o600)
|
||||||
if fd < 0 {
|
if fd < 0 {
|
||||||
@@ -132,13 +166,12 @@ func scroll(_ p: CGPoint, dx: Int32, dy: Int32) {
|
|||||||
|
|
||||||
func type(_ s: String) async throws {
|
func type(_ s: String) async throws {
|
||||||
for ch in s {
|
for ch in s {
|
||||||
|
if let (code, mods) = keystroke(ch) {
|
||||||
|
press(code, mods)
|
||||||
|
try await pause(15)
|
||||||
|
continue
|
||||||
|
}
|
||||||
for down in [true, false] {
|
for down in [true, false] {
|
||||||
if let (code, flags) = keystroke(ch) {
|
|
||||||
let e = CGEvent(keyboardEventSource: nil, virtualKey: code, keyDown: down)
|
|
||||||
e?.flags = flags
|
|
||||||
post(e)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let units = Array(String(ch).utf16)
|
let units = Array(String(ch).utf16)
|
||||||
let e = CGEvent(keyboardEventSource: nil, virtualKey: 0, keyDown: down)
|
let e = CGEvent(keyboardEventSource: nil, virtualKey: 0, keyDown: down)
|
||||||
e?.keyboardSetUnicodeString(stringLength: units.count, unicodeString: units)
|
e?.keyboardSetUnicodeString(stringLength: units.count, unicodeString: units)
|
||||||
@@ -149,11 +182,13 @@ func type(_ s: String) async throws {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func key(_ combo: String) throws {
|
func key(_ combo: String) throws {
|
||||||
var flags: CGEventFlags = []
|
var mods: [Modifier] = []
|
||||||
var code: CGKeyCode?
|
var code: CGKeyCode?
|
||||||
for part in combo.lowercased().split(separator: "+").map(String.init) {
|
for part in combo.lowercased().split(separator: "+").map(String.init) {
|
||||||
if let f = modifierFlags[part] {
|
if let m = modifiers[part] {
|
||||||
flags.insert(f)
|
if !mods.contains(m) {
|
||||||
|
mods.append(m)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
guard let c = keyCodes[part] else {
|
guard let c = keyCodes[part] else {
|
||||||
@@ -164,9 +199,5 @@ func key(_ combo: String) throws {
|
|||||||
guard let code else {
|
guard let code else {
|
||||||
throw ToolError("no key in \(combo.debugDescription)")
|
throw ToolError("no key in \(combo.debugDescription)")
|
||||||
}
|
}
|
||||||
for down in [true, false] {
|
press(code, mods)
|
||||||
let e = CGEvent(keyboardEventSource: nil, virtualKey: code, keyDown: down)
|
|
||||||
e?.flags = flags
|
|
||||||
post(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user