欢迎光临
我们一直在努力

Electron 鸿蒙快捷键全失灵,我排查了六个小时

Electron 鸿蒙快捷键全失灵,我排查了六个小时

先上代码。这是我们 App 里监听快捷键的一段,在 Windows 和 Mac 上跑了半年没出过事:

// main.js
const { app, globalShortcut, BrowserWindow } = require('electron');

app.whenReady().then(() => {
const win = new BrowserWindow({ width: 800, height: 600 });
win.loadFile('index.html');

globalShortcut.register('CmdOrCtrl+I', () => {
win.webContents.toggleDevTools();
console.log('[OK] DevTools toggled');
});

globalShortcut.register('CmdOrCtrl+N', () => {
console.log('[OK] New window shortcut triggered');
});
});

逻辑简单得不能再简单:Ctrl+I 开 DevTools,Ctrl+N 打日志。直到上周五下午我把这个应用扔到鸿蒙 PC 上跑。

按下 Ctrl+I——毫无反应。连个报错都没有,控制台干净得像刚重装完系统。我扭头看了看时间:14:27。

14:30 它明明注册成功了

第一时间自然是确认 register 返回值。globalShortcut.register() 返回布尔值,true 表示注册成功,false 表示失败(通常是快捷键被别的程序占用了)。

const registered = globalShortcut.register('CmdOrCtrl+I', handler);
console.log('GlobalShortcut registered:', registered); // → true

true。这事就诡异了——Electron 自己说注册成功,但按键下去事件没触发。难道是因为窗口没焦点?我猛点了十几下窗口标题栏,又试了一遍。还是没有。

等一下,在继续往下说之前我得交代一个重要前提。鸿蒙 PC 上的 Electron 不是官方原版 Electron,是华为团队基于 arkui-x 做的适配层。说白了就是 Electron 的 Chromium 内核跟鸿蒙的窗口系统之间多了一层 arkui-x 做翻译。这层翻译要是对键盘事件的处理有什么偏差,那出任何幺蛾子都不奇怪。

15:10 换一批快捷键试试

我第一反应是 Ctrl+I 可能被鸿蒙系统自己拦截了。毕竟很多 Linux 桌面环境里 Ctrl+I 是打开斜体格式的快捷键,鸿蒙底层是 Linux 内核,抢走也正常。

// 试一个冷门组合键,理论上不会跟系统冲突
globalShortcut.register('CmdOrCtrl+Shift+Alt+K', () => {
console.log('Cold key combo works!');
});

一个都没触发。不是 Ctrl+I 的问题,是所有快捷键都挂了。

我把能想到的组合全测了一遍:Ctrl+F、Alt+Q、Ctrl+Shift+T、Shift+F5。不是 Ctrl 的问题,不是 Alt 的问题,是 globalShortcut 整个模块在鸿蒙上完全不工作。

说实话这时候我心里已经在骂了——要是某个特定组合被占用,我能理解。整个模块都废了,这算什么?文档上一个字没提。

16:00 绕过 globalShortcut 试试

globalShortcut 不行,那 BrowserWindow 级的键盘事件呢?

const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});

win.webContents.on('before-input-event', (event, input) => {
console.log('Key event caught:', input.key, input.type, input.modifiers);
if (input.key === 'i' && (input.control || input.meta)) {
event.preventDefault();
win.webContents.toggleDevTools();
}
});

如果 before-input-event 能拿到键盘事件,至少说明问题在 accelerator 匹配那一步而非更底层。结果呢——这个事件根本没触发。键盘事件压根没传到 Electron 的 JS 层。

16:45 暂停,先确认是谁的锅

