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.
@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<MainScreen>
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<MainScreen>
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;
+7 -5
View File
@@ -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);
});