模拟全局键盘按下

Simulating global keyboard presses

我正在 mac 上构建一个 swiftUI 应用程序,我想知道是否有一种方法可以在应用程序处于 运行 或在后台时模拟键盘快捷键。我对这个应用程序的目标是在我的服务器向我发送相应的手势信息时调用快捷方式。我一直在考虑使用 NSEvent.addGlobalMonitorForEvents(matching: .keyDown, handler: self.handler),但我认为这是更多的重新编程快捷方式,而不是仅仅调用它们。

例如,如果我的服务器向我发送一个字符串“粘贴”,那么无论我在哪个屏幕上,只要我的应用程序是 运行,我将如何调用命令粘贴快捷方式。任何帮助或信息将不胜感激!

我的 Mac 上有这个游乐场,所以我想我会在这里分享它以防它有帮助:

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])] + "")

一些注意事项:

  • 不知道CGEventSource(stateID: .hidSystemState)对不对,还是应该换个状态。 Check the documentation.
  • down.post(tap: .cghidEventTap) 相同 (docs)
  • 请注意,您需要 post 一个 keyDown: true 事件 一个 keydown: false 事件。
  • 显然你不能输入像 这样的特殊字符,所以这段代码打印 # 而不是
  • 否则,您可以将字符串传递给 type(_:typing:),它会键入它。
  • 在 运行 之前仔细阅读这段代码 — 它被设计为 运行 Spotlight 绑定到命令-space(否则它可能会在重要的地方输入)并且会输入 运行 =17=] 在你的第二个打开的应用程序的末尾
  • 打字率枚举来自我的 Typer library,它使用 AppleScript 打字(您的另一个选择),但不支持修饰符。

如果您只使用 Mac,您也可以使用 AppleScript 打字。我相信这会做到:

tell app "System Events" to keystroke "v" using command down
tell app "System Events" to keystroke "v" using {shift down, command down}

看看我的 Typer 库,看看我是如何编译和 运行 AppleScript 的。