Support system-wide (OS global) keybindings#323871
Merged
Merged
Conversation
Allow user keybindings in keybindings.json to be marked with "systemWide": true so they register as operating-system global shortcuts that fire even when the window is not focused. - Thread the systemWide flag through IUserFriendlyKeybinding, ResolvedKeybindingItem and KeybindingIO (read + serialize) - New GlobalKeybindingsMainService owns Electron's globalShortcut, reconciles per-window registrations, resolves conflicts deterministically and routes triggers through the existing vscode:runAction path - New renderer contribution syncs opted-in bindings to the main process, gated behind the experimental, off-by-default setting keyboard.enableSystemWideKeybindings with a one-time confirmation dialog - Enable the GlobalShortcutsPortal feature on Linux/Wayland Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Force-focusing the routing (main) window before dispatching the command pulled it to the foreground even when the command opens/reveals a different window (e.g. openAgentsWindow reveals the agents window), producing a visible flicker. Remove the force-focus and let the invoked command control what is surfaced/focused, matching every other vscode:runAction sender. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds a generic command that brings the current window to the foreground and focuses it using FocusMode.Force (which works even when the application is not the active app). This lets users compose system-wide keybindings via runCommands to reveal the window before running a command that surfaces UI in it, e.g. [workbench.action.focusWindow, workbench.action.quickOpen]. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds experimental support for system-wide (OS global) keybindings on desktop by introducing an opt-in "systemWide": true flag in keybindings.json, syncing eligible shortcuts to the main process, and routing triggers back into a window via the existing vscode:runAction path.
Changes:
- Extend keybinding IO + resolved keybinding items to parse, persist, and carry a
systemWideflag. - Add a workbench contribution that selects/dedupes eligible system-wide bindings and syncs them to a new main-process global shortcut service.
- Introduce
workbench.action.focusWindow(usingFocusMode.Force) to supportrunCommandscompositions for commands that need the window raised first.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/workbench.desktop.main.ts | Loads the new system-wide keybindings renderer contribution in the desktop workbench entrypoint. |
| src/vs/workbench/test/electron-browser/workbenchTestServices.ts | Extends the test native host service with a stub for syncing system-wide keybindings. |
| src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts | Adds tests for reading/writing/round-tripping the systemWide flag. |
| src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts | Adds a test ensuring editing preserves systemWide on unrelated entries. |
| src/vs/workbench/services/keybinding/common/keybindingIO.ts | Parses systemWide from user keybindings and writes it back only when true. |
| src/vs/workbench/services/keybinding/browser/keybindingService.ts | Threads systemWide into ResolvedKeybindingItem and exposes schema support in keybindings.json. |
| src/vs/workbench/electron-browser/window.ts | Adds a systemWideKeybinding run-action path that preserves positional args (no { from } sentinel). |
| src/vs/workbench/electron-browser/desktop.contribution.ts | Registers the new workbench.action.focusWindow action on desktop. |
| src/vs/workbench/electron-browser/actions/windowActions.ts | Implements FocusWindowAction using IHostService.focus(..., FocusMode.Force). |
| src/vs/workbench/contrib/keybindings/test/electron-browser/systemWideKeybindings.test.ts | Adds unit tests for renderer-side selection/dedupe/unsupported reporting. |
| src/vs/workbench/contrib/keybindings/electron-browser/systemWideKeybindings.contribution.ts | Implements the renderer contribution: gating setting, one-time confirmation, warnings, and nativeHost sync. |
| src/vs/platform/window/common/window.ts | Extends run-action request origin union to include systemWideKeybinding. |
| src/vs/platform/native/electron-main/nativeHostMainService.ts | Adds an IPC entrypoint to update per-window system-wide bindings via the new main service. |
| src/vs/platform/native/common/native.ts | Defines native DTOs + native host API for syncing system-wide keybindings. |
| src/vs/platform/keybinding/common/resolvedKeybindingItem.ts | Adds systemWide onto resolved keybinding items with a default of false. |
| src/vs/platform/keybinding/common/keybinding.ts | Extends IUserFriendlyKeybinding to include optional systemWide. |
| src/vs/platform/globalKeybindings/test/electron-main/globalKeybindingsMainService.test.ts | Adds main-process unit tests for registration, conflict resolution, routing, and teardown. |
| src/vs/platform/globalKeybindings/electron-main/globalKeybindingsMainService.ts | Introduces GlobalKeybindingsMainService to own Electron globalShortcut registration and route triggers to windows. |
| src/vs/code/electron-main/app.ts | Registers the new main-process global keybindings service and injects Electron’s globalShortcut. |
| src/vs/main.ts | Enables Electron’s Wayland global shortcuts portal feature flag on Linux. |
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 3
- Review effort level: Low
- keybindingService.ts: correct 'systemWide' schema default to false to match KeybindingIO parsing (defaults to false when absent/invalid) - systemWideKeybindings.contribution.ts: add '&& denotes a mnemonic' translator comment to the Enable button label - keybindingEditing.test.ts: fix test title grammar (a user) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Removes the experimental `keyboard.enableSystemWideKeybindings` setting so the feature is always active: any user keybinding with "systemWide": true is a candidate. The one-time confirmation dialog is retained and now serves as the opt-out - its Enable/Disable choice is persisted as a tri-state consent (unset -> ask, granted -> register, denied -> stay off and never re-ask). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…rompt
The system-wide keybindings feature is always on, so the first-run dialog no
longer needs to grant/deny permission. Replace the Enable/Disable confirm dialog
with a single-button informational notice ("I Understand") shown once before the
first registration. Collapses the tri-state consent to a boolean acknowledged
flag; the feature has no decline path, so there is no stuck off-state.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
ulugbekna
marked this pull request as ready for review
July 1, 2026 18:50
ulugbekna
enabled auto-merge (squash)
July 1, 2026 18:51
Contributor
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: @deepak1556Matched files:
|
DonJayamanne
approved these changes
Jul 1, 2026
This was referenced Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Some workflows benefit from a keybinding that works even when VS Code does not have OS focus, for example a global shortcut to open or reveal the Agents window from anywhere. Today VS Code keybindings only fire when a VS Code window is focused.
What
Adds an opt-in
"systemWide": trueflag for entries inkeybindings.json:{ "key": "ctrl+cmd+a", "command": "workbench.action.openAgentsWindow", "systemWide": true }When set, the shortcut is registered as an OS-global shortcut, so pressing it runs the command even while another application is focused.
Opt-in is per keybinding - there is no global on/off setting to flip. Any user keybinding that carries
"systemWide": trueis registered as a global shortcut. Because global shortcuts are captured OS-wide (they take the key combination away from other apps while VS Code is running), the first time such a binding is registered the user sees a one-time informational notice with a single "I Understand" button, so the behavior is never a surprise.How it works
End to end: parse the flag, thread it through the resolved keybinding model, have a renderer contribution select the opted-in bindings and sync them to the main process over a new
nativeHostIPC, and have a new main-process service own Electron'sglobalShortcut. On trigger, the service routes the command back into a window renderer via the existingvscode:runActionpath.Focus behavior
A system-wide shortcut fires while VS Code is typically unfocused, so the trigger deliberately does not pull the routing window to the foreground. The invoked command decides what to surface:
openAgentsWindow, for instance, reveals and focuses the Agents window on its own, with no flicker of the main window.For commands that surface UI in the current window (e.g. Quick Open) and therefore do want the window revealed, this PR also adds a generic
workbench.action.focusWindowcommand that raises the current window usingFocusMode.Force(works even when the app is not active). Users can compose it viarunCommands:{ "key": "ctrl+cmd+p", "command": "runCommands", "args": { "commands": ["workbench.action.focusWindow", "workbench.action.quickOpen"] }, "systemWide": true }Changes by area (suggested review order)
Review roughly top to bottom: shared contracts first, then parsing, then the renderer, then the main process, then trigger routing, then the focus command, then tests.
1. Shared contracts and data model
platform/keybinding/common/keybinding.ts- addssystemWide?: booleantoIUserFriendlyKeybinding(the user-facingkeybindings.jsonshape), documented as desktop-only and user-keybindings-only.platform/keybinding/common/resolvedKeybindingItem.ts- carriessystemWideonResolvedKeybindingItemas a new trailing constructor arg defaulting tofalse, so defaults and extension keybindings are never system-wide.platform/native/common/native.ts- new IPC contract:INativeSystemWideKeybinding(accelerator, commandId, args, label),INativeSystemWideKeybindingResult(failed[]), andsyncSystemWideKeybindings(...)on the native host service.platform/window/common/window.ts- adds'systemWideKeybinding'to theINativeRunActionInWindowRequest.fromunion.2. Parsing and serialization (
keybindings.json<-> model)workbench/services/keybinding/common/keybindingIO.ts- readssystemWidefrom JSON (defaults tofalsewhen absent/non-boolean) and writes it back out; adds the field toIUserKeybindingItem.workbench/services/keybinding/browser/keybindingService.ts- threadsitem.systemWideinto bothResolvedKeybindingItemconstruction paths, and registers thesystemWideproperty in thekeybindings.jsonJSON schema (documented as desktop-only, single-combination-only,whenignored for the global trigger, with a one-time notice on first registration).3. Renderer contribution (selection + sync)
workbench/contrib/keybindings/electron-browser/systemWideKeybindings.contribution.ts- the core renderer logic. Exposes a pureselectSystemWideKeybindings()(filter opted-in bindings, translate to Electron accelerators, dedupe, drop unsupported chords with a warning), debounces syncing via aRunOnceScheduler, shows a one-time informational notice before the first registration (persisting an "acknowledged" flag in application storage so it appears only once), and callsnativeHostService.syncSystemWideKeybindings(...).workbench/workbench.desktop.main.ts- registers the contribution in the desktop workbench.4. Main process (owns the OS globalShortcut)
platform/globalKeybindings/electron-main/globalKeybindingsMainService.ts- the heart of the feature. Owns a per-window registry, registers/unregisters accelerators via an injectedglobalShortcut(IGlobalShortcutRegistry, for testability), resolves cross-window conflicts deterministically, returns labels that failed to register, and on trigger routes the command to a target window viasendWhenReady('vscode:runAction', ...). Cleans up on window destroy and shutdown.platform/native/electron-main/nativeHostMainService.ts- implementssyncSystemWideKeybindingsby forwarding to the service (keyed by the callingwindowId).code/electron-main/app.ts- registers the service in DI, injecting Electron'sglobalShortcutas a constructor dependency.main.ts- enables Electron'sGlobalShortcutsPortalfeature on Linux soglobalShortcutworks under Wayland (no-op elsewhere).5. Trigger routing (main -> renderer -> command)
workbench/electron-browser/window.ts- handlesfrom: 'systemWideKeybinding'in thevscode:runActionhandler, running the command with exactly the configured args and intentionally not appending a{ from }sentinel.6. Focus behavior and the focusWindow command
workbench/electron-browser/actions/windowActions.ts- addsFocusWindowAction(workbench.action.focusWindow) that raises the current window viahostService.focus(getActiveWindow(), { mode: FocusMode.Force }).workbench/electron-browser/desktop.contribution.ts- registers the new action.7. Tests
platform/globalKeybindings/test/electron-main/globalKeybindingsMainService.test.ts- registration, dedupe, cross-window conflict resolution, trigger payload/routing (asserts the routing window is not force-focused), and teardown on window close/shutdown.workbench/contrib/keybindings/test/electron-browser/systemWideKeybindings.test.ts-selectSystemWideKeybindings()filtering, args/when handling, chord rejection, and first-wins dedupe.workbench/services/keybinding/test/browser/keybindingIO.test.ts- round-trips thesystemWideflag.workbench/services/keybinding/test/browser/keybindingEditing.test.ts- guards against data loss: editing one entry preservessystemWideon others.workbench/test/electron-browser/workbenchTestServices.ts- stub for the new native host method.Notes for reviewers
"systemWide": true); there is no enablement setting. The one-time informational notice makes the OS-global capture explicit before the first registration and is purely a heads-up, so there is no "off" state to get stuck in.GlobalKeybindingsMainServicetakes Electron'sglobalShortcutas a constructor dependency so it stays unit-testable.globalShortcutcapabilities.