keep-awake-during-incoming-sessions (#14082)

* keep-awake-during-incoming-sessions

* Update flutter/lib/desktop/pages/desktop_setting_page.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update flutter/lib/common.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update flutter/lib/mobile/pages/settings_page.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update common.dart

* wakelock

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix build

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Update server_model.dart

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
RustDesk
2026-01-21 16:25:57 +08:00
committed by GitHub
parent a6724b1c07
commit 21a7cef98a
60 changed files with 219 additions and 52 deletions

View File

@@ -1578,7 +1578,7 @@ bool option2bool(String option, String value) {
option == kOptionForceAlwaysRelay) {
res = value == "Y";
} else {
assert(false);
// "" is true
res = value != "N";
}
return res;
@@ -1596,9 +1596,6 @@ String bool2option(String option, bool b) {
option == kOptionForceAlwaysRelay) {
res = b ? 'Y' : defaultOptionNo;
} else {
if (option != kOptionEnableUdpPunch && option != kOptionEnableIpv6Punch) {
assert(false);
}
res = b ? 'Y' : 'N';
}
return res;
@@ -2684,20 +2681,44 @@ class SimpleWrapper<T> {
/// This manager handles multiple tabs within the same isolate.
class WakelockManager {
static final Set<UniqueKey> _enabledKeys = {};
// Don't use WakelockPlus.enabled, it causes error on Android:
// Unhandled Exception: FormatException: Message corrupted
//
// On Linux, multiple enable() calls create only one inhibit, but each disable()
// only releases if _cookie != null. So we need our own _enabled state to avoid
// calling disable() when not enabled.
// See: https://github.com/fluttercommunity/wakelock_plus/blob/0c74e5bbc6aefac57b6c96bb7ef987705ed559ec/wakelock_plus/lib/src/wakelock_plus_linux_plugin.dart#L48
static bool _enabled = false;
static void enable(UniqueKey key) {
if (isLinux) return;
_enabledKeys.add(key);
WakelockPlus.enable();
static void enable(UniqueKey key, {bool isServer = false}) {
// Check if we should keep awake during outgoing sessions
if (!isServer) {
final keepAwake =
mainGetLocalBoolOptionSync(kOptionKeepAwakeDuringOutgoingSessions);
if (!keepAwake) {
return; // Don't enable wakelock if user disabled keep awake
}
}
if (isDesktop) {
_enabledKeys.add(key);
}
if (!_enabled) {
_enabled = true;
WakelockPlus.enable();
}
}
static void disable(UniqueKey key) {
if (isLinux) return;
if (_enabledKeys.remove(key)) {
if (_enabledKeys.isEmpty) {
WakelockPlus.disable();
if (isDesktop) {
_enabledKeys.remove(key);
if (_enabledKeys.isNotEmpty) {
return;
}
}
if (_enabled) {
WakelockPlus.disable();
_enabled = false;
}
}
}

View File

@@ -194,6 +194,9 @@ const String kOptionDisableFloatingWindow = "disable-floating-window";
const String kOptionKeepScreenOn = "keep-screen-on";
const String kOptionKeepAwakeDuringIncomingSessions = "keep-awake-during-incoming-sessions";
const String kOptionKeepAwakeDuringOutgoingSessions = "keep-awake-during-outgoing-sessions";
const String kOptionShowMobileAction = "showMobileActions";
const String kUrlActionClose = "close";

View File

@@ -557,6 +557,17 @@ class _GeneralState extends State<_General> {
],
],
];
// Add client-side wakelock option for desktop platforms
if (!bind.isIncomingOnly()) {
children.add(_OptionCheckBox(
context,
'keep-awake-during-outgoing-sessions-label',
kOptionKeepAwakeDuringOutgoingSessions,
isServer: false,
));
}
if (!isWeb && bind.mainShowOption(key: kOptionAllowLinuxHeadless)) {
children.add(_OptionCheckBox(
context, 'Allow linux headless', kOptionAllowLinuxHeadless));
@@ -1219,6 +1230,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
...directIp(context),
whitelist(),
...autoDisconnect(context),
_OptionCheckBox(context, 'keep-awake-during-incoming-sessions-label',
kOptionKeepAwakeDuringIncomingSessions,
reverse: false, enabled: enabled),
if (bind.mainIsInstalled())
_OptionCheckBox(context, 'allow-only-conn-window-open-tip',
'allow-only-conn-window-open',

View File

@@ -5,7 +5,6 @@ import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:get/get.dart';
import 'package:toggle_switch/toggle_switch.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
@@ -72,6 +71,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
showLocal ? model.localController : model.remoteController;
FileDirectory get currentDir => currentFileController.directory.value;
DirectoryOptions get currentOptions => currentFileController.options.value;
final _uniqueKey = UniqueKey();
@override
void initState() {
@@ -86,7 +86,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
gFFI.ffiModel.updateEventListener(gFFI.sessionId, widget.id);
WakelockPlus.enable();
WakelockManager.enable(_uniqueKey);
}
@override
@@ -94,7 +94,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
model.close().whenComplete(() {
gFFI.close();
gFFI.dialogManager.dismissAll();
WakelockPlus.disable();
WakelockManager.disable(_uniqueKey);
});
model.jobController.clear();
super.dispose();

View File

@@ -14,7 +14,6 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../common.dart';
import '../../common/widgets/overlay.dart';
@@ -67,7 +66,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
String _value = '';
Orientation? _currentOrientation;
double _viewInsetsBottom = 0;
final _uniqueKey = UniqueKey();
Timer? _timerDidChangeMetrics;
final _blockableOverlayState = BlockableOverlayState();
@@ -105,9 +104,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
gFFI.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
if (!isWeb) {
WakelockPlus.enable();
}
WakelockManager.enable(_uniqueKey);
_physicalFocusNode.requestFocus();
gFFI.inputModel.listenToMouse(true);
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
@@ -146,9 +143,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
gFFI.dialogManager.dismissAll();
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: SystemUiOverlay.values);
if (!isWeb) {
await WakelockPlus.disable();
}
WakelockManager.disable(_uniqueKey);
await keyboardSubscription.cancel();
removeSharedStates(widget.id);
// `on_voice_call_closed` should be called when the connection is ended.

View File

@@ -100,6 +100,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
var _enableIpv6Punch = false;
var _isUsingPublicServer = false;
var _allowAskForNoteAtEndOfConnection = false;
var _preventSleepWhileConnected = true;
_SettingsState() {
_enableAbr = option2bool(
@@ -140,6 +141,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_enableIpv6Punch = mainGetLocalBoolOptionSync(kOptionEnableIpv6Punch);
_allowAskForNoteAtEndOfConnection =
mainGetLocalBoolOptionSync(kOptionAllowAskForNoteAtEndOfConnection);
_preventSleepWhileConnected =
mainGetLocalBoolOptionSync(kOptionKeepAwakeDuringOutgoingSessions);
_showTerminalExtraKeys =
mainGetLocalBoolOptionSync(kOptionEnableShowTerminalExtraKeys);
}
@@ -823,7 +826,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_allowAskForNoteAtEndOfConnection = newValue;
});
},
)
),
if (!incomingOnly)
SettingsTile.switchTile(
title: Text(translate('keep-awake-during-outgoing-sessions-label')),
initialValue: _preventSleepWhileConnected,
onToggle: (v) async {
await mainSetLocalBoolOption(kOptionKeepAwakeDuringOutgoingSessions, v);
setState(() {
_preventSleepWhileConnected = v;
});
},
),
]),
if (isAndroid)
SettingsSection(title: Text(translate('Hardware Codec')), tiles: [

View File

@@ -11,7 +11,6 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../common.dart';
import '../../common/widgets/overlay.dart';
@@ -62,7 +61,7 @@ class _ViewCameraPageState extends State<ViewCameraPage>
bool _showGestureHelp = false;
Orientation? _currentOrientation;
double _viewInsetsBottom = 0;
final _uniqueKey = UniqueKey();
Timer? _timerDidChangeMetrics;
final _blockableOverlayState = BlockableOverlayState();
@@ -100,9 +99,7 @@ class _ViewCameraPageState extends State<ViewCameraPage>
gFFI.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
if (!isWeb) {
WakelockPlus.enable();
}
WakelockManager.enable(_uniqueKey);
_physicalFocusNode.requestFocus();
gFFI.inputModel.listenToMouse(true);
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
@@ -139,9 +136,7 @@ class _ViewCameraPageState extends State<ViewCameraPage>
gFFI.dialogManager.dismissAll();
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: SystemUiOverlay.values);
if (!isWeb) {
await WakelockPlus.disable();
}
WakelockManager.disable(_uniqueKey);
removeSharedStates(widget.id);
// `on_voice_call_closed` should be called when the connection is ended.
// The inner logic of `on_voice_call_closed` will check if the voice call is active.

View File

@@ -8,7 +8,6 @@ import 'package:flutter_hbb/mobile/pages/settings_page.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:window_manager/window_manager.dart';
import '../common.dart';
@@ -51,6 +50,8 @@ class ServerModel with ChangeNotifier {
Timer? cmHiddenTimer;
final _wakelockKey = UniqueKey();
bool get isStart => _isStart;
bool get mediaOk => _mediaOk;
@@ -466,10 +467,8 @@ class ServerModel with ChangeNotifier {
await parent.target?.invokeMethod("stop_service");
await bind.mainStopService();
notifyListeners();
if (!isLinux) {
// current linux is not supported
WakelockPlus.disable();
}
// for androidUpdatekeepScreenOn only
WakelockManager.disable(_wakelockKey);
}
Future<bool> setPermanentPassword(String newPW) async {
@@ -613,12 +612,12 @@ class ServerModel with ChangeNotifier {
void showLoginDialog(Client client) {
showClientDialog(
client,
client.isFileTransfer
? "Transfer file"
client.isFileTransfer
? "Transfer file"
: client.isViewCamera
? "View camera"
: client.isTerminal
? "Terminal"
: client.isTerminal
? "Terminal"
: "Share screen",
'Do you accept?',
'android_new_connection_tip',
@@ -797,12 +796,10 @@ class ServerModel with ChangeNotifier {
final on = ((keepScreenOn == KeepScreenOn.serviceOn) && _isStart) ||
(keepScreenOn == KeepScreenOn.duringControlled &&
_clients.map((e) => !e.disconnected).isNotEmpty);
if (on != await WakelockPlus.enabled) {
if (on) {
WakelockPlus.enable();
} else {
WakelockPlus.disable();
}
if (on) {
WakelockManager.enable(_wakelockKey, isServer: true);
} else {
WakelockManager.disable(_wakelockKey);
}
}
}