From 0b2f53cb811691417ebb8b313ad9f47af69541cc Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Wed, 17 Jun 2026 22:48:05 -0400 Subject: [PATCH 1/2] fix: surface notification when globalShortcut.register() fails When Electron's globalShortcut.register() returns false (silent failure due to OS-level or another app's conflict), CrossOver previously logged nothing and left the shortcut silently inactive. - registerShortcut() in keyboard.js now returns the boolean result and logs a console warning on failure - registerKeyboardShortcuts() in crossover.js collects failed accelerators and fires a user-facing notification with the conflicting combos - Notification is wrapped in try/catch so early startup calls (before renderer is ready) degrade gracefully to log-only Fixes the 'shortcut stops working after relaunch on Windows 11' pattern reported in #516: registration can fail silently at startup when a game or overlay has already claimed the combo. Co-Authored-By: Paperclip --- src/main/crossover.js | 40 +++++++++++++++++++++++++++++++++++++--- src/main/keyboard.js | 14 +++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/main/crossover.js b/src/main/crossover.js index b710441b..98dca763 100644 --- a/src/main/crossover.js +++ b/src/main/crossover.js @@ -185,8 +185,13 @@ const registerKeyboardShortcuts = () => { // Register all shortcuts const { keybinds } = Preferences.getDefaults() const custom = preferences.value( 'keybinds' ) // Defaults + const failed = [] + for ( const shortcut of keyboardShortcuts() ) { + let accelerator + let registered + // Custom shortcuts if ( custom[shortcut.action] === '' ) { @@ -196,20 +201,49 @@ const registerKeyboardShortcuts = () => { // If a custom shortcut exists for this action log.info( `Custom keybind for ${shortcut.action}` ) - keyboard.registerShortcut( custom[shortcut.action], shortcut.fn ) + accelerator = custom[shortcut.action] + registered = keyboard.registerShortcut( accelerator, shortcut.fn ) } else if ( keybinds[shortcut.action] ) { // Set default keybind - keyboard.registerShortcut( keybinds[shortcut.action], shortcut.fn ) + accelerator = keybinds[shortcut.action] + registered = keyboard.registerShortcut( accelerator, shortcut.fn ) } else { // Fallback to internal bind - THIS SHOULDNT HAPPEN // if it does you forgot to add a default keybind for this shortcut log.info( 'ERROR - you likely forgot to add a default keybind for this shortcut: ', shortcut ) - keyboard.registerShortcut( shortcut.keybind, shortcut.fn ) + accelerator = shortcut.keybind + registered = keyboard.registerShortcut( accelerator, shortcut.fn ) + + } + + if ( accelerator && registered === false ) { + + failed.push( accelerator ) + + } + + } + + if ( failed.length > 0 ) { + + log.warn( `Shortcut registration failed for: ${failed.join( ', ' )}` ) + + // Notify the user — renderer must be ready; guard against early startup calls + try { + + const notification = require( './notification' ) + notification( { + title: 'Keyboard Shortcut Conflict', + body: `Could not register: ${failed.join( ', ' )}. Another app may be using this combo. Try rebinding in Settings.`, + } ) + + } catch ( _err ) { + // Window not yet ready — failure already logged above } } diff --git a/src/main/keyboard.js b/src/main/keyboard.js index 1cf5bf31..6bf7e743 100644 --- a/src/main/keyboard.js +++ b/src/main/keyboard.js @@ -22,7 +22,19 @@ const registerEscape = ( action = keyboard.escapeAction ) => { } -const registerShortcut = ( ...args ) => globalShortcut.register( ...args ) +const registerShortcut = ( accelerator, fn ) => { + + const registered = globalShortcut.register( accelerator, fn ) + if ( !registered ) { + + // eslint-disable-next-line no-console + console.warn( `[CrossOver] globalShortcut.register failed for: ${accelerator} (another app may have claimed this combo)` ) + + } + + return registered + +} const unregisterShortcut = ( ...args ) => globalShortcut.unregister( ...args ) From e49ba28d0417c70f0bd4e8c4ce24265224383948 Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Thu, 18 Jun 2026 07:06:52 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20Gemini=20review=20?= =?UTF-8?q?=E2=80=94=20explicit=20window=20guard,=20use=20log=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace try/catch anti-pattern with explicit windows.win && !isDestroyed() check - Import log module in keyboard.js; replace console.warn with log.warn Co-Authored-By: Paperclip --- src/main/crossover.js | 7 ++----- src/main/keyboard.js | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/crossover.js b/src/main/crossover.js index 98dca763..c9f6cc85 100644 --- a/src/main/crossover.js +++ b/src/main/crossover.js @@ -232,8 +232,8 @@ const registerKeyboardShortcuts = () => { log.warn( `Shortcut registration failed for: ${failed.join( ', ' )}` ) - // Notify the user — renderer must be ready; guard against early startup calls - try { + // Notify the user — only if the renderer window is ready + if ( windows.win && !windows.win.isDestroyed() ) { const notification = require( './notification' ) notification( { @@ -241,9 +241,6 @@ const registerKeyboardShortcuts = () => { body: `Could not register: ${failed.join( ', ' )}. Another app may be using this combo. Try rebinding in Settings.`, } ) - } catch ( _err ) { - - // Window not yet ready — failure already logged above } } diff --git a/src/main/keyboard.js b/src/main/keyboard.js index 6bf7e743..91a41542 100644 --- a/src/main/keyboard.js +++ b/src/main/keyboard.js @@ -1,4 +1,5 @@ const { globalShortcut } = require( 'electron' ) +const log = require( './log' ) const windows = require( './windows' ) const escapeAction = () => { @@ -27,8 +28,7 @@ const registerShortcut = ( accelerator, fn ) => { const registered = globalShortcut.register( accelerator, fn ) if ( !registered ) { - // eslint-disable-next-line no-console - console.warn( `[CrossOver] globalShortcut.register failed for: ${accelerator} (another app may have claimed this combo)` ) + log.warn( `globalShortcut.register failed for: ${accelerator} (another app may have claimed this combo)` ) }