CGEvent
CGEvent 的定义如下:
Defines an opaque type that represents a low-level hardware event.
通常用来表示低级别用户输入事件的核心数据类型,我们可以用它来:
- 接收和检查 系统范围内的低级别输入事件(通过 Event Taps)。
- 创建和发送 合成的输入事件来模拟用户操作(通过 Event Posting)。
其中一个 Api 如下
@available(macOS 10.4, *)
public /*not inherited*/ init?(keyboardEventSource source: CGEventSource?, virtualKey: CGKeyCode, keyDown: Bool)
CGKeyCode
其中 CGKeyCode 的定义如下:
Represents the virtual key codes used in keyboard events
Example 模拟点击 Shift + Command +T 事件
在了解到上述的代码之后,如果需要模拟点击 Shift + Command +T (Apple Note 中添加 Title 的快捷键) 的事件,很容易可以写出下面的代码
public func sendKeyCode() {
const keyCodes = [57, 55, 17] // 分别代表 shift、command 和 t
for code in keyCodes {
if let down = CGEvent(keyboardEventSource: source, virtualKey: code, keyDown: true) {
down.post(tap: .cghidEventTap)
}
}
// Release all keys
for code in keyCodes {
if let up = CGEvent(keyboardEventSource: source, virtualKey: code, keyDown: false) {
up.post(tap: .cghidEventTap)
}
}
}
这段代码一不留心就看似很正确了(正是我写的。。。),但是其实有严重问题。正常逻辑下使用快捷键 Shift + Command + T 的时候,当 T 按下当时候,应当保证 Shift 和 Command 同时处于按下当状态,上面的代码正确工作的前提是,当 T 被按下当时候,另外两个按键还没有被释放掉,这显然是不可靠的。
如何去修复这个代码呢?这涉及到 CGEvent 的另外一个属性 — var flags: [CGEventFlags]{ get set }
CGEventFlags
定义如下:
Constants that indicate the modifier key state at the time an event is created, as well as other event-related states.
这里提到一个新的关键词 modifier key.
Modifier Keys
上面提到的 Api 中有个参数叫做 virtual key, virutal key 里还有一部分被称为 modifier keys,它们在操作系统中代表那些本身不执行操作,但与其他键组合使用时可以改变其它键功能的按键。
键名 | 常见用途 |
---|---|
Shift | 输入大写字母、输入符号(如 Shift + 2 输入 @) |
Ctrl | 控制类快捷键(如 Ctrl + C 复制,Ctrl + V 粘贴) |
Alt | 组合键(如 Alt + Tab 切换窗口) |
Command (⌘)(macOS) | Mac 中类似 Ctrl 的作用(如 ⌘ + C 复制) |
Option (⌥)(macOS) | 多功能键(输入特殊字符、组合快捷键) |
Fn | 访问功能键(如 F1 ~ F12)或笔记本的额外功能 |
这部分的参数应当在 flags 中使用。
正确的代码
import CoreGraphics
public func sendKeyCode() {
let keyCodeT: UInt16 = 17 // T 键的 Virtual Key Code
let flags: CGEventFlags = [.maskShift, .maskCommand] // 指定 Shift 和 Command 修饰键
// 1. 创建 T 键按下的事件,并设置修饰键 flags
let keyDownEvent = CGEvent(keyboardEventSource: source, virtualKey: keyCodeT, keyDown: true)
keyDownEvent?.flags = flags // <--- 关键在这里!
// 2. 创建 T 键弹起的事件,同样设置修饰键 flags (通常也需要)
let keyUpEvent = CGEvent(keyboardEventSource: source, virtualKey: keyCodeT, keyDown: false)
keyUpEvent?.flags = flags // <--- 关键在这里!
// 3. 发送事件
keyDownEvent?.post(tap: .cghidEventTap)
// 可以加一个微小的延迟,模拟按键时长,但对于快捷键通常不是必须的
// usleep(10000) // e.g., 10ms delay
keyUpEvent?.post(tap: .cghidEventTap)
}