8000 fix(browser): let Ctrl+W close the browser tab from inside a page by alejandronava562 · Pull Request #432 · amirlehmam/wmux · GitHub
[go: up one dir, main page]

Skip to content

fix(browser): let Ctrl+W close the browser tab from inside a page - #432

Merged
amirlehmam merged 2 commits into
amirlehmam:masterfrom
alejandronava562:fix/browser-page-tab-shortcuts
Nov 4, 2026
Merged

amirlehmam merged 2 commits into
amirlehmam:masterfrom
alejandronava562:fix/browser-page-tab-shortcuts

Conversation

@alejandronava562
Copy link
Copy Markdown
Contributor

Follow-up to #431. With a browser pane's page focused, none of wmux's shortcuts fire. After #431, Ctrl+W there does nothing at all; this makes it close the browser tab.

Builds on #431. This branch contains #431's commit. Without it, the forwarded Ctrl+W would still reach Electron's default menu and close the window. Merge #431 first, or merge this PR, which includes it.

Why the shortcuts never fired

A focused <webview> guest receives its keys before the desktop renderer does. They never reach the document-level listeners in useKeyboardShortcuts.

Main is the only place that sees those keys, through the guest's before-input-event. But that event fires before the page handles the key, so main can't tell whether the page is going to use it.

Fix

Main (webview-guest-keys.ts, shared/guest-key.ts).

  • Forwards a guest's key-downs to the window that hosts it, over browser:guest-key.
  • Only forwards Ctrl, Alt or Meta chords and function keys. Plain typing and IME composition never leave the page.
  • Never calls preventDefault. Main doesn't know the user's bindings, and keeping a second copy of claimsKeyEvent there is exactly what AGENTS.md rules out.

Renderer (useKeyboardShortcuts).

  • Matches the forwarded key against the user's bindings, using the same matchesBinding the DOM path uses.
  • Only runs actions in GLOBAL_IN_PAGE (shortcut-target.ts), the counterpart of GLOBAL_IN_EDITOR.
  • The page has already received the key, so anything in that set fires alongside the page. That's why the set is limited to what a browser reserves from pages: close, new, reopen and cycle tabs, plus new, close and cycle workspaces, and new window.
  • Ctrl+B is bold in every web editor and Ctrl+F is the page's own search, so both stay with the page.
  • The set is keyed by action, not by chord, so rebinds keep working.
  • Auto-repeat is dropped, so holding Ctrl+W closes one tab, not one tab per repeat.

Closing the right tab (utils/guest-pane.ts).

  • The key carries the guest's webContents id, which resolves to the pane whose <webview> it is.
  • Clicking into a page already focuses its pane (Clicking in browser does not focus pane. #388). But focus can reach a page without that click, and every tab action works on the focused pane.
  • When the two disagree, the page's pane is focused first and the key is replayed once the hook re-runs for it, within 1 second. Ctrl+W therefore never closes a tab in another pane.

To let another action work from a page, add it to GLOBAL_IN_PAGE. Only do that if no web app would reasonably use its chord.

Verification

I ran a dev build side by side with the installed wmux under WMUX_INSTANCE, with a terminal pane and a browser pane, and sent real input to the guests with webContents.sendInputEvent. The table shows the store state:

Scenario Result
Focus the left terminal, then put focus in the right pane's page from main (wmux's pane focus doesn't move), press Ctrl+W right browser pane closed, left untouched
Ctrl+W in a page in a pane with tabs t, b*, t that browser tab closed: t, t*
Ctrl+T in a page new tab opened in that pane
Ctrl+B in a page sidebar unchanged, page's keydown saw Ctrl+b
plain w typed in a page nothing forwarded, nothing changed
  • Unit tests: guest-key.test.ts covers the input filter and the main forwarder. guest-pane.test.ts covers the guest → pane lookup. shortcut-target.test.ts covers the GLOBAL_IN_PAGE contents.
  • tsc passes for both projects. The only renderer errors are katex/mermaid being missing from my local node_modules, which is unrelated to this change.
  • eslint is clean on the changed files.

alejandronava562 and others added 2 commits November 4, 2026 11:56
A focused <webview> guest gets its keys before the desktop renderer, so
none of wmux's shortcuts fired while a page had focus. Ctrl+W did
nothing (or, before the previous commit, quit wmux).

Main now forwards a guest's shortcut-shaped key-downs (Ctrl/Alt/Meta
chords and function keys) to its host window without preventing them.
The page keeps every key, so the desktop only answers with the
tab-level actions a browser reserves from pages (GLOBAL_IN_PAGE):
close, open, reopen and cycle tabs, workspaces and windows. A web app's
Ctrl+B or Ctrl+F stays the page's.

The key is resolved to the pane hosting that guest. If focus reached
the page without moving wmux's pane focus there, the pane is focused
first and the key replayed, so Ctrl+W never closes a tab in another
pane.
The renderer half of the guest key forwarding (drop auto-repeats, match
the user's bindings, answer only GLOBAL_IN_PAGE actions) lived inline in
the useKeyboardShortcuts effect, so removing the repeat guard or the
GLOBAL_IN_PAGE filter left every test green. Move the decision into an
exported pure guestShortcutAction(key, shortcuts) and have the hook call
it. Behaviour is unchanged: the hook still drops repeats before focusing
the guest's pane, and still focuses before matching and replaying.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@amirlehmam
amirlehmam force-pushed the fix/browser-page-tab-shortcuts branch from 9303933 to eff3e5e Compare November 4, 2026 10:57
@amirlehmam
Copy link
Copy Markdown
94F4
Owner

Thanks, this fills the gap #431 left. The approach holds up under review:

  • main never calls preventDefault;
  • the webContents id is read in main, so a page can't spoof it;
  • IME composition and auto-repeat are dropped;
  • matching never reads a digit off e.key, so AZERTY layouts are fine.

#431 is merged, so I rebased this onto master with only your commit. I added one commit on top: the renderer half (drop repeats, match the user's bindings, keep only GLOBAL_IN_PAGE actions) was inline in the effect, and removing either guard left every test green. It is now a pure guestShortcutAction helper next to matchesBinding, with tests for repeat, Ctrl+W, Ctrl+B staying with the page, rebinding and unbound actions. The focus-then-replay order is unchanged. Merging.

@amirlehmam
amirlehmam merged commit 6807444 into amirlehmam:master Nov 4, 2026
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.

2 participants

0