From 73ce7f9bed2c12bd3f644eb897b5052471d5ceb6 Mon Sep 17 00:00:00 2001 From: Austin Pickett Date: Fri, 24 Jul 2026 11:10:05 -0400 Subject: [PATCH] fix(desktop): make WSLg window-control buttons clickable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The renderer-drawn min/max/close called event.preventDefault() on pointerdown to keep WSLg from stealing keyboard focus on maximize, but preventDefault on pointerdown suppresses the synthesized click under WSLg's XWayland/RAIL compositor — the buttons rendered but never fired. Switch to stopPropagation (the pattern the native titlebar tools already use), which stops the drag region from swallowing the press while leaving the click intact. Focus reassertion after maximize is already handled main-side in performWindowControl via win.focus(). --- .../src/app/shell/wslg-window-controls.test.tsx | 7 +++++-- .../desktop/src/app/shell/wslg-window-controls.tsx | 14 ++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/app/shell/wslg-window-controls.test.tsx b/apps/desktop/src/app/shell/wslg-window-controls.test.tsx index 5897187a5bc..8c5bc3035b6 100644 --- a/apps/desktop/src/app/shell/wslg-window-controls.test.tsx +++ b/apps/desktop/src/app/shell/wslg-window-controls.test.tsx @@ -72,7 +72,7 @@ describe('WslgWindowControls', () => { expect(screen.queryByLabelText('Window controls')).toBeNull() }) - it('prevents pointer activation from stealing renderer focus', () => { + it('stops pointerdown propagation without cancelling the click', () => { desktopWindow.hermesDesktop = { windowControls } as unknown as Window['hermesDesktop'] renderControls() const event = new MouseEvent('pointerdown', { bubbles: true, cancelable: true }) @@ -81,7 +81,10 @@ describe('WslgWindowControls', () => { fireEvent(button, event) fireEvent.click(button) - expect(event.defaultPrevented).toBe(true) + // preventDefault on pointerdown kills the synthesized click under WSLg's + // RAIL compositor, so the button must NOT cancel the default — only stop + // propagation so the drag region doesn't swallow the press. + expect(event.defaultPrevented).toBe(false) expect(windowControls.toggleMaximize).toHaveBeenCalledOnce() }) diff --git a/apps/desktop/src/app/shell/wslg-window-controls.tsx b/apps/desktop/src/app/shell/wslg-window-controls.tsx index a623533bd1e..676c172c44a 100644 --- a/apps/desktop/src/app/shell/wslg-window-controls.tsx +++ b/apps/desktop/src/app/shell/wslg-window-controls.tsx @@ -21,7 +21,13 @@ interface WslgWindowControlsProps { const buttonClass = 'grid h-full w-[46px] place-items-center border-0 bg-transparent p-0 text-muted-foreground transition-colors duration-75 select-none [-webkit-app-region:no-drag] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring hover:bg-white/10 hover:text-foreground active:bg-white/15' -const preserveRendererFocus = (event: PointerEvent) => event.preventDefault() +// Match the native titlebar tools: stopPropagation (NOT preventDefault) on +// pointerdown. preventDefault on pointerdown suppresses the synthesized click +// under WSLg's XWayland/RAIL compositor, so the buttons render but never fire. +// stopPropagation keeps the drag region from swallowing the press while leaving +// the click intact; keyboard-focus reassertion after maximize is handled on the +// main side in performWindowControl (win.focus()). +const stopTitlebarDrag = (event: PointerEvent) => event.stopPropagation() export function WslgWindowControls({ isFullscreen, isMaximized }: WslgWindowControlsProps) { const location = useLocation() @@ -43,7 +49,7 @@ export function WslgWindowControls({ isFullscreen, isMaximized }: WslgWindowCont aria-label="Minimize window" className={buttonClass} onClick={controls.minimize} - onPointerDown={preserveRendererFocus} + onPointerDown={stopTitlebarDrag} type="button" > @@ -52,7 +58,7 @@ export function WslgWindowControls({ isFullscreen, isMaximized }: WslgWindowCont aria-label={isMaximized ? 'Restore window' : 'Maximize window'} className={buttonClass} onClick={controls.toggleMaximize} - onPointerDown={preserveRendererFocus} + onPointerDown={stopTitlebarDrag} type="button" > @@ -64,7 +70,7 @@ export function WslgWindowControls({ isFullscreen, isMaximized }: WslgWindowCont 'hover:bg-[#c42b1c] hover:text-white active:bg-[#b3271a] active:text-white dark:hover:bg-[#c42b1c]' )} onClick={controls.close} - onPointerDown={preserveRendererFocus} + onPointerDown={stopTitlebarDrag} type="button" >