@@ -85,19 +85,19 @@ const writeConfig = (next: HotkeyConfig): HotkeyConfig => {
8585 return readConfig ( ) ;
8686} ;
8787
88- /** 卸载所有 globalShortcut + 清空冲突 */
89- const unregisterAll = ( ) : void => {
88+ /** 卸载 Electron 快捷键并清空冲突 */
89+ const unregisterElectronShortcuts = ( ) : void => {
9090 globalShortcut . unregisterAll ( ) ;
9191 conflicts = [ ] ;
9292} ;
9393
9494/**
9595 * 注册全部 global accelerators(仅 Electron 模式)
9696 * 同 accelerator 重复 / OS 占用 / 解析异常 都计入 conflicts
97- * globalEnabled = false 时只 unregisterAll 不注册新的
97+ * globalEnabled = false 时只解绑, 不注册新的
9898 */
99- const applyAll = ( ) : void => {
100- unregisterAll ( ) ;
99+ const registerElectronShortcuts = ( ) : void => {
100+ unregisterElectronShortcuts ( ) ;
101101 if ( currentMode === "portal" ) return ;
102102 const config = readConfig ( ) ;
103103 if ( ! config . globalEnabled ) {
@@ -157,6 +157,16 @@ const buildPortalShortcuts = (): PortalShortcut[] =>
157157 : undefined ,
158158 } ) ) ;
159159
160+ /** 绑定 Portal 快捷键,失败时记录原因 */
161+ const bindPortalShortcuts = async ( ) : Promise < boolean > => {
162+ const res = await portalModule ! . bindShortcuts ( buildPortalShortcuts ( ) ) ;
163+ if ( ! res . ok ) {
164+ coreLog . error ( `[portal] 绑定全局快捷键失败: ${ res . error ?? "未知错误" } ` ) ;
165+ return false ;
166+ }
167+ return true ;
168+ } ;
169+
160170/** 解绑当前 portal 会话并释放绑定(globalEnabled 关闭 / 退出 portal)入串行链 */
161171const portalUnbindAll = ( ) : Promise < void > =>
162172 enqueueTransition ( async ( ) => {
@@ -172,13 +182,33 @@ const portalUnbindAll = (): Promise<void> =>
172182const portalBindAll = ( ) : Promise < void > =>
173183 enqueueTransition ( async ( ) => {
174184 if ( currentMode !== "portal" || ! portalModule ) return ;
175- // 原生层绑定前已关闭旧会话,失败后实际无任何快捷键;直接 await switchToElectron()
176- // 会在当前节点内等自己排入链尾的节点而死锁,故作为链的下一节点排队回退
177- const res = await portalModule . bindShortcuts ( buildPortalShortcuts ( ) ) ;
178- if ( ! res . ok ) {
179- coreLog . error ( `[portal] 绑定全局快捷键失败: ${ res . error ?? "未知错误" } ` ) ;
185+ if ( ! readConfig ( ) . globalEnabled || ( await bindPortalShortcuts ( ) ) ) return ;
186+ // 回退操作必须排在当前 transition 之后,避免等待自身造成死锁。
187+ void switchToElectron ( ) ;
188+ } ) ;
189+
190+ /** 切换到 Portal 模式并绑定当前配置 */
191+ const switchToPortal = ( epoch : number ) : Promise < void > =>
192+ enqueueTransition ( async ( ) => {
193+ if ( epoch !== portalEpoch ) return ;
194+
195+ currentMode = "portal" ;
196+ unregisterElectronShortcuts ( ) ;
197+
198+ const config = readConfig ( ) ;
199+ if ( config . globalEnabled && ! ( await bindPortalShortcuts ( ) ) ) {
200+ if ( epoch !== portalEpoch ) {
201+ void portalModule ! . unbindShortcuts ( ) ;
202+ return ;
203+ }
180204 void switchToElectron ( ) ;
205+ return ;
181206 }
207+
208+ portalBound = config . globalEnabled ;
209+ conflicts = [ ] ;
210+ broadcast ( "hotkey:conflicts" , conflicts ) ;
211+ broadcastMode ( ) ;
182212 } ) ;
183213
184214/**
@@ -193,7 +223,7 @@ const switchToElectron = (): Promise<void> =>
193223 portalBound = false ;
194224 // 等待解绑完成释放按键后再注册 Electron,避免注册被占用
195225 if ( portalModule ) await portalModule . unbindShortcuts ( ) ;
196- applyAll ( ) ;
226+ registerElectronShortcuts ( ) ;
197227 broadcastMode ( ) ;
198228 } ) ;
199229
@@ -244,32 +274,7 @@ const ensurePortalReady = async (): Promise<void> => {
244274 await descriptionsReady ;
245275 if ( epoch !== portalEpoch ) return ;
246276
247- // 绑定与解绑入同一串行链:确保前一个 switchToElectron 的解绑先完成,再切换并绑定 portal
248- await enqueueTransition ( async ( ) => {
249- if ( epoch !== portalEpoch ) return ;
250- currentMode = "portal" ;
251- // 从 Electron 模式切回 portal 时,同键可能已在两个后端注册:先释放 globalShortcut,
252- // 避免一次按键触发两次或与 portal preferredTrigger 冲突(绑定失败时 switchToElectron 会重注册)
253- unregisterAll ( ) ;
254- // globalEnabled=false 时不注册任何 portal 动作(与 Electron 模式语义一致)
255- const config = readConfig ( ) ;
256- if ( config . globalEnabled ) {
257- const res = await portalModule ! . bindShortcuts ( buildPortalShortcuts ( ) ) ;
258- if ( epoch !== portalEpoch ) {
259- void portalModule ! . unbindShortcuts ( ) ;
260- return ;
261- }
262- if ( ! res . ok ) {
263- coreLog . error ( `[portal] 绑定全局快捷键失败: ${ res . error ?? "未知错误" } ` ) ;
264- await switchToElectron ( ) ;
265- return ;
266- }
267- }
268- portalBound = config . globalEnabled ;
269- conflicts = [ ] ;
270- broadcast ( "hotkey:conflicts" , conflicts ) ;
271- broadcastMode ( ) ;
272- } ) ;
277+ await switchToPortal ( epoch ) ;
273278} ;
274279
275280/** 触发一次 portal 初始化(幂等,进行中则复用) */
@@ -320,7 +325,7 @@ export const initGlobalHotkey = (): void => {
320325
321326/** 退出清理 */
322327export const cleanupGlobalHotkey = ( ) : void => {
323- unregisterAll ( ) ;
328+ unregisterElectronShortcuts ( ) ;
324329 if ( portalModule ) void portalModule . shutdown ( ) ;
325330} ;
326331
@@ -406,20 +411,20 @@ export const probeAccelerator = (accelerator: string): boolean => {
406411 ok = false ;
407412 }
408413 if ( wasRegistered ) {
409- applyAll ( ) ;
414+ registerElectronShortcuts ( ) ;
410415 }
411416 return ok ;
412417} ;
413418
414419/**
415420 * 绑定变更后的重注册
416421 * - Electron 模式:整体重注册 globalShortcut
417- * - portal 模式:globalEnabled 翻转时同步绑定/解绑,其余配置变更不重建会话
422+ * - Portal 模式:只在 globalEnabled 翻转时绑定或解绑会话
418423 * (避免覆盖用户在系统设置侧的自定义)
419424 */
420425const syncRegistration = ( ) : void => {
421426 if ( currentMode !== "portal" ) {
422- applyAll ( ) ;
427+ registerElectronShortcuts ( ) ;
423428 return ;
424429 }
425430 const config = readConfig ( ) ;
0 commit comments