From f65952cf1cbd1bccc97aabcd78f07da2c11ae019 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 5 Jan 2026 22:16:35 +0800 Subject: [PATCH] fix(desktop): wakelock issue with multiple tabs in same window (#13956) Each desktop isolate now independently tracks wakelock state. WakelockPlus.disable() is only called when all tabs within the same isolate are closed/minimized. WakelockPlus ensures screen stays awake as long as any isolate has wakelock enabled. Signed-off-by: 21pages --- flutter/lib/common.dart | 26 +++++++++++++++++++ .../lib/desktop/pages/file_manager_page.dart | 10 +++---- flutter/lib/desktop/pages/remote_page.dart | 22 +++++----------- .../lib/desktop/pages/view_camera_page.dart | 22 +++++----------- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 07340e16b..0804ebbf4 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -24,6 +24,7 @@ import 'package:provider/provider.dart'; import 'package:uni_links/uni_links.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:window_manager/window_manager.dart'; import 'package:window_size/window_size.dart' as window_size; @@ -2676,6 +2677,31 @@ class SimpleWrapper { SimpleWrapper(this.value); } +/// Wakelock manager with reference counting for desktop. +/// Ensures wakelock is only disabled when all sessions are closed/minimized. +/// +/// Note: Each isolate has its own WakelockPlus instance with independent assertion. +/// As long as one isolate has wakelock enabled, the screen stays awake. +/// This manager handles multiple tabs within the same isolate. +class WakelockManager { + static final Set _enabledKeys = {}; + + static void enable(UniqueKey key) { + if (isLinux) return; + _enabledKeys.add(key); + WakelockPlus.enable(); + } + + static void disable(UniqueKey key) { + if (isLinux) return; + if (_enabledKeys.remove(key)) { + if (_enabledKeys.isEmpty) { + WakelockPlus.disable(); + } + } + } +} + /// call this to reload current window. /// /// [Note] diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 9e554cbe8..cf97351b3 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -17,7 +17,6 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:flutter_hbb/web/dummy.dart' if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart'; @@ -86,6 +85,7 @@ class _FileManagerPageState extends State final _dropMaskVisible = false.obs; // TODO impl drop mask final _overlayKeyState = OverlayKeyState(); + final _uniqueKey = UniqueKey(); late FFI _ffi; @@ -107,9 +107,7 @@ class _FileManagerPageState extends State .showLoading(translate('Connecting...'), onCancel: closeConnection); }); Get.put(_ffi, tag: 'ft_${widget.id}'); - if (!isLinux) { - WakelockPlus.enable(); - } + WakelockManager.enable(_uniqueKey); if (isWeb) { _ffi.ffiModel.updateEventListener(_ffi.sessionId, widget.id); } @@ -127,9 +125,7 @@ class _FileManagerPageState extends State model.close().whenComplete(() { _ffi.close(); _ffi.dialogManager.dismissAll(); - if (!isLinux) { - WakelockPlus.disable(); - } + WakelockManager.disable(_uniqueKey); Get.delete(tag: 'ft_${widget.id}'); }); WidgetsBinding.instance.removeObserver(this); diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index a752efe6b..3c5245bb3 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:flutter/scheduler.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:flutter_hbb/models/state_model.dart'; import '../../consts.dart'; @@ -85,6 +84,7 @@ class _RemotePageState extends State late RxBool _zoomCursor; late RxBool _remoteCursorMoved; late RxBool _keyboardEnabled; + final _uniqueKey = UniqueKey(); var _blockableOverlayState = BlockableOverlayState(); @@ -138,9 +138,7 @@ class _RemotePageState extends State _ffi.dialogManager .showLoading(translate('Connecting...'), onCancel: closeConnection); }); - if (!isLinux) { - WakelockPlus.enable(); - } + WakelockManager.enable(_uniqueKey); _ffi.ffiModel.updateEventListener(sessionId, widget.id); if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); @@ -206,26 +204,20 @@ class _RemotePageState extends State if (isWindows) { _isWindowBlur = false; } - if (!isLinux) { - WakelockPlus.enable(); - } + WakelockManager.enable(_uniqueKey); } // When the window is unminimized, onWindowMaximize or onWindowRestore can be called when the old state was maximized or not. @override void onWindowMaximize() { super.onWindowMaximize(); - if (!isLinux) { - WakelockPlus.enable(); - } + WakelockManager.enable(_uniqueKey); } @override void onWindowMinimize() { super.onWindowMinimize(); - if (!isLinux) { - WakelockPlus.disable(); - } + WakelockManager.disable(_uniqueKey); } @override @@ -268,9 +260,7 @@ class _RemotePageState extends State await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); } - if (!isLinux) { - await WakelockPlus.disable(); - } + WakelockManager.disable(_uniqueKey); await Get.delete(tag: widget.id); removeSharedStates(widget.id); } diff --git a/flutter/lib/desktop/pages/view_camera_page.dart b/flutter/lib/desktop/pages/view_camera_page.dart index 6be074b59..c45ec4d86 100644 --- a/flutter/lib/desktop/pages/view_camera_page.dart +++ b/flutter/lib/desktop/pages/view_camera_page.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/remote_input.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:flutter_hbb/models/state_model.dart'; import '../../consts.dart'; @@ -77,6 +76,7 @@ class _ViewCameraPageState extends State String keyboardMode = "legacy"; bool _isWindowBlur = false; final _cursorOverImage = false.obs; + final _uniqueKey = UniqueKey(); var _blockableOverlayState = BlockableOverlayState(); @@ -124,9 +124,7 @@ class _ViewCameraPageState extends State _ffi.dialogManager .showLoading(translate('Connecting...'), onCancel: closeConnection); }); - if (!isLinux) { - WakelockPlus.enable(); - } + WakelockManager.enable(_uniqueKey); _ffi.ffiModel.updateEventListener(sessionId, widget.id); if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); @@ -185,26 +183,20 @@ class _ViewCameraPageState extends State if (isWindows) { _isWindowBlur = false; } - if (!isLinux) { - WakelockPlus.enable(); - } + WakelockManager.enable(_uniqueKey); } // When the window is unminimized, onWindowMaximize or onWindowRestore can be called when the old state was maximized or not. @override void onWindowMaximize() { super.onWindowMaximize(); - if (!isLinux) { - WakelockPlus.enable(); - } + WakelockManager.enable(_uniqueKey); } @override void onWindowMinimize() { super.onWindowMinimize(); - if (!isLinux) { - WakelockPlus.disable(); - } + WakelockManager.disable(_uniqueKey); } @override @@ -247,9 +239,7 @@ class _ViewCameraPageState extends State await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); } - if (!isLinux) { - await WakelockPlus.disable(); - } + WakelockManager.disable(_uniqueKey); await Get.delete(tag: widget.id); removeSharedStates(widget.id); }