RegisterEventHotKey 是 macOS 的 Carbon API 中的一个函数,用于注册全局快捷键(热键),即使应用当前不在前台,也可以响应指定的键盘组合。
基本用法
OSStatus RegisterEventHotKey(
UInt32 inHotKeyCode,
UInt32 inHotKeyModifiers,
EventHotKeyID inHotKeyID,
EventTargetRef inTarget,
OptionBits inOptions,
EventHotKeyRef *outRef
);
可以通过它实现「按下 ⌘ + ⇧ + S 在任何应用中截屏」这样的功能。
参数详解
1、inHotKeyCode:快捷键的 keyCode(物理按键的代码,如 kVK_ANSI_S);
2、inHotKeyModifiers:修饰键,如 cmd, shift, option, control 等(例如 cmd + shift 就是 `cmdKey;
3、inHotKeyID:用于标识这个热键(signature 和 id 字段);
4、inTarget:通常为 GetEventDispatcherTarget(),表示系统事件分发器;
5、inOptions:一般传 0;
6、outRef:返回注册的热键引用,用于后续注销(UnregisterEventHotKey)。
快捷键修饰键常量
cmdKey = 0x1000
optionKey = 0x0800
controlKey = 0x4000
shiftKey = 0x0200
使用场景-注册快捷键
import Carbon.HIToolbox
var hotKeyRef: EventHotKeyRef? = nil
var gMyHotKeyID = EventHotKeyID(signature: OSType(bitPattern: UInt32(truncatingIfNeeded: "TEST".fourCharCodeValue)),
id: 1)
// 注册快捷键 cmd + shift + S
RegisterEventHotKey(UInt32(kVK_ANSI_S),
UInt32(cmdKey | shiftKey),
gMyHotKeyID,
GetEventDispatcherTarget(),
0,
&hotKeyRef)
extension String {
var fourCharCodeValue: UInt32 {
var result: UInt32 = 0
for character in self.utf8.prefix(4) {
result = (result << 8) + UInt32(character)
}
return result
}
}
还需要监听热键事件,一般写在入口文件或 NSApplicationDelegate 里:
InstallEventHandler(GetApplicationEventTarget(), { (handlerCallRef, eventRef, userData) in
var hotKeyID = EventHotKeyID()
GetEventParameter(eventRef, EventParamName(kEventParamDirectObject),
EventParamType(typeEventHotKeyID), nil,
MemoryLayout<EventHotKeyID>.size,
nil, &hotKeyID)
if hotKeyID.id == 1 {
print("触发快捷键:cmd+shift+S")
}
return noErr
}, 1, [EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed))], nil, nil)
1、快捷键代码解析
1、导入Carbon框架
import Carbon.HIToolbox
Carbon 是 macOS 上的底层 C API 框架;
HIToolbox 提供了与键盘、菜单等 UI 相关的功能;
RegisterEventHotKey 和 InstallEventHandler 都定义在这个框架中。
2、声明热键变量和 ID
var hotKeyRef: EventHotKeyRef? = nil
用来保存注册热键的引用;
后面如果需要取消注册,可以使用 UnregisterEventHotKey(hotKeyRef!)。
var gMyHotKeyID = EventHotKeyID(
signature: OSType(bitPattern: UInt32(truncatingIfNeeded: "TEST".fourCharCodeValue)),
id: 1
)
EventHotKeyID 是用来标识热键事件的结构体;
1)signature: 任意 4 个字符(例如 “TEST”),用于分类;
2)id: 用于区分不同的热键(如果注册了多个热键,可以通过 id 来区分是哪个被按下)。
3)fourCharCodeValue 是一个扩展方法,将 “TEST” 转为 UInt32。也可以手动写 FOUR_CHAR_CODE(“TEST”)。
如果不想要使用fourCharCodeValue方法,也可以直接转换为UInt32类型,例如“TEST”可以转换为:
let hotKeyID = EventHotKeyID(signature: OSType(0x54455354), id: 1) // 'TEST'
3、注册热键
RegisterEventHotKey(
UInt32(kVK_ANSI_S), // 键码(S键)
UInt32(cmdKey | shiftKey), // 修饰键(⌘ + ⇧)
gMyHotKeyID, // 热键ID
GetEventDispatcherTarget(), // 事件分发目标(系统默认即可)
0, // 默认选项
&hotKeyRef // 返回热键引用
)
这段代码注册了一个全局快捷键 ⌘ + ⇧ + S;
如果系统中没有冲突(其他应用没占用),注册将成功;
此快捷键即使在其他 App 中也能触发事件处理器。
2、监听热键事件代码解析
安装事件监听器:
InstallEventHandler(
GetApplicationEventTarget(), // 事件监听目标
{ (handlerCallRef, eventRef, userData) in
var hotKeyID = EventHotKeyID()
GetEventParameter(eventRef,
EventParamName(kEventParamDirectObject),
EventParamType(typeEventHotKeyID),
nil,
MemoryLayout<EventHotKeyID>.size,
nil,
&hotKeyID)
if hotKeyID.id == 1 {
print("触发快捷键:cmd+shift+S")
}
return noErr
},
1, // 监听的事件数量
[EventTypeSpec(eventClass: OSType(kEventClassKeyboard),
eventKind: UInt32(kEventHotKeyPressed))], // 快捷键按下事件
nil,
nil
)
InstallEventHandler(…):注册一个事件回调函数:
InstallEventHandler(
_ inTarget: EventTargetRef, // 谁来接收事件
_ inHandler: EventHandlerUPP, // 回调函数
_ inNumTypes: UInt32, // 要监听的事件数量
_ inList: UnsafePointer<EventTypeSpec>, // 要监听的事件列表
_ inUserData: UnsafeMutableRawPointer?, // 用户数据(可选)
_ outRef: UnsafeMutablePointer<EventHandlerRef>? // 返回值(可选)
)
注册了一个全局键盘快捷键(例如 ⌘ + ⇧ + S),注册热键之后,还需要监听“热键被触发”的事件,这就是 InstallEventHandler 的作用。
1、监听层级
InstallEventHandler(
GetApplicationEventTarget(), // 表示“应用级别”的事件目标
这里监听的是整个 App 层面的事件(不是窗口,不是控件)。
2、回调事件
{ (handlerCallRef, eventRef, userData) in
var hotKeyID = EventHotKeyID()
GetEventParameter(eventRef,
EventParamName(kEventParamDirectObject),
EventParamType(typeEventHotKeyID),
nil,
MemoryLayout<EventHotKeyID>.size,
nil,
&hotKeyID)
从 eventRef 中提取出触发事件的 HotKey ID。它对应之前注册的 EventHotKeyID(signature: …, id: 1)。
if hotKeyID.id == 1 {
print("触发快捷键:cmd+shift+S")
}
return noErr
根据热键 ID 判断哪个热键被触发,然后执行对应操作(比如截图、弹窗、调用其他函数等)。
3、监听事件数量
1, // 要监听一个事件
监听的事件种类数量为 1 个。
4、指定监听的事件
[EventTypeSpec(
eventClass: OSType(kEventClassKeyboard),
eventKind: UInt32(kEventHotKeyPressed)
)], // 指定要监听“热键被按下”的事件
nil,
nil
指定监听的事件是:键盘事件中的 “热键按下” 类型。
3、实现效果

注意事项
1、RegisterEventHotKey 是 Carbon API,虽然已过时但仍然有效;
2、注册全局快捷键不等于监听按键,需要手动添加事件处理器;
3、若要监听快捷键并做 UI 操作,建议使用非沙盒 App;
4、注册冲突(已有其他应用占用快捷键)时,不会生效,建议提供设置界面让用户自定义快捷键。
5、InstallEventHandler 必须在主线程中执行。
总结
RegisterEventHotKey用于注册全局快捷键,在应用启动时,会监听应用层面的快捷键,如果与其他应用的快捷键重复,可能会失效或覆盖其他应用的快捷键。
以Command + Shift + S为例,在Word应用中为另存为文件,如果设置Command + Shift + S的RegisterEventHotKey快捷键后,按Command + Shift + S就会调用RegisterEventHotKey对于的方法。
与其他监听不同的是,RegisterEventHotKey走的是 Carbon 的 系统事件分发机制,属于官方支持的“安全”热键注册机制,不涉及任何监听或拦截行为:
它是向系统注册快捷键,系统在合适时把事件“分发”给用户;
它不会监听用户的按键流,不会知道用户是否按了其他键;
所以 macOS 不认为这侵犯隐私,不要求授权辅助功能权限。
快捷键优先级问题
当注册的全局快捷键与其他应用的全局快捷键重复时,macOS会遵循以下优先级规则(非官方总结):
1、前台 App(active App)优先;
2、先注册的 App 优先(同一层级时);
3、某些系统级 App(如 iShot、Alfred、Karabiner)使用了 更底层机制(如 CGEventTap)或私有 API,可绕过常规限制;
4、辅助功能(Accessibility)权限更高的 App 可注册更强控制;
5、沙盒应用在某些情况下受到限制。
而RegisterEventHotKey属于 Carbon 的全局热键注册机制,不需要辅助权限,所以它的“全局”不是系统层最高优先级,而是系统协调分发的那一层;
如果其他 App 注册了相同组合,RegisterEventHotKey就抢不过它们。
完整代码
1、HotKey代码:
import Carbon.HIToolbox
extension String {
var fourCharCodeValue: UInt32 {
var result: UInt32 = 0
for character in self.utf8.prefix(4) {
result = (result << 8) + UInt32(character)
}
return result
}
}
class HotKey {
var hotKeyRef: EventHotKeyRef? = nil
var gMyHotKeyID = EventHotKeyID(signature: OSType("TEST".fourCharCodeValue), id: 1)
func registerHotKey() {
// 注册快捷键 cmd + shift + S
RegisterEventHotKey(UInt32(kVK_ANSI_S),
UInt32(cmdKey | shiftKey),
gMyHotKeyID,
GetEventDispatcherTarget(),
0,
&hotKeyRef)
}
}
2、AppDelegate文件代码:
import Carbon.HIToolbox
class AppDelegate: NSObject, NSApplicationDelegate {
var hotKey: HotKey?
func applicationDidFinishLaunching(_ notification: Notification) {
hotKey = HotKey()
hotKey?.registerHotKey()
print("注册快捷键")
InstallEventHandler(
GetApplicationEventTarget(), // 事件监听目标
{ (handlerCallRef, eventRef, userData) in
var hotKeyID = EventHotKeyID()
GetEventParameter(eventRef,
EventParamName(kEventParamDirectObject),
EventParamType(typeEventHotKeyID),
nil,
MemoryLayout<EventHotKeyID>.size,
nil,
&hotKeyID)
if hotKeyID.id == 1 {
print("触发快捷键:cmd+shift+S")
print("调用全屏截图")
StatusBarController.shared.fullScreenshoot()
}
return noErr
},
1, // 监听的事件数量
[EventTypeSpec(eventClass: OSType(kEventClassKeyboard),
eventKind: UInt32(kEventHotKeyPressed))], // 快捷键按下事件
nil,
nil
)
print("快捷键注册完成")
}
}