I'm building a swiftUI app on mac and I was wondering if there was a way to simulate keyboard shortcuts while the app is running, or in the background. My goal with this app is to call on shortcuts when my server sends me the corresponding gesture information. I was looking at using NSEvent.addGlobalMonitorForEvents(matching: .keyDown, handler: self.handler)
but I think that was more reprograming shortcuts rather than just calling on them.
For example if my server sends me a string that says "Paste", how would I call on the command paste shorcut no matter what screen i'm on, as long as my app is running. Any help or information would be appreciated!
CodePudding user response:
I had this playground sitting on my Mac, so I figured I'd share it here in case it's helpful:
import Cocoa
let src = CGEventSource(stateID: .hidSystemState)
let down = CGEvent(keyboardEventSource: src, virtualKey: 0x12, keyDown: true)!
let up = CGEvent(keyboardEventSource: src, virtualKey: 0x12, keyDown: false)!
sleep(5)
////down?.post(tap: .cghidEventTap)
////up?.post(tap: .cghidEventTap)
////down?.flags = .maskCommand
//
//let cmd_d = CGEvent(keyboardEventSource: src, virtualKey: 0x37, keyDown: true)!
//let cmd_u = CGEvent(keyboardEventSource: src, virtualKey: 0x37, keyDown: false)!
//let a_d = CGEvent(keyboardEventSource: src, virtualKey: 0x00, keyDown: true)!
//let a_u = CGEvent(keyboardEventSource: src, virtualKey: 0x00, keyDown: false)!
//let delete_d = CGEvent(keyboardEventSource: src, virtualKey: 0x33, keyDown: true)!
//let delete_u = CGEvent(keyboardEventSource: src, virtualKey: 0x33, keyDown: false)!
//
//down.post(tap: .cghidEventTap)
//up.post(tap: .cghidEventTap)
//sleep(1)
////cmd_d.post(tap: .cghidEventTap)
//a_d.flags = .maskCommand
//a_u.flags = .maskCommand
//a_d.post(tap: .cghidEventTap)
//a_u.post(tap: .cghidEventTap)
////cmd_u.post(tap: .cghidEventTap)
//sleep(1)
//delete_d.post(tap: .cghidEventTap)
//delete_u.post(tap: .cghidEventTap)
enum KeyCode: UInt16 {
// https://gist.github.com/swillits/df648e87016772c7f7e5dbed2b345066
// Layout-independent Keys
// eg.These key codes are always the same key on all layouts.
case returnKey = 0x24
// case enter = 0x4C //0x24
case tab = 0x30
case space = 0x31
case delete = 0x33
case escape = 0x35
case command = 0x37
case shift = 0x38
case capsLock = 0x39
case option = 0x3A
case control = 0x3B
case rightShift = 0x3C
case rightOption = 0x3D
case rightControl = 0x3E
case leftArrow = 0x7B
case rightArrow = 0x7C
case downArrow = 0x7D
case upArrow = 0x7E
case volumeUp = 0x48
case volumeDown = 0x49
case mute = 0x4A
case help = 0x72
case home = 0x73
case pageUp = 0x74
case forwardDelete = 0x75
case end = 0x77
case pageDown = 0x79
case function = 0x3F
case f1 = 0x7A
case f2 = 0x78
case f4 = 0x76
case f5 = 0x60
case f6 = 0x61
case f7 = 0x62
case f3 = 0x63
case f8 = 0x64
case f9 = 0x65
case f10 = 0x6D
case f11 = 0x67
case f12 = 0x6F
case f13 = 0x69
case f14 = 0x6B
case f15 = 0x71
case f16 = 0x6A
case f17 = 0x40
case f18 = 0x4F
case f19 = 0x50
case f20 = 0x5A
// US-ANSI Keyboard Positions
// eg. These key codes are for the physical key (in any keyboard layout)
// at the location of the named key in the US-ANSI layout.
case a = 0x00
case b = 0x0B
case c = 0x08
case d = 0x02
case e = 0x0E
case f = 0x03
case g = 0x05
case h = 0x04
case i = 0x22
case j = 0x26
case k = 0x28
case l = 0x25
case m = 0x2E
case n = 0x2D
case o = 0x1F
case p = 0x23
case q = 0x0C
case r = 0x0F
case s = 0x01
case t = 0x11
case u = 0x20
case v = 0x09
case w = 0x0D
case x = 0x07
case y = 0x10
case z = 0x06
case zero = 0x1D
case one = 0x12
case two = 0x13
case three = 0x14
case four = 0x15
case five = 0x17
case six = 0x16
case seven = 0x1A
case eight = 0x1C
case nine = 0x19
case equals = 0x18
case minus = 0x1B
case semicolon = 0x29
case apostrophe = 0x27
case comma = 0x2B
case period = 0x2F
case forwardSlash = 0x2C
case backslash = 0x2A
case grave = 0x32
case leftBracket = 0x21
case rightBracket = 0x1E
case keypadDecimal = 0x41
case keypadMultiply = 0x43
case keypadPlus = 0x45
case keypadClear = 0x47
case keypadDivide = 0x4B
case keypadEnter = 0x4C
case keypadMinus = 0x4E
case keypadEquals = 0x51
case keypad0 = 0x52
case keypad1 = 0x53
case keypad2 = 0x54
case keypad3 = 0x55
case keypad4 = 0x56
case keypad5 = 0x57
case keypad6 = 0x58
case keypad7 = 0x59
case keypad8 = 0x5B
case keypad9 = 0x5C
}
func press(_ key: KeyCode, withModifiers modifiers: CGEventFlags = .init()) {
let down = CGEvent(keyboardEventSource: src, virtualKey: key.rawValue, keyDown: true)!
let up = CGEvent(keyboardEventSource: src, virtualKey: key.rawValue, keyDown: false)!
down.flags = modifiers
up.flags = modifiers
down.post(tap: .cghidEventTap)
up.post(tap: .cghidEventTap)
}
struct KeyPress {
let key: KeyCode
let modifiers: CGEventFlags
}
func press(_ key: KeyPress) {
press(key.key, withModifiers: key.modifiers)
}
/// An enum representing possible typing rates.
///
/// - allAtOnce: All the text should be typed at once.
/// - consistent: The text should be typed at a reasonable speed, but with no variance in delay.
/// - natural: The text should be typed so that it appears natural.
/// - customConsistent: The text should be typed at a consistent speed, specified by the associated value.
/// - customVarying: The text should be typed around a given speed, with 5 possible ranges of variation. Both the base speed and the maximum variance are specified by associated values.
public enum Rate {
/// All the text should be typed at once.
case allAtOnce
/// The text should be typed at a reasonable speed, but with no variance in delay.
case consistent
/// The text should be typed so that it appears natural.
case natural
/// The text should be typed at a specified consistent speed.
/// - µsecondDelay: The delay between each key typed.
case customConsistent(µsecondDelay: UInt32)
/// The text should be typed around a specified given speed, with specified variation. The base delay should be the average delay time, and the max variance is the maximum distance from the average to the fastest/slowest possible delay.
/// - µsecondBaseDelay: The base delay between each key typed.
/// - maxVariance: The delay between each key typed.
case customVarying(µsecondBaseDelay: UInt32, maxVariance: UInt32)
}
func type(_ text: [KeyPress], typing: Rate = .natural) {
for character in text {
press(character)
switch typing {
case .allAtOnce:
usleep(0001000)
case .consistent:
usleep(0100000)
case .natural:
var sleepTime = UInt32.random(in: 0...4)
sleepTime *= 10000
usleep(0080000 sleepTime)
case .customConsistent(let µsecondDelay):
usleep(µsecondDelay)
case let .customVarying(µsecondBaseDelay, maxVariance):
var sleepTime = UInt32.random(in: 0...4)
let base = µsecondBaseDelay - maxVariance
sleepTime *= (maxVariance / 2)
let µsecondDelay = base sleepTime
usleep(µsecondDelay)
}
}
}
let lowercaseCharMap: [Character: KeyCode] = [
"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,
"0": .zero,
"1": .one,
"2": .two,
"3": .three,
"4": .four,
"5": .five,
"6": .six,
"7": .seven,
"8": .eight,
"9": .nine,
"=": .equals,
"-": .minus,
";": .semicolon,
"'": .apostrophe,
",": .comma,
".": .period,
"/": .forwardSlash,
"\\": .backslash,
"`": .grave,
"[": .leftBracket,
"]": .rightBracket,
" ": .space
]
let uppercaseCharMap: [Character: KeyCode] = [
"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,
")": .zero,
"!": .one,
"@": .two,
"#": .three,
"$": .four,
"%": .five,
"^": .six,
"&": .seven,
"*": .eight,
"(": .nine,
" ": .equals,
"_": .minus,
":": .semicolon,
"\"": .apostrophe,
"<": .comma,
">": .period,
"?": .forwardSlash,
"|": .backslash,
"~": .grave,
"{": .leftBracket,
"}": .rightBracket,
]
func type(_ text: String, typing: Rate = .natural) {
type(str_to_kparr(text), typing: typing)
}
type("Hello, there")
func str_to_kparr(_ str: String) -> [KeyPress] {
str.map { char -> KeyPress in
if let kc = lowercaseCharMap[char] {
return KeyPress(key: kc, modifiers: .init())
}
if let kc = uppercaseCharMap[char] {
return KeyPress(key: kc, modifiers: .maskShift)
}
return KeyPress(key: .three, modifiers: .maskShift)
}
}
func (lhs: String, rhs: [KeyPress]) -> [KeyPress] {
return str_to_kparr(lhs) rhs
}
func (lhs: [KeyPress], rhs: String) -> [KeyPress] {
return lhs str_to_kparr(rhs)
}
type("! General Kenobi" [.init(key: .a, modifiers: .maskCommand), .init(key: .delete, modifiers: .init()), .init(key: .space, modifiers: [.maskCommand])])
extension KeyPress {
static let returnKey = KeyPress(key: .returnKey, modifiers: .init())
static let enter = Self.returnKey
static let tab = KeyPress(key: .tab, modifiers: .init())
static let space = KeyPress(key: .space, modifiers: .init())
static let delete = KeyPress(key: .delete, modifiers: .init())
static let escape = KeyPress(key: .escape, modifiers: .init())
static let command = KeyPress(key: .command, modifiers: .init())
static let shift = KeyPress(key: .shift, modifiers: .init())
static let capsLock = KeyPress(key: .capsLock, modifiers: .init())
static let option = KeyPress(key: .option, modifiers: .init())
static let control = KeyPress(key: .control, modifiers: .init())
static let rightShift = KeyPress(key: .rightShift, modifiers: .init())
static let rightOption = KeyPress(key: .rightOption, modifiers: .init())
static let rightControl = KeyPress(key: .rightControl, modifiers: .init())
static let leftArrow = KeyPress(key: .leftArrow, modifiers: .init())
static let rightArrow = KeyPress(key: .rightArrow, modifiers: .init())
static let downArrow = KeyPress(key: .downArrow, modifiers: .init())
static let upArrow = KeyPress(key: .upArrow, modifiers: .init())
static let volumeUp = KeyPress(key: .volumeUp, modifiers: .init())
static let volumeDown = KeyPress(key: .volumeDown, modifiers: .init())
static let mute = KeyPress(key: .mute, modifiers: .init())
static let help = KeyPress(key: .help, modifiers: .init())
static let home = KeyPress(key: .home, modifiers: .init())
static let pageUp = KeyPress(key: .pageUp, modifiers: .init())
static let forwardDelete = KeyPress(key: .forwardDelete, modifiers: .init())
static let end = KeyPress(key: .end, modifiers: .init())
static let pageDown = KeyPress(key: .pageDown, modifiers: .init())
static let function = KeyPress(key: .function, modifiers: .init())
static let f1 = KeyPress(key: .f1, modifiers: .init())
static let f2 = KeyPress(key: .f2, modifiers: .init())
static let f4 = KeyPress(key: .f4, modifiers: .init())
static let f5 = KeyPress(key: .f5, modifiers: .init())
static let f6 = KeyPress(key: .f6, modifiers: .init())
static let f7 = KeyPress(key: .f7, modifiers: .init())
static let f3 = KeyPress(key: .f3, modifiers: .init())
static let f8 = KeyPress(key: .f8, modifiers: .init())
static let f9 = KeyPress(key: .f9, modifiers: .init())
static let f10 = KeyPress(key: .f10, modifiers: .init())
static let f11 = KeyPress(key: .f11, modifiers: .init())
static let f12 = KeyPress(key: .f12, modifiers: .init())
static let f13 = KeyPress(key: .f13, modifiers: .init())
static let f14 = KeyPress(key: .f14, modifiers: .init())
static let f15 = KeyPress(key: .f15, modifiers: .init())
static let f16 = KeyPress(key: .f16, modifiers: .init())
static let f17 = KeyPress(key: .f17, modifiers: .init())
static let f18 = KeyPress(key: .f18, modifiers: .init())
static let f19 = KeyPress(key: .f19, modifiers: .init())
static let f20 = KeyPress(key: .f20, modifiers: .init())
// US-ANSI Keyboard Positions
// eg. These key codes are for the physical key (in any keyboard layout)
// at the location of the named key in the US-ANSI layout.
static let a = KeyPress(key: .a, modifiers: .init())
static let b = KeyPress(key: .b, modifiers: .init())
static let c = KeyPress(key: .c, modifiers: .init())
static let d = KeyPress(key: .d, modifiers: .init())
static let e = KeyPress(key: .e, modifiers: .init())
static let f = KeyPress(key: .f, modifiers: .init())
static let g = KeyPress(key: .g, modifiers: .init())
static let h = KeyPress(key: .h, modifiers: .init())
static let i = KeyPress(key: .i, modifiers: .init())
static let j = KeyPress(key: .j, modifiers: .init())
static let k = KeyPress(key: .k, modifiers: .init())
static let l = KeyPress(key: .l, modifiers: .init())
static let m = KeyPress(key: .m, modifiers: .init())
static let n = KeyPress(key: .n, modifiers: .init())
static let o = KeyPress(key: .o, modifiers: .init())
static let p = KeyPress(key: .p, modifiers: .init())
static let q = KeyPress(key: .q, modifiers: .init())
static let r = KeyPress(key: .r, modifiers: .init())
static let s = KeyPress(key: .s, modifiers: .init())
static let t = KeyPress(key: .t, modifiers: .init())
static let u = KeyPress(key: .u, modifiers: .init())
static let v = KeyPress(key: .v, modifiers: .init())
static let w = KeyPress(key: .w, modifiers: .init())
static let x = KeyPress(key: .x, modifiers: .init())
static let y = KeyPress(key: .y, modifiers: .init())
static let z = KeyPress(key: .z, modifiers: .init())
static let A = KeyPress(key: .a, modifiers: .maskShift)
static let B = KeyPress(key: .b, modifiers: .maskShift)
static let C = KeyPress(key: .c, modifiers: .maskShift)
static let D = KeyPress(key: .d, modifiers: .maskShift)
static let E = KeyPress(key: .e, modifiers: .maskShift)
static let F = KeyPress(key: .f, modifiers: .maskShift)
static let G = KeyPress(key: .g, modifiers: .maskShift)
static let H = KeyPress(key: .h, modifiers: .maskShift)
static let I = KeyPress(key: .i, modifiers: .maskShift)
static let J = KeyPress(key: .j, modifiers: .maskShift)
static let K = KeyPress(key: .k, modifiers: .maskShift)
static let L = KeyPress(key: .l, modifiers: .maskShift)
static let M = KeyPress(key: .m, modifiers: .maskShift)
static let N = KeyPress(key: .n, modifiers: .maskShift)
static let O = KeyPress(key: .o, modifiers: .maskShift)
static let P = KeyPress(key: .p, modifiers: .maskShift)
static let Q = KeyPress(key: .q, modifiers: .maskShift)
static let R = KeyPress(key: .r, modifiers: .maskShift)
static let S = KeyPress(key: .s, modifiers: .maskShift)
static let T = KeyPress(key: .t, modifiers: .maskShift)
static let U = KeyPress(key: .u, modifiers: .maskShift)
static let V = KeyPress(key: .v, modifiers: .maskShift)
static let W = KeyPress(key: .w, modifiers: .maskShift)
static let X = KeyPress(key: .x, modifiers: .maskShift)
static let Y = KeyPress(key: .y, modifiers: .maskShift)
static let Z = KeyPress(key: .z, modifiers: .maskShift)
static let zero = KeyPress(key: .zero, modifiers: .init())
static let one = KeyPress(key: .one, modifiers: .init())
static let two = KeyPress(key: .two, modifiers: .init())
static let three = KeyPress(key: .three, modifiers: .init())
static let four = KeyPress(key: .four, modifiers: .init())
static let five = KeyPress(key: .five, modifiers: .init())
static let six = KeyPress(key: .six, modifiers: .init())
static let seven = KeyPress(key: .seven, modifiers: .init())
static let eight = KeyPress(key: .eight, modifiers: .init())
static let nine = KeyPress(key: .nine, modifiers: .init())
static let leftParenthesis = KeyPress(key: .zero, modifiers: .maskShift)
static let exclamationPoint = KeyPress(key: .one, modifiers: .maskShift)
static let atSign = KeyPress(key: .two, modifiers: .maskShift)
static let numberSign = KeyPress(key: .three, modifiers: .maskShift)
static let dollarSign = KeyPress(key: .four, modifiers: .maskShift)
static let percent = KeyPress(key: .five, modifiers: .maskShift)
static let caret = KeyPress(key: .six, modifiers: .maskShift)
static let ampersand = KeyPress(key: .seven, modifiers: .maskShift)
static let asterisk = KeyPress(key: .eight, modifiers: .maskShift)
static let rightParenthesis = KeyPress(key: .nine, modifiers: .maskShift)
static let equals = KeyPress(key: .equals, modifiers: .init())
static let minus = KeyPress(key: .minus, modifiers: .init())
static let semicolon = KeyPress(key: .semicolon, modifiers: .init())
static let apostrophe = KeyPress(key: .apostrophe, modifiers: .init())
static let comma = KeyPress(key: .comma, modifiers: .init())
static let period = KeyPress(key: .period, modifiers: .init())
static let forwardSlash = KeyPress(key: .forwardSlash, modifiers: .init())
static let backslash = KeyPress(key: .backslash, modifiers: .init())
static let grave = KeyPress(key: .grave, modifiers: .init())
static let leftBracket = KeyPress(key: .leftBracket, modifiers: .init())
static let rightBracket = KeyPress(key: .rightBracket, modifiers: .init())
static let plus = KeyPress(key: .equals, modifiers: .maskShift)
static let underscore = KeyPress(key: .minus, modifiers: .maskShift)
static let colon = KeyPress(key: .semicolon, modifiers: .maskShift)
static let quotationMark = KeyPress(key: .apostrophe, modifiers: .maskShift)
static let lessThan = KeyPress(key: .comma, modifiers: .maskShift)
static let greaterThan = KeyPress(key: .period, modifiers: .maskShift)
static let questionMark = KeyPress(key: .forwardSlash, modifiers: .maskShift)
static let pipe = KeyPress(key: .backslash, modifiers: .maskShift)
static let tilde = KeyPress(key: .grave, modifiers: .maskShift)
static let leftBrace = KeyPress(key: .leftBracket, modifiers: .maskShift)
static let rightBrace = KeyPress(key: .rightBracket, modifiers: .maskShift)
static let keypadDecimal = KeyPress(key: .keypadDecimal, modifiers: .init())
static let keypadMultiply = KeyPress(key: .keypadMultiply, modifiers: .init())
static let keypadPlus = KeyPress(key: .keypadPlus, modifiers: .init())
static let keypadClear = KeyPress(key: .keypadClear, modifiers: .init())
static let keypadDivide = KeyPress(key: .keypadDivide, modifiers: .init())
static let keypadEnter = KeyPress(key: .keypadEnter, modifiers: .init())
static let keypadMinus = KeyPress(key: .keypadMinus, modifiers: .init())
static let keypadEquals = KeyPress(key: .keypadEquals, modifiers: .init())
static let keypad0 = KeyPress(key: .keypad0, modifiers: .init())
static let keypad1 = KeyPress(key: .keypad1, modifiers: .init())
static let keypad2 = KeyPress(key: .keypad2, modifiers: .init())
static let keypad3 = KeyPress(key: .keypad3, modifiers: .init())
static let keypad4 = KeyPress(key: .keypad4, modifiers: .init())
static let keypad5 = KeyPress(key: .keypad5, modifiers: .init())
static let keypad6 = KeyPress(key: .keypad6, modifiers: .init())
static let keypad7 = KeyPress(key: .keypad7, modifiers: .init())
static let keypad8 = KeyPress(key: .keypad8, modifiers: .init())
static let keypad9 = KeyPress(key: .keypad9, modifiers: .init())
}
extension KeyPress {
func withModifiers(_ modifiers: CGEventFlags) -> KeyPress {
return KeyPress(key: self.key, modifiers: modifiers)
}
func appendingModifiers(_ modifiers: CGEventFlags) -> KeyPress {
return self.withModifiers(self.modifiers.union(modifiers))
}
}
sleep(2)
type([KeyPress.space.withModifiers([.maskCommand])] "My name is Inigo Montoya. You killed my father. Prepare to die" [KeyPress.space.withModifiers([.maskCommand]), KeyPress.tab.withModifiers(.maskCommand), .command])
func (lhs: String, rhs: KeyPress) -> [KeyPress] {
return lhs [rhs]
}
func (lhs: KeyPress, rhs: String) -> [KeyPress] {
return [lhs] rhs
}
type([KeyPress.tab.withModifiers(.maskCommand), .command, KeyPress.space.withModifiers([.maskCommand])] "")
A few notes:
- I don't know if
CGEventSource(stateID: .hidSystemState)
is right, or if it should be a different state. Check the documentation. - Same for
down.post(tap: .cghidEventTap)
(docs) - Note that you need to post a
keyDown: true
event and akeydown: false
event. - Obviously you can't type special characters like
, so this code prints#
instead - Otherwise, you can pass a string to
type(_:typing:)
and it will type it out. - Look through this code before you run it — it's designed to run with Spotlight bound to command-space (otherwise it might type somewhere important) and will type a
#
at the end in your second open application - The typing rate enum is from my Typer library, which uses AppleScript to type (another option for you), but does not support modifiers.
You could also use AppleScript to type, if you're only on a Mac. I believe this would do it:
tell app "System Events" to keystroke "v" using command down
tell app "System Events" to keystroke "v" using {shift down, command down}
Take a look at my Typer library to see how I compile and run AppleScript.