fix(desktop): reserve root Escape for leaving fullscreen instead of quitting
CI - Sanity Checks / Code Analysis (push) Successful in 5m36s
CI - Sanity Checks / Unit Tests (push) Failing after 13m36s
CI - Sanity Checks / Android JVM and Native Tests (push) Failing after 1m13s
CI - Sanity Checks / Native Formatting (push) Failing after 22s
CI - Sanity Checks / Linux native reliability (thread) (push) Failing after 2m59s
CI - Sanity Checks / Linux native reliability (address) (push) Failing after 2m57s
CI - Sanity Checks / Apple native reliability (iOS) (push) Canceled after 0s
CI - Sanity Checks / Apple native reliability (macOS) (push) Canceled after 0s
CI - Sanity Checks / Apple native reliability (tvOS) (push) Canceled after 0s
CI - Sanity Checks / Windows native reliability (arm64) (push) Canceled after 0s
CI - Sanity Checks / Windows native reliability (x64) (push) Canceled after 0s
CI - Sanity Checks / Dependency Validation (push) Successful in 1m9s
CI - Sanity Checks / Server checks (push) Successful in 28s
CI - Sanity Checks / Website checks (push) Successful in 1m22s
CI - Sanity Checks / Linux package smoke build (push) Failing after 6m41s

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
This commit is contained in:
edde746
2026-08-11 11:15:51 +02:00
parent 6663353895
commit 26e98e898d
2 changed files with 20 additions and 15 deletions
+13 -10
View File
@@ -78,14 +78,14 @@ import '../watch_together/watch_together.dart';
// browse rail can import the scope without an import cycle through this file. // browse rail can import the scope without an import cycle through this file.
@visibleForTesting @visibleForTesting
bool shouldHandleMacOsRootEscape({ bool shouldHandleDesktopRootEscape({
required bool isMacOS, required bool isDesktop,
required bool isPhysicalKeyboardEvent, required bool isPhysicalKeyboardEvent,
required LogicalKeyboardKey logicalKey, required LogicalKeyboardKey logicalKey,
required bool isCurrentRoute, required bool isCurrentRoute,
required bool isHomeTab, required bool isHomeTab,
}) { }) {
return isMacOS && isPhysicalKeyboardEvent && logicalKey == LogicalKeyboardKey.escape && isCurrentRoute && isHomeTab; return isDesktop && isPhysicalKeyboardEvent && logicalKey == LogicalKeyboardKey.escape && isCurrentRoute && isHomeTab;
} }
@visibleForTesting @visibleForTesting
@@ -1348,13 +1348,16 @@ class _MainScreenState extends State<MainScreen>
return KeyEventResult.handled; return KeyEventResult.handled;
} }
/// On macOS, native fullscreen is window state shared by every route. /// Desktop physical-keyboard Escape at root Home is reserved for leaving
/// Player Escape therefore leaves it alone; only root Home owns the /// window fullscreen; it never arms the press-back-again quit, so an Escape
/// conventional Escape-to-leave-fullscreen behavior. /// aimed at fullscreen can't close the app (#1748). Remotes, gamepad B, and
KeyEventResult _handleMacOsRootEscape(KeyEvent event) { /// 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 tabs = _getVisibleTabs(_isOffline);
final shouldHandle = shouldHandleMacOsRootEscape( final shouldHandle = shouldHandleDesktopRootEscape(
isMacOS: Platform.isMacOS, isDesktop: PlatformDetector.isDesktopOS(),
isPhysicalKeyboardEvent: event.isPhysicalKeyboardEvent, isPhysicalKeyboardEvent: event.isPhysicalKeyboardEvent,
logicalKey: event.logicalKey, logicalKey: event.logicalKey,
isCurrentRoute: ModalRoute.of(context)?.isCurrent == true, isCurrentRoute: ModalRoute.of(context)?.isCurrent == true,
@@ -1750,7 +1753,7 @@ class _MainScreenState extends State<MainScreen>
canPop: false, canPop: false,
child: Focus( child: Focus(
onKeyEvent: (node, event) { onKeyEvent: (node, event) {
final rootEscapeResult = _handleMacOsRootEscape(event); final rootEscapeResult = _handleDesktopRootEscape(event);
if (rootEscapeResult == KeyEventResult.handled) return rootEscapeResult; if (rootEscapeResult == KeyEventResult.handled) return rootEscapeResult;
final fullscreenResult = _handleFullscreenShortcut(event); final fullscreenResult = _handleFullscreenShortcut(event);
if (fullscreenResult == KeyEventResult.handled) return fullscreenResult; if (fullscreenResult == KeyEventResult.handled) return fullscreenResult;
+7 -5
View File
@@ -95,16 +95,16 @@ void main() {
expect(published, [true]); 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 shouldHandle({
bool isMacOS = true, bool isDesktop = true,
bool isPhysicalKeyboardEvent = true, bool isPhysicalKeyboardEvent = true,
LogicalKeyboardKey logicalKey = LogicalKeyboardKey.escape, LogicalKeyboardKey logicalKey = LogicalKeyboardKey.escape,
bool isCurrentRoute = true, bool isCurrentRoute = true,
bool isHomeTab = true, bool isHomeTab = true,
}) { }) {
return shouldHandleMacOsRootEscape( return shouldHandleDesktopRootEscape(
isMacOS: isMacOS, isDesktop: isDesktop,
isPhysicalKeyboardEvent: isPhysicalKeyboardEvent, isPhysicalKeyboardEvent: isPhysicalKeyboardEvent,
logicalKey: logicalKey, logicalKey: logicalKey,
isCurrentRoute: isCurrentRoute, isCurrentRoute: isCurrentRoute,
@@ -115,8 +115,10 @@ void main() {
expect(shouldHandle(), isTrue); expect(shouldHandle(), isTrue);
expect(shouldHandle(isHomeTab: false), isFalse); expect(shouldHandle(isHomeTab: false), isFalse);
expect(shouldHandle(isCurrentRoute: 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(isPhysicalKeyboardEvent: false), isFalse);
expect(shouldHandle(isMacOS: false), isFalse); expect(shouldHandle(isDesktop: false), isFalse);
expect(shouldHandle(logicalKey: LogicalKeyboardKey.gameButtonB), isFalse); expect(shouldHandle(logicalKey: LogicalKeyboardKey.gameButtonB), isFalse);
}); });