Skip to content

feat: 在 linux 下使用 portal 注册全局快捷键 - #162

Draft
MoYingJi wants to merge 4 commits into
SPlayer-Dev:devfrom
MoYingJi:linux-portal
Draft

feat: 在 linux 下使用 portal 注册全局快捷键#162
MoYingJi wants to merge 4 commits into
SPlayer-Dev:devfrom
MoYingJi:linux-portal

Conversation

@MoYingJi

@MoYingJi MoYingJi commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

改动类型

  • 新功能(feat)
  • 缺陷修复(fix)
  • 重构 / 优化(不改变对外行为)
  • 文档(docs)
  • 其他(请在「改动说明」中注明)

是否包含破坏性变更

  • 是(请在「改动说明」中详细描述)

改动说明

在 Linux 下使用 xdg-desktop-portal 注册全局快捷键

测试情况

包含巨量 AI 生成代码,已经人类初步审阅

已在 KDE Plasma Wayland 下测试通过,测试了使用新增的 portal 模式,和关闭 portalGlobalShortcuts 回退的 electron 模式(非 Linux 系统下回退或在 Linux 下手动回退)

自查清单

  • 本 PR 只包含一个主要功能 / 修复,没有夹带无关改动
  • 已在本地完整测试通过;AI 生成的代码同样自行测试并审阅过,未做未经验证的提交
  • 已运行 pnpm format,并确认 pnpm typecheckpnpm lint 通过
  • 改动涉及原生模块时已 pnpm build:native 验证;未手写 native/*/index.d.ts
  • 已向 dev 分支提交

@MoYingJi
MoYingJi force-pushed the linux-portal branch 2 times, most recently from 86cc6ce to fbebc4a Compare August 15, 2026 03:27
@MoYingJi
MoYingJi requested a balanced review from Copilot August 15, 2026 03:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

为 Linux 增加基于 XDG Desktop Portal 的全局快捷键,并在不可用时回退 Electron 实现。

Changes:

  • 新增 Rust linux-portal 原生模块及 portal 会话管理。
  • 扩展快捷键 IPC、状态管理和设置界面。
  • 补充构建配置、类型、国际化与 Wayland 文档。

Reviewed changes

Copilot reviewed 29 out of 32 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tsconfig.node.json 添加原生模块别名
src/stores/hotkey.ts 管理 portal 模式状态
src/settings/categories/hotkeys.ts 添加 portal 开关
src/main.ts 同步本地化快捷键描述
src/i18n/locales/zh-CN.json 添加中文文案
src/i18n/locales/en-US.json 添加英文文案并调整 Orpheus 文案
src/components/settings/custom/HotkeyConfig.vue 添加 portal 模式界面
shared/utils/accelerator.ts 转换 XDG 触发器格式
shared/types/settings.ts 扩展系统配置类型
shared/types/hotkey.ts 扩展快捷键 API 类型
shared/defaults/settings.ts 默认启用 portal
scripts/build-native.ts Linux 下构建新模块
native/linux-portal/src/shortcuts.rs 实现 portal 会话及绑定
native/linux-portal/src/model.rs 定义 NAPI 数据模型
native/linux-portal/src/logger.rs 实现原生日志
native/linux-portal/src/lib.rs 暴露 NAPI 接口
native/linux-portal/package.json 配置 NAPI 构建
native/linux-portal/index.d.ts 生成原生模块类型
native/linux-portal/Cargo.toml 配置 Rust 依赖
native/linux-portal/build.rs 初始化 NAPI 构建
electron/preload/index.ts 暴露 portal IPC
electron/main/utils/nativeLoader.ts 调整原生模块加载路径
electron/main/services/globalHotkey.ts 实现双后端快捷键服务
electron/main/ipc/hotkey.ts 注册 portal IPC
electron/main/ipc/config.ts 响应 portal 配置变更
electron.vite.config.ts 添加 Vite 路径别名
electron-builder.config.ts 打包 portal 二进制
docs/troubleshooting/wayland.md 更新中文 Wayland 指南
docs/en/troubleshooting/wayland.md 更新英文 Wayland 指南
components.d.ts 更新自动生成组件声明
Cargo.toml 加入 workspace
Cargo.lock 锁定新增依赖
Suppressed comments (1)

electron/main/services/globalHotkey.ts:233

  • 必修:快速执行“关闭 portal → 再开启”时,关闭操作会递增 portalEpoch,但再次开启仍复用旧的 readyPromise。旧初始化随后因 epoch 不匹配退出,finally 只清空 promise,不会按最新配置重新初始化,最终设置显示开启但模式仍是 Electron。请记录最新期望模式,并在旧任务结束后为新代际重新运行初始化。
const runPortalReady = (): Promise<void> => {
  if (!readyPromise) {
    readyPromise = ensurePortalReady().finally(() => {
      readyPromise = null;
    });
  }
  return readyPromise;

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread electron/main/services/globalHotkey.ts
Comment thread electron/main/services/globalHotkey.ts Outdated
Comment thread electron/main/services/globalHotkey.ts Outdated
Comment thread native/linux-portal/src/shortcuts.rs
Comment thread electron/main/services/globalHotkey.ts Outdated
@MoYingJi
MoYingJi force-pushed the linux-portal branch 2 times, most recently from e75e7ac to 1308f40 Compare August 15, 2026 04:21
@MoYingJi
MoYingJi requested a balanced review from Copilot August 15, 2026 05:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 30 changed files in this pull request and generated 2 comments.

Suppressed comments (4)

electron/main/services/globalHotkey.ts:225

  • 必修:能力探测失败后 portalCapability 会缓存一个 supported: false 的对象;用户之后关闭再开启 portal 时,这个 if 被跳过,代码会继续进入绑定流程,而不是保持 Electron 回退。请把 supported 判断移到缓存判断之外(或失败时清空缓存),确保缓存的“不支持”结果也会直接回退。
  if (!portalCapability) {
    portalCapability = await portalModule.detect();
    if (epoch !== portalEpoch) return;
    if (!portalCapability.supported) {

electron/main/services/globalHotkey.ts:259

  • 必修(并发):这里正在 enqueueTransition 的回调内部 await switchToElectron(),而后者会再次排入同一个 transitionChain。新节点必须等当前节点结束,当前节点又在等待新节点,因此首次 portal 绑定失败时会永久死锁,readyPromise/getGlobalMode 也不会完成。应在当前节点内直接完成回退,或抽出一个不再入队的内部切换函数。
      if (!res.ok) {
        coreLog.error(`[portal] 绑定全局快捷键失败: ${res.error ?? "未知错误"}`);
        await switchToElectron();
        return;

electron/main/services/globalHotkey.ts:219

  • 必修(并发):portal 信号被无条件转发。关闭全局快捷键时 unbindShortcuts() 是异步排队的,旧会话在关闭完成前仍可能发出 Activated;切回 Electron 时也可能收到已排队的旧信号,导致禁用后仍执行动作或切换期间重复触发。回调应按当前模式和 portalBound 丢弃失效事件。

This issue also appears on line 256 of the same file.

    portalModule.onActivated((_err, id) => {
      broadcast("hotkey:trigger", id);
    });

native/linux-portal/src/shortcuts.rs:261

  • 必修(并发):这里持有 activated_cb 的互斥锁调用外部 ThreadsafeFunction。即使当前使用 NonBlocking,调用路径仍不应占用共享状态锁,否则回调替换/关闭会被阻塞,未来调用语义变化时也可能形成重入死锁。请先克隆回调并释放 guard,再执行 call
        let cb = state()
            .activated_cb
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        if let Some(cb) = cb.as_ref() {

Comment thread electron/main/services/globalHotkey.ts Outdated
Comment on lines +175 to +179
const res = await portalModule.bindShortcuts(buildPortalShortcuts());
if (!res.ok) {
coreLog.error(`[portal] 绑定全局快捷键失败: ${res.error ?? "未知错误"}`);
}
});
Comment on lines +159 to +163
Err(AshpdError::Response(ResponseError::Cancelled)) => {
// 用户取消授权框:快捷键已注册,仅未分配 preferred_trigger,保留会话
info!("用户取消全局快捷键授权,快捷键仍注册成功(未分配默认触发器)");
Vec::new()
}
@MoYingJi
MoYingJi force-pushed the linux-portal branch 5 times, most recently from 11d4630 to 6e74ea4 Compare August 16, 2026 16:47
主要是为了在 linux 下打包方式导致的原生模块路径差异。顺便也把开发环境下的原生模块路径也换成了 `app.getAppPath()`
@MoYingJi
MoYingJi marked this pull request as ready for review August 18, 2026 15:15
@ycna07

ycna07 commented Aug 19, 2026

Copy link
Copy Markdown

我对splayer-next代码库不了解
可以问一下为什么不使用electron内置的global-shortcut吗?

@MoYingJi

MoYingJi commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

可以问一下为什么不使用electron内置的global-shortcut吗?

我知道有这个东西,文档里面我也写了关于它的一段。能用,但很灵车(难用我没在 SPlayer-Next 的文档说,我不确定是不是只有我一个人这样,我没搞清它如何工作🤔)。electron 那个文档说要启用 feature(修改:原来我看了这么多年的旧文档。但后面的问题不变),但我无论是否启用都会使用这个东西(kde plasma wayland),而且时而生效时而不生效的,有点奇怪... 我对 electron 那边也不了解

@MoYingJi
MoYingJi marked this pull request as draft August 19, 2026 11:15
@ycna07

ycna07 commented Aug 19, 2026

Copy link
Copy Markdown

看了下electron有数个相关issue没有解决,也许可以等这个pr合并...
electron/electron#52224

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants