From 26e98e898d6ed8418dcceab18d0eaa106fafb728 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:15:51 +0200 Subject: [PATCH] fix(desktop): reserve root Escape for leaving fullscreen instead of quitting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Physical-keyboard Escape at root Home now exits window fullscreen on Windows and Linux the way it already did on macOS, and never arms the press-back-again quit — so Escape aimed at fullscreen can't close the app. Remotes, gamepad B, and system back keep the double-press exit. close #1748 --- lib/screens/main_screen.dart | 23 +++++++++++++---------- test/screens/main_screen_layout_test.dart | 12 +++++++----- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index ae90f674..cc4ccd7c 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -78,14 +78,14 @@ import '../watch_together/watch_together.dart'; // browse rail can import the scope without an import cycle through this file. @visibleForTesting -bool shouldHandleMacOsRootEscape({ - required bool isMacOS, +bool shouldHandleDesktopRootEscape({ + required bool isDesktop, required bool isPhysicalKeyboardEvent, required LogicalKeyboardKey logicalKey, required bool isCurrentRoute, required bool isHomeTab, }) { - return isMacOS && isPhysicalKeyboardEvent && logicalKey == LogicalKeyboardKey.escape && isCurrentRoute && isHomeTab; + return isDesktop && isPhysicalKeyboardEvent && logicalKey == LogicalKeyboardKey.escape && isCurrentRoute && isHomeTab; } @visibleForTesting @@ -1348,13 +1348,16 @@ class _MainScreenState extends State return KeyEventResult.handled; } - /// On macOS, native fullscreen is window state shared by every route. - /// Player Escape therefore leaves it alone; only root Home owns the - /// conventional Escape-to-leave-fullscreen behavior. - KeyEventResult _handleMacOsRootEscape(KeyEvent event) { + /// Desktop physical-keyboard Escape at root Home is reserved for leaving + /// window fullscreen; it never arms the press-back-again quit, so an Escape + /// aimed at fullscreen can't close the app (#1748). Remotes, gamepad B, and + /// system back keep the double-press exit path. On macOS this also keeps + /// player Escape away from native fullscreen, which is window state shared + /// by every route. + KeyEventResult _handleDesktopRootEscape(KeyEvent event) { final tabs = _getVisibleTabs(_isOffline); - final shouldHandle = shouldHandleMacOsRootEscape( - isMacOS: Platform.isMacOS, + final shouldHandle = shouldHandleDesktopRootEscape( + isDesktop: PlatformDetector.isDesktopOS(), isPhysicalKeyboardEvent: event.isPhysicalKeyboardEvent, logicalKey: event.logicalKey, isCurrentRoute: ModalRoute.of(context)?.isCurrent == true, @@ -1750,7 +1753,7 @@ class _MainScreenState extends State canPop: false, child: Focus( onKeyEvent: (node, event) { - final rootEscapeResult = _handleMacOsRootEscape(event); + final rootEscapeResult = _handleDesktopRootEscape(event); if (rootEscapeResult == KeyEventResult.handled) return rootEscapeResult; final fullscreenResult = _handleFullscreenShortcut(event); if (fullscreenResult == KeyEventResult.handled) return fullscreenResult; diff --git a/test/screens/main_screen_layout_test.dart b/test/screens/main_screen_layout_test.dart index 82e8642a..3ac0624a 100644 --- a/test/screens/main_screen_layout_test.dart +++ b/test/screens/main_screen_layout_test.dart @@ -95,16 +95,16 @@ void main() { expect(published, [true]); }); - test('macOS physical Escape is reserved for native fullscreen only at root Home', () { + test('desktop physical Escape is reserved for window fullscreen only at root Home', () { bool shouldHandle({ - bool isMacOS = true, + bool isDesktop = true, bool isPhysicalKeyboardEvent = true, LogicalKeyboardKey logicalKey = LogicalKeyboardKey.escape, bool isCurrentRoute = true, bool isHomeTab = true, }) { - return shouldHandleMacOsRootEscape( - isMacOS: isMacOS, + return shouldHandleDesktopRootEscape( + isDesktop: isDesktop, isPhysicalKeyboardEvent: isPhysicalKeyboardEvent, logicalKey: logicalKey, isCurrentRoute: isCurrentRoute, @@ -115,8 +115,10 @@ void main() { expect(shouldHandle(), isTrue); expect(shouldHandle(isHomeTab: false), isFalse); expect(shouldHandle(isCurrentRoute: false), isFalse); + // A remote/gamepad-synthesized escape is not a physical keyboard Escape; + // it keeps the press-back-again exit path. expect(shouldHandle(isPhysicalKeyboardEvent: false), isFalse); - expect(shouldHandle(isMacOS: false), isFalse); + expect(shouldHandle(isDesktop: false), isFalse); expect(shouldHandle(logicalKey: LogicalKeyboardKey.gameButtonB), isFalse); });