diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 578ba3ce3..7a3f98377 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -120,6 +120,7 @@ class FfiModel with ChangeNotifier { late VirtualMouseMode virtualMouseMode; Timer? _timer; var _reconnects = 1; + DateTime? _offlineReconnectStartTime; bool _viewOnly = false; bool _showMyCursor = false; WeakReference parent; @@ -783,7 +784,8 @@ class FfiModel with ChangeNotifier { } } - Future updateCurDisplay(SessionID sessionId, {updateCursorPos = false}) async { + Future updateCurDisplay(SessionID sessionId, + {updateCursorPos = false}) async { final newRect = displaysRect(); if (newRect == null) { return; @@ -939,11 +941,46 @@ class FfiModel with ChangeNotifier { showPrivacyFailedDialog( sessionId, type, title, text, link, hasRetry, dialogManager); } else { - final hasRetry = evt['hasRetry'] == 'true'; + var hasRetry = evt['hasRetry'] == 'true'; + if (!hasRetry) { + hasRetry = shouldAutoRetryOnOffline(type, title, text); + } showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager); } } + /// Auto-retry check for "Remote desktop is offline" error. + /// returns true to auto-retry, false otherwise. + bool shouldAutoRetryOnOffline( + String type, + String title, + String text, + ) { + if (type == 'error' && + title == 'Connection Error' && + text == 'Remote desktop is offline' && + _pi.isSet.isTrue) { + // Auto retry for ~30s (server's peer offline threshold) when controlled peer's account changes + // (e.g., signout, switch user, login into OS) causes temporary offline via websocket/tcp connection. + // The actual wait may exceed 30s (e.g., 20s elapsed + 16s next retry = 36s), which is acceptable + // since the controlled side reconnects quickly after account changes. + // Uses time-based check instead of _reconnects count because user can manually retry. + // https://github.com/rustdesk/rustdesk/discussions/14048 + if (_offlineReconnectStartTime == null) { + // First offline, record time and start retry + _offlineReconnectStartTime = DateTime.now(); + return true; + } else { + final elapsed = + DateTime.now().difference(_offlineReconnectStartTime!).inSeconds; + if (elapsed < 30) { + return true; + } + } + } + return false; + } + handleToast(Map evt, SessionID sessionId, String peerId) { final type = evt['type'] ?? 'info'; final text = evt['text'] ?? ''; @@ -1001,6 +1038,7 @@ class FfiModel with ChangeNotifier { _reconnects *= 2; } else { _reconnects = 1; + _offlineReconnectStartTime = null; } } @@ -1323,6 +1361,7 @@ class FfiModel with ChangeNotifier { } if (displays.isNotEmpty) { _reconnects = 1; + _offlineReconnectStartTime = null; waitForFirstImage.value = true; isRefreshing = false; }