mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-06 15:25:09 +03:00
Feature: Restore the last viewed monitor on auto reconnect (#15441)
* Feature: Restore the last viewed monitor on auto reconnect Remembers the users last manually selected remote monitor and returns to it after an auto reconnect. In memory, reconnect only, and bounds checked against the current display count. It is skipped in "use all my displays" mode. Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com> * Address review on reconnect monitor restore Avoid a crash if the session closes during a reconnect. Don't overwrite the remembered monitor on auto restore. Defer the switch until the view is ready so a monitor with a different size renders correctly. Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com> * Guard all-displays reconnect restore against empty display list * Harden reconnect monitor restore against races and multi-UI sessions Cancel a queued restore when the user manually selects a monitor, so a newer choice is not overridden by a stale pending restore. Compare the remembered monitor against the reconnect event's display instead of the stale _pi.currentDisplay, which is intentionally left unchanged when the peer has multiple sessions. Add a frame-independent fallback so a multi-UI tab that never receives the first-image event (its display is filtered to the owning tab) still restores the remembered monitor. Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com> * Harden reconnect monitor restore: fallback timer, lifecycle, cursor Follow-up hardening on the auto-reconnect monitor restore: - Cancel the fallback timer synchronously once this tab owns the restore, so it can no longer fire while onEvent2UIRgba is awaiting canvas setup and switch displays before the canvas is ready (the offset the deferred restore exists to avoid). The multi-UI no-frame fallback stays intact. - Apply the restore in a finally so a throwing canvas init still runs it instead of stranding a queued restore with the timer already cancelled. - Cancel the fallback timer on a manual monitor switch, so a newer user selection supersedes a queued restore instead of racing it. - Restore with updateCursorPos: false, matching other programmatic display switches so an auto-restore does not reposition the cursor. --------- Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
This commit is contained in:
@@ -3350,7 +3350,12 @@ Future<List<Rect>> getScreenRectList() async {
|
||||
}
|
||||
|
||||
openMonitorInTheSameTab(int i, FFI ffi, PeerInfo pi,
|
||||
{bool updateCursorPos = true}) {
|
||||
{bool updateCursorPos = true, bool recordSelection = true}) {
|
||||
if (recordSelection) {
|
||||
ffi.ffiModel.lastUserDisplay = i;
|
||||
ffi.ffiModel.cancelPendingRestoreTimer();
|
||||
ffi.ffiModel.pendingMonitorRestore = null;
|
||||
}
|
||||
final displays = i == kAllDisplayValue
|
||||
? List.generate(pi.displays.length, (index) => index)
|
||||
: [i];
|
||||
|
||||
@@ -112,6 +112,9 @@ class CachedPeerData {
|
||||
class FfiModel with ChangeNotifier {
|
||||
CachedPeerData cachedPeerData = CachedPeerData();
|
||||
PeerInfo _pi = PeerInfo();
|
||||
int? lastUserDisplay;
|
||||
int? pendingMonitorRestore;
|
||||
Timer? _pendingRestoreTimer;
|
||||
Rect? _rect;
|
||||
|
||||
var _inputBlocked = false;
|
||||
@@ -248,6 +251,8 @@ class FfiModel with ChangeNotifier {
|
||||
|
||||
clear() {
|
||||
_pi = PeerInfo();
|
||||
lastUserDisplay = null;
|
||||
_cancelPendingMonitorRestore();
|
||||
_secure = null;
|
||||
_direct = null;
|
||||
_inputBlocked = false;
|
||||
@@ -932,6 +937,7 @@ class FfiModel with ChangeNotifier {
|
||||
// frame briefly, then shows the Connecting overlay.
|
||||
if (_restartReconnectDelayTimer == null) {
|
||||
parent.target?.inputModel.setRelativeMouseMode(false);
|
||||
_cancelPendingMonitorRestore();
|
||||
bind.sessionReconnect(sessionId: sessionId, forceRelay: false);
|
||||
clearPermissions();
|
||||
// Retry once more after the silent window so restart reconnect attempts
|
||||
@@ -1084,10 +1090,22 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelPendingMonitorRestore() {
|
||||
_pendingRestoreTimer?.cancel();
|
||||
_pendingRestoreTimer = null;
|
||||
pendingMonitorRestore = null;
|
||||
}
|
||||
|
||||
void cancelPendingRestoreTimer() {
|
||||
_pendingRestoreTimer?.cancel();
|
||||
_pendingRestoreTimer = null;
|
||||
}
|
||||
|
||||
void reconnect(OverlayDialogManager dialogManager, SessionID sessionId,
|
||||
bool forceRelay) {
|
||||
// Disable relative mouse mode before reconnecting to ensure cursor is released.
|
||||
parent.target?.inputModel.setRelativeMouseMode(false);
|
||||
_cancelPendingMonitorRestore();
|
||||
bind.sessionReconnect(sessionId: sessionId, forceRelay: forceRelay);
|
||||
clearPermissions();
|
||||
dialogManager.dismissAll();
|
||||
@@ -1401,6 +1419,25 @@ class FfiModel with ChangeNotifier {
|
||||
// now replaced to _updateCurDisplay
|
||||
updateCurDisplay(sessionId);
|
||||
}
|
||||
// After reconnecting, restore the last selected monitor once the canvas is ready.
|
||||
// Switching earlier can offset the view if the monitor sizes differ.
|
||||
final last = lastUserDisplay;
|
||||
pendingMonitorRestore = (!isCache &&
|
||||
last != null &&
|
||||
last != currentDisplay &&
|
||||
bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
|
||||
sessionId: sessionId) !=
|
||||
'Y' &&
|
||||
((last == kAllDisplayValue && _pi.displays.isNotEmpty) ||
|
||||
(last >= 0 && last < _pi.displays.length)))
|
||||
? last
|
||||
: null;
|
||||
// Fallback if the first image event never reaches this tab (multi-UI).
|
||||
_pendingRestoreTimer?.cancel();
|
||||
if (pendingMonitorRestore != null) {
|
||||
_pendingRestoreTimer = Timer(const Duration(milliseconds: 1500),
|
||||
() => parent.target?._applyPendingMonitorRestore());
|
||||
}
|
||||
if (displays.isNotEmpty) {
|
||||
_reconnects = 1;
|
||||
_offlineReconnectStartTime = null;
|
||||
@@ -3911,17 +3948,35 @@ class FFI {
|
||||
}
|
||||
if (ffiModel.waitForFirstImage.value == true) {
|
||||
ffiModel.waitForFirstImage.value = false;
|
||||
ffiModel.cancelPendingRestoreTimer();
|
||||
ffiModel.resetRestartReconnectState();
|
||||
dialogManager.dismissAll();
|
||||
await canvasModel.updateViewStyle();
|
||||
await canvasModel.updateScrollStyle();
|
||||
await canvasModel.initializeEdgeScrollEdgeThickness();
|
||||
for (final cb in imageModel.callbacksOnFirstImage) {
|
||||
cb(id);
|
||||
try {
|
||||
await canvasModel.updateViewStyle();
|
||||
await canvasModel.updateScrollStyle();
|
||||
await canvasModel.initializeEdgeScrollEdgeThickness();
|
||||
for (final cb in imageModel.callbacksOnFirstImage) {
|
||||
cb(id);
|
||||
}
|
||||
} finally {
|
||||
_applyPendingMonitorRestore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _applyPendingMonitorRestore() {
|
||||
final restore = ffiModel.pendingMonitorRestore;
|
||||
ffiModel._cancelPendingMonitorRestore();
|
||||
if (restore == null || closed) return;
|
||||
// The display list may have changed since the restore was queued.
|
||||
final displays = ffiModel.pi.displays;
|
||||
if ((restore == kAllDisplayValue && displays.isNotEmpty) ||
|
||||
(restore >= 0 && restore < displays.length)) {
|
||||
openMonitorInTheSameTab(restore, this, ffiModel.pi,
|
||||
recordSelection: false, updateCursorPos: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Login with [password], choose if the client should [remember] it.
|
||||
void login(String osUsername, String osPassword, SessionID sessionId,
|
||||
String password, bool remember) {
|
||||
|
||||
Reference in New Issue
Block a user