NW.js Shortcut API 完全指南:实现应用失焦也能触发的全局桌面快捷键
【免费下载链接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.项目地址: https://gitcode.com/gh_mirrors/nw/nw.js
导读
Shortcut是 NW.js 提供的全局桌面快捷键(系统级热键)API:只要注册成功,即使应用窗口完全没有焦点,用户按下对应组合键时应用依然能收到通知。本文以官方文档 docs/References/Shortcut.md 为主体,结合仓库源码(src/api/shortcut/目录及src/api/app/app.cc)深入讲解快捷键的创建、注册、事件监听与注销全流程,并给出修饰键、按键的完整合法取值表与底层实现原理,帮助你为 NW.js 应用快速实现"全局热键唤起"类功能。
基本概念:全局快捷键与普通快捷键的区别
Shortcut代表一个全局键盘快捷键(global keyboard shortcut),也就是常说的系统级热键(system-wide hotkey)。它与网页内keydown/keyup事件监听的最大区别在于:
- 普通按键事件只在应用获得焦点、且焦点位于页面内时才会触发;
- 全局快捷键由操作系统层面捕获,即使应用没有焦点,只要用户按下已注册的组合键,应用就会收到通知。
从源码结构看,NW.js 通过nwapi::GlobalShortcutListener这一"平台无关 + 平台具体"两层设计来实现该能力:公共逻辑维护"快捷键 → 观察者"映射表(global_shortcut_listener.h),而真正的系统级按键捕获委托给各平台实现(macOS、Windows、X11/Linux 各有独立文件,如 global_shortcut_listener_x11.cc)。
Shortcut对象继承自 Node.js 的EventEmitter。每次用户按下已注册的快捷键,应用都会在该 shortcut 对象上收到active事件;注册失败或按键无法解析时则会收到failed事件。
完整示例:最短可用代码
官方文档给出的 Synopsis 覆盖了创建、注册、监听、注销四个完整步骤,这里完整保留并加以注释:
var option = { key : "Ctrl+Shift+A", active : function() { console.log("Global desktop keyboard shortcut: " + this.key + " active."); }, failed : function(msg) { // :(, fail to register the |key| or couldn't parse the |key|. console.log(msg); } }; // Create a shortcut with |option|. var shortcut = new nw.Shortcut(option); // Register global desktop shortcut, which can work without focus. nw.App.registerGlobalHotKey(shortcut); // If register |shortcut| successfully and user struck "Ctrl+Shift+A", |shortcut| // will get an "active" event. // You can also add listener to shortcut's active and failed event. shortcut.on('active', function() { console.log("Global desktop keyboard shortcut: " + this.key + " active."); }); shortcut.on('failed', function(msg) { console.log(msg); }); // Unregister the global desktop shortcut. nw.App.unregisterGlobalHotKey(shortcut);使用要点:
- 通过
new nw.Shortcut(option)创建快捷键对象; - 通过
nw.App.registerGlobalHotKey(shortcut)向系统注册; - 监听
active事件响应按键; - 不再需要时用
nw.App.unregisterGlobalHotKey(shortcut)注销,避免热键被应用长期占用。
在 JS 绑定层(shorcut.js),构造函数会做严格校验:option必须是对象,且必须包含key属性,否则直接抛出TypeError;active、failed若提供则必须是函数。也就是说key是唯一必填项,两个回调均可选。
new Shortcut(option) 构造参数详解
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
option | Object | 是 | 包含初始设置的对象 |
option.key | String | 是 | 快捷键组合,如"Ctrl+Shift+A",详见下文shortcut.key |
option.active | Function | 否 | 热键被触发时的回调,对应shortcut.active属性 |
option.failed | Function | 否 | 热键注册失败时的回调,对应shortcut.failed属性 |
从 C++ 侧看,Shortcut构造时(shortcut.cc)会从option中取出key字符串交给Parse()解析为ui::Accelerator;如果解析结果主键码为VKEY_UNKNOWN,会立即触发OnFailed("Can not parse shortcut: ..."),把错误信息通过failed事件回报给 JS 层。
shortcut.key:组合键的书写规范与完整取值表
shortcut.key用于获取一个Shortcut的按键组合,它是一个字符串,形如"Ctrl+Alt+A"。组合键由零个或多个修饰键(modifiers)与一个主键码(key)组成,且只支持单个键码——即一个key字符串里只能有一个主键,不能写成"Ctrl+A+B"这种多主键形式。键码大小写不敏感,例如"ctrl+shift+a"与"Ctrl+Shift+A"等价。
在底层解析时(shortcut.cc),Parse()会先把整个字符串转为小写,再按+号分割成 token,逐个识别为修饰键或主键码。
支持的修饰键(Modifiers)
| 修饰键 | 说明 |
|---|---|
Ctrl | 控制键 |
Alt | Alt 键 |
Shift | Shift 键 |
Command | 在 macOS 上映射为 Apple 键(⌘);在 Windows 和 Linux 上映射为 Windows 键 |
值得注意的一个平台差异细节:在 shortcut.cc 中,Ctrl在 macOS 上被映射为EF_COMMAND_DOWN(即 Command 键),而在其他平台映射为EF_CONTROL_DOWN(Ctrl 键)。这意味着"Ctrl+A"在 Mac 上实际绑定的是 ⌘+A,跨平台应用需要留意这一行为差异。
支持的按键(Keys)
| 类别 | 合法取值 |
|---|---|
| 字母 | A-Z |
| 数字 | 0-9 |
| 功能键 | F1-F24 |
| 编辑/导航键 | Home/End/PageUp/PageDown/Insert/Delete |
| 方向键 | Up/Down/Left/Right |
| 媒体键 | MediaNextTrack/MediaPlayPause/MediaPrevTrack/MediaStop |
| 标点符号别名 | Comma或,;Period或.;Tab或\t;Backquote或`;Enter或\n;Minus或-;Equal或=;Backslash或\;Semicolon或;;Quote或';BracketLeft或[;BracketRight或] |
| 其他 | Escape,以及 DOM Level 3 W3C KeyboardEvent Code Values 中定义的键值 |
这些取值与源码中的常量一一对应(见 shortcut_constants.cc),例如kKeyMediaNextTrack = "medianexttrack"、kKeyPgUp = "pageup"、kKeyTab = "tab"。解析时字母和数字通过 ASCII 区间直接映射为VKEY_A-VKEY_Z、VKEY_0-VKEY_9(shortcut.cc)。
警告:无修饰键的单键注册需谨慎
官方文档明确给出警告:nw.App.registerGlobalHotKey()允许应用拦截单个按键(如{ key: "A" })。但一旦注册成功,在应用注销之前,用户在系统里将无法正常使用字母 "A",因为该按键已被系统级截获。不过 API 本身不限制这种用法——如果你想监听媒体键(如MediaNextTrack),无修饰键注册反而是有用的。
只在明确知道自己在做什么时才使用零修饰键的注册。
shortcut.active 与 shortcut.failed
shortcut.active:获取或设置快捷键被按下时的回调函数。用户按下已注册组合键时触发。shortcut.failed:获取或设置失败回调。当应用传入无效的按键(无法解析),或注册失败(例如该热键已被其他程序占用)时触发,回调参数为失败原因字符串。
在 JS 绑定层(shorcut.js),handleEvent收到'active'事件时会调用this.active(),收到'failed'事件时会以arguments[1]为参数调用this.failed(msg);随后继续交由exports.Base的handleEvent分发,从而同步触发对应的 EventEmitter 事件(见下节)。C++ 侧对应的事件发送逻辑位于 shortcut.cc:OnActive()发送空参数的active事件,OnFailed()把错误消息字符串随failed事件一并发出。
事件:active 与 failed
- Event:
active:与shortcut.active属性等价。用户按下已注册快捷键时触发。 - Event:
failed:与shortcut.failed属性等价。传入非法按键或注册失败时触发。
因为Shortcut继承自EventEmitter(shorcut.js 中util.inherits(Shortcut, exports.Base),Base具备事件能力),所以既可以在构造参数里传回调,也可以事后用.on('active', ...)/.on('failed', ...)添加监听器,两种方式可以混用。
从源码看注册与注销的完整调用链
当你调用nw.App.registerGlobalHotKey(shortcut)时,实际发生的过程如下:
- JS → C++ 桥接:
app.cc的同步方法分发中,"RegisterGlobalHotKey"分支根据传入的object_id取出对应的Shortcut*对象,然后调用GlobalShortcutListener::GetInstance()->RegisterAccelerator(shortcut->GetAccelerator(), shortcut)(app.cc)。如果注册失败,会立刻回调shortcut->OnFailed("Register global desktop keyboard shortcut failed.")。 - 公共逻辑去重:
GlobalShortcutListener::RegisterAccelerator(global_shortcut_listener.cc)先检查该加速键是否已在accelerator_map_中,若已被注册则返回false;接着调用平台相关的RegisterAcceleratorImpl,只有底层系统注册成功才会把"加速键 → 观察者"写入映射表。若此前没有任何注册,会先StartListening()开始监听系统按键事件。 - 平台实现:以 X11/Linux 为例(global_shortcut_listener_x11.cc),实现通过
XGrabKey在根窗口上抓取按键,并且为了兼容 Num Lock、Caps Lock、Scroll Lock 三种锁定状态,会对 8 种修饰键组合(kModifiersMasks)逐一抓取,从而保证各平台行为一致。 - 按键分发:用户按下组合键后,平台层回调
NotifyKeyPressed(accelerator),在映射表中找到观察者并调用其OnKeyPressed;Shortcut::OnKeyPressed确认加速键匹配后触发OnActive(),最终把active事件送回 JS 层(shortcut.cc)。
注销过程是对称的:nw.App.unregisterGlobalHotKey(shortcut)最终调用GlobalShortcutListener::UnregisterAccelerator,执行平台层的XUngrabKey等解除抓取操作,并从映射表移除条目;当映射表清空时自动StopListening()(global_shortcut_listener.cc)。此外GlobalShortcutListener还提供了SetShortcutHandlingSuspended挂起/恢复机制,用于避免在窗口设置快捷键时系统热键干扰输入。
另外,仓库中还存在一份扩展 API 的 IDL 定义 nw_shortcut.idl,声明了registerAccelerator/unregisterAccelerator函数与onKeyPressed事件,其Accelerator结构由key加command/ctrl/alt/shift四个布尔修饰键组成,可看作 Shortcut 机制在扩展系统侧的另一种表达。
典型应用场景与注意事项
适合使用全局快捷键的场景包括:
- 全局唤起:应用最小化或失焦时,通过热键把主窗口重新显示并前置;
- 快捷操作:截图、录音、翻译等工具型应用常驻后台时响应热键;
- 媒体控制:注册
MediaNextTrack/MediaPlayPause等媒体键,实现全局播放控制(这也是文档特别指出"无修饰键注册有用"的场景)。
实践建议:
- 始终在应用退出或不再需要时调用
nw.App.unregisterGlobalHotKey(shortcut)释放热键,避免占用系统资源或与其他应用冲突; - 为
failed事件编写兜底逻辑(例如弹出提示或改用应用内快捷键),因为同一组合键可能已被其他原生程序注册——GlobalShortcutListener::RegisterAccelerator的注释也说明,注册失败最常见的原因就是"该快捷键已被其他原生应用注册"; - 注意
Command/Ctrl在 macOS 上的映射差异,跨平台应用应针对不同平台选用合适的组合键。
相关文档与源码索引
- 官方参考文档:docs/References/Shortcut.md
- JS 绑定层:src/api/shortcut/shorcut.js
- C++ 实现与按键解析:src/api/shortcut/shortcut.cc、src/api/shortcut/shortcut_constants.cc
- 平台无关监听器:src/api/shortcut/global_shortcut_listener.cc、src/api/shortcut/global_shortcut_listener.h
- 平台实现:X11/Linux 版 global_shortcut_listener_x11.cc,另有 macOS、Windows 版本位于同目录
- 注册/注销入口:src/api/app/app.cc
- 扩展 API 定义:src/api/nw_shortcut.idl
【免费下载链接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.项目地址: https://gitcode.com/gh_mirrors/nw/nw.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考