diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 58ddc0cb0..14a53b354 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -3350,7 +3350,12 @@ Future> 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]; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 3054ffa96..175e3ff2d 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -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) {