为了区分是 Electron 的问题还是鸿蒙的问题,我在同一台机器上快速做了三个对照实验:

  • 跑一个纯 Chromium 浏览器 → 键盘事件正常
  • 跑一个 arkui-x 原生 demo → 能收到键盘事件
  • 跑 Electron 应用 → 键盘事件丢失
  • 结论很明确了:arkui-x → Electron C++ 层 这一段把键盘事件弄丢了。

    17:30 往 C++ 层埋日志

    走到这步只剩一条路:拉源码、加日志、编译、看输出。这一步本身就极其痛苦——Electron 源码仓库大得离谱,git clone 走了快二十分钟。编译更不用提了,第一次完整编译我等了十五分钟,中间还因为缺少鸿蒙 SDK 的编译链断了一次。

    编译通过后,我在 shell/browser/ui/views/electron_views_native_window.cc 里插了几行 LOG(INFO),专门打印收到的键盘事件:

    // electron_views_native_window.cc(简化)
    void ElectronViewsNativeWindow::OnKeyEvent(const KeyEvent& event) {
    LOG(INFO) << "[electron:key_event] Received: keyCode=" << event.key_code
    << ", modifiers=" << event.modifiers;

    if (ShouldHandleKeyboardShortcut(event)) {
    LOG(INFO) << "[electron:key_event] Matched accelerator, dispatching…";
    HandleKeyboardEvent(event);
    } else {
    LOG(INFO) << "[electron:key_event] Accelerator NOT matched for combo";
    }
    }

    跑起来后看日志,问题浮出水面了:

    [electron:key_event] Received: keyCode=73, modifiers=CTRL
    [electron:key_event] Accelerator NOT matched for combo

    键盘事件确实传到了 C++ 层,keyCode=73(就是字母 I),修饰键也正确识别为 CTRL。但 accelerator 匹配这一步失败了。

    18:40 根因:修饰键标记位对不上

    顺藤摸瓜找到匹配逻辑,在 ui/base/accelerators/accelerator_parsing.cc 里。具体来说,CmdOrCtrl 在鸿蒙上被解析为 Ctrl 没错,但 arkui-x 传过来的键盘修饰键位掩码跟 Chromium 内部定义的 EF_CONTROL_DOWN 常量对不上号。

    鸿蒙底层虽然是 Linux 内核,但 arkui-x 的输入子系统重新定义了一套修饰键枚举值。Chromium 里的 EF_CONTROL_DOWN = 1 << 2,arkui-x 传过来的可能是 1 << 3 或者其他值,反正不相等。结果就是:Electron 拿着 Ctrl 键的掩码去比较,永远返回 false。

    说白了就是一个常量定义不一致导致整个 accelerator 匹配系统全线崩溃。不是什么复杂的 bug,但非常隐蔽——如果不往 C++ 加日志,你永远只能看到"注册成功但没反应"这个表象。

    19:30 绕路方案:放弃 globalShortcut

    知道了根因,但我没时间等上游修。明天要给合作方做 demo,今晚必须把功能跑起来。

    我想了三个方案:

    方案 A:改 Chromium 的 accelerator_parsing.cc,把 arkui-x 的修饰键值映射到 Chromium 的标准值。可以,但需要重新编译 Electron 整个 C++ 层,编译一次二十分钟起,改动一处重编全量。pass。

    方案 B:在前端用 keydown 事件模拟全局快捷键。问题是 Electron 的渲染进程 keydown 只在窗口获得焦点时触发,做不了真正的全局快捷键。

    方案 C:直接用鸿蒙系统的 hidumper 命令从系统层抓键盘事件,绕开 Electron 的 accelerator 系统。

    方案 C 最脏,但最快能跑。我选了 C。

    const { spawn } = require('child_process');

    class HarmonyShortcutManager {
    constructor() {
    this.handlers = new Map();
    this.watcher = null;
    }

    register(combo, callback) {
    const parts = combo.split('+');
    const key = parts.pop().toUpperCase();
    const modifiers = new Set(parts);
    this.handlers.set(key, { modifiers, callback });
    }

    start() {
    // hdc shell hidumper –keyevent 是鸿蒙的按键事件 dump 工具
    this.watcher = spawn('hdc', ['shell', 'hidumper', '–keyevent'], {
    stdio: ['ignore', 'pipe', 'pipe'],
    });

    this.watcher.stdout.on('data', (raw) => {
    const event = raw.toString();
    this.handlers.forEach((spec, targetKey) => {
    if (event.includes(`KEY_${targetKey}`)) {
    const hasCtrl = event.includes('CTRL');
    const hasAlt = event.includes('ALT');
    const hasShift = event.includes('SHIFT');

    const matched = [spec.modifiers].every(m => {
    if (m === 'Ctrl') return hasCtrl;
    if (m === 'Alt') return hasAlt;
    if (m === 'Shift') return hasShift;
    return true;
    });

    if (matched) {
    spec.callback();
    }
    }
    });
    });

    this.watcher.stderr.on('data', (err) => {
    console.error('[Shortcut] hidumper error:', err.toString());
    });
    }

    stop() {
    if (this.watcher) {
    this.watcher.kill();
    this.watcher = null;
    }
    }
    }

    说真的,我自己都觉得这玩意丑。用 hidumper 监听系统输入然后字符串匹配来模拟快捷键——在正常环境里我绝对不会这么干。但在鸿蒙上,当所有正规途径都走不通的时候,能跑的方案就是好方案。

    我把它挂到主进程里跑了一晚上,Ctrl+I 和 Ctrl+N 都能正常触发。延迟大概几十毫秒,可以接受。唯一需要注意的是 hidumper 的权限——它需要在调试模式下运行,生产环境得把权限提前配置好。

    写到这儿

    这件事给我最大的教训不是什么技术层面的。而是:在一个新平台上跑已有方案时,永远别假设基础 API 能用。我花了两个小时在 globalShortcut 的各种参数和组合键上折腾,如果一开始我用三分钟跑一个快捷键单元测试,当时就能发现问题,省下后面四个小时的排查。

    我个人特别讨厌这种"文档里写了但实际不能用的"的情况——它不是一个 bug,它是一个信任问题。你让开发者怎么相信这个平台上的其他 API 是可靠的?

    如果你要在鸿蒙上做 Electron 开发,快捷键这部分我的建议就两条:

    • 别用 globalShortcut,至少等 arkui-x 下一个大版本看修没修
    • 非要用的话,hidumper 绕路能用,但仅限调试/开发阶段

    你遇到过类似的情况吗?有更好的方案欢迎留言。反正我是短期不会再碰 globalShortcut 了。

    本文遵循 MIT 协议,转载请注明出处。

    赞(0)
    未经允许不得转载:171主机测评 » Electron 鸿蒙快捷键全失灵,我排查了六个小时
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址