From 0d77482a640688fab0c64eb251f8eb0293cf4acc Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sat, 25 Apr 2026 01:07:00 +0800 Subject: [PATCH] Fix terminal auto-reconnect freeze: reconnect resumes terminal output, while multi-tab reconnect avoids restoring duplicate tabs for terminals that are already open. --- .../lib/desktop/pages/terminal_tab_page.dart | 17 ++++++++++++++++- flutter/lib/models/terminal_model.dart | 16 +++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/flutter/lib/desktop/pages/terminal_tab_page.dart b/flutter/lib/desktop/pages/terminal_tab_page.dart index 28e59fb05..ab64c1a12 100644 --- a/flutter/lib/desktop/pages/terminal_tab_page.dart +++ b/flutter/lib/desktop/pages/terminal_tab_page.dart @@ -368,8 +368,23 @@ class _TerminalTabPageState extends State { final persistentSessions = args['persistent_sessions'] as List? ?? []; final sortedSessions = persistentSessions.whereType().toList()..sort(); + var peerId = args['peer_id'] as String? ?? ''; + if (peerId.isEmpty) { + final currentTab = tabController.state.value.selectedTabInfo; + final parsed = _parseTabKey(currentTab.key); + if (parsed == null) return; + peerId = parsed.$1; + } + final existingTerminalIds = tabController.state.value.tabs + .map((tab) => _parseTabKey(tab.key)) + .where((parsed) => parsed != null && parsed.$1 == peerId) + .map((parsed) => parsed!.$2) + .toSet(); for (final terminalId in sortedSessions) { - _addNewTerminalForCurrentPeer(terminalId: terminalId); + if (!existingTerminalIds.add(terminalId)) { + continue; + } + _addNewTerminal(peerId, terminalId: terminalId); // A delay is required to ensure the UI has sufficient time to update // before adding the next terminal. Without this delay, `_TerminalPageState::dispose()` // may be called prematurely while the tab widget is still in the tab controller. diff --git a/flutter/lib/models/terminal_model.dart b/flutter/lib/models/terminal_model.dart index a74241ccb..8356464b4 100644 --- a/flutter/lib/models/terminal_model.dart +++ b/flutter/lib/models/terminal_model.dart @@ -110,14 +110,16 @@ class TerminalModel with ChangeNotifier { void onReady() { parent.dialogManager.dismissAll(); - // Fire and forget - don't block onReady - openTerminal().catchError((e) { + // Fire and forget - don't block onReady. If the transport reconnects while + // this model is still open, re-send OpenTerminal so the remote service marks + // the persistent session active again and resumes output streaming. + openTerminal(force: _terminalOpened).catchError((e) { debugPrint('[TerminalModel] Error opening terminal: $e'); }); } - Future openTerminal() async { - if (_terminalOpened) return; + Future openTerminal({bool force = false}) async { + if (_terminalOpened && !force) return; // Request the remote side to open a terminal with default shell // The remote side will decide which shell to use based on its OS @@ -297,12 +299,16 @@ class TerminalModel with ChangeNotifier { }); final persistentSessions = - evt['persistent_sessions'] as List? ?? []; + (evt['persistent_sessions'] as List? ?? []) + .whereType() + .where((id) => !parent.terminalModels.containsKey(id)) + .toList(); if (kWindowId != null && persistentSessions.isNotEmpty) { DesktopMultiWindow.invokeMethod( kWindowId!, kWindowEventRestoreTerminalSessions, jsonEncode({ + 'peer_id': id, 'persistent_sessions': persistentSessions, })); }