mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-20 13:53:21 +03:00
refact: restore terminals (#12334)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -64,6 +64,7 @@ const String kWindowEventNewFileTransfer = "new_file_transfer";
|
|||||||
const String kWindowEventNewViewCamera = "new_view_camera";
|
const String kWindowEventNewViewCamera = "new_view_camera";
|
||||||
const String kWindowEventNewPortForward = "new_port_forward";
|
const String kWindowEventNewPortForward = "new_port_forward";
|
||||||
const String kWindowEventNewTerminal = "new_terminal";
|
const String kWindowEventNewTerminal = "new_terminal";
|
||||||
|
const String kWindowEventRestoreTerminalSessions = "restore_terminal_sessions";
|
||||||
const String kWindowEventActiveSession = "active_session";
|
const String kWindowEventActiveSession = "active_session";
|
||||||
const String kWindowEventActiveDisplaySession = "active_display_session";
|
const String kWindowEventActiveDisplaySession = "active_display_session";
|
||||||
const String kWindowEventGetRemoteList = "get_remote_list";
|
const String kWindowEventGetRemoteList = "get_remote_list";
|
||||||
|
|||||||
@@ -171,6 +171,8 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
|||||||
forceRelay: args['forceRelay'],
|
forceRelay: args['forceRelay'],
|
||||||
connToken: args['connToken'],
|
connToken: args['connToken'],
|
||||||
));
|
));
|
||||||
|
} else if (call.method == kWindowEventRestoreTerminalSessions) {
|
||||||
|
_restoreSessions(call.arguments);
|
||||||
} else if (call.method == "onDestroy") {
|
} else if (call.method == "onDestroy") {
|
||||||
tabController.clear();
|
tabController.clear();
|
||||||
} else if (call.method == kWindowActionRebuild) {
|
} else if (call.method == kWindowActionRebuild) {
|
||||||
@@ -188,6 +190,32 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _restoreSessions(String arguments) async {
|
||||||
|
Map<String, dynamic>? args;
|
||||||
|
try {
|
||||||
|
args = jsonDecode(arguments) as Map<String, dynamic>;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error parsing JSON arguments in _restoreSessions: $e");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final persistentSessions =
|
||||||
|
args['persistent_sessions'] as List<dynamic>? ?? [];
|
||||||
|
final sortedSessions = persistentSessions.whereType<int>().toList()..sort();
|
||||||
|
for (final terminalId in sortedSessions) {
|
||||||
|
_addNewTerminalForCurrentPeer(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.
|
||||||
|
// This behavior is likely due to a race condition between the UI rendering lifecycle
|
||||||
|
// and the addition of new tabs. Attempts to use `_TerminalPageState::addPostFrameCallback()`
|
||||||
|
// to wait for the previous page to be ready were unsuccessful, as the observed call sequence is:
|
||||||
|
// `initState() 2 -> dispose() 2 -> postFrameCallback() 2`, followed by `initState() 3`.
|
||||||
|
// The `Future.delayed` approach mitigates this issue by introducing a buffer period,
|
||||||
|
// allowing the UI to stabilize before proceeding.
|
||||||
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool _handleKeyEvent(KeyEvent event) {
|
bool _handleKeyEvent(KeyEvent event) {
|
||||||
if (event is KeyDownEvent) {
|
if (event is KeyDownEvent) {
|
||||||
// Use Cmd+T on macOS, Ctrl+Shift+T on other platforms
|
// Use Cmd+T on macOS, Ctrl+Shift+T on other platforms
|
||||||
@@ -276,17 +304,20 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addNewTerminal(String peerId) {
|
void _addNewTerminal(String peerId, {int? terminalId}) {
|
||||||
// Find first tab for this peer to get connection parameters
|
// Find first tab for this peer to get connection parameters
|
||||||
final firstTab = tabController.state.value.tabs.firstWhere(
|
final firstTab = tabController.state.value.tabs.firstWhere(
|
||||||
(tab) => tab.key.startsWith('$peerId\_'),
|
(tab) => tab.key.startsWith('$peerId\_'),
|
||||||
);
|
);
|
||||||
if (firstTab.page is TerminalPage) {
|
if (firstTab.page is TerminalPage) {
|
||||||
final page = firstTab.page as TerminalPage;
|
final page = firstTab.page as TerminalPage;
|
||||||
final terminalId = _nextTerminalId++;
|
final newTerminalId = terminalId ?? _nextTerminalId++;
|
||||||
|
if (terminalId != null && terminalId >= _nextTerminalId) {
|
||||||
|
_nextTerminalId = terminalId + 1;
|
||||||
|
}
|
||||||
tabController.add(_createTerminalTab(
|
tabController.add(_createTerminalTab(
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
terminalId: terminalId,
|
terminalId: newTerminalId,
|
||||||
password: page.password,
|
password: page.password,
|
||||||
isSharedPassword: page.isSharedPassword,
|
isSharedPassword: page.isSharedPassword,
|
||||||
forceRelay: page.forceRelay,
|
forceRelay: page.forceRelay,
|
||||||
@@ -295,12 +326,12 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addNewTerminalForCurrentPeer() {
|
void _addNewTerminalForCurrentPeer({int? terminalId}) {
|
||||||
final currentTab = tabController.state.value.selectedTabInfo;
|
final currentTab = tabController.state.value.selectedTabInfo;
|
||||||
final parts = currentTab.key.split('_');
|
final parts = currentTab.key.split('_');
|
||||||
if (parts.isNotEmpty) {
|
if (parts.isNotEmpty) {
|
||||||
final peerId = parts[0];
|
final peerId = parts[0];
|
||||||
_addNewTerminal(peerId);
|
_addNewTerminal(peerId, terminalId: terminalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hbb/consts.dart';
|
||||||
|
import 'package:flutter_hbb/main.dart';
|
||||||
import 'package:xterm/xterm.dart';
|
import 'package:xterm/xterm.dart';
|
||||||
|
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
@@ -195,6 +198,17 @@ class TerminalModel with ChangeNotifier {
|
|||||||
debugPrint('[TerminalModel] Error processing buffered input: $e');
|
debugPrint('[TerminalModel] Error processing buffered input: $e');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final persistentSessions =
|
||||||
|
evt['persistent_sessions'] as List<dynamic>? ?? [];
|
||||||
|
if (kWindowId != null && persistentSessions.isNotEmpty) {
|
||||||
|
DesktopMultiWindow.invokeMethod(
|
||||||
|
kWindowId!,
|
||||||
|
kWindowEventRestoreTerminalSessions,
|
||||||
|
jsonEncode({
|
||||||
|
'persistent_sessions': persistentSessions,
|
||||||
|
}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
terminal.write('Failed to open terminal: $message\r\n');
|
terminal.write('Failed to open terminal: $message\r\n');
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule libs/hbb_common updated: 25e761f467...f91459c4ab
@@ -1119,6 +1119,9 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
("pid", json!(opened.pid)),
|
("pid", json!(opened.pid)),
|
||||||
("service_id", json!(&opened.service_id)),
|
("service_id", json!(&opened.service_id)),
|
||||||
];
|
];
|
||||||
|
if !opened.persistent_sessions.is_empty() {
|
||||||
|
event_data.push(("persistent_sessions", json!(opened.persistent_sessions)));
|
||||||
|
}
|
||||||
self.push_event_("terminal_response", &event_data, &[], &[]);
|
self.push_event_("terminal_response", &event_data, &[], &[]);
|
||||||
}
|
}
|
||||||
Some(Union::Data(data)) => {
|
Some(Union::Data(data)) => {
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ fn get_or_create_service(
|
|||||||
// Ensure cleanup task is running
|
// Ensure cleanup task is running
|
||||||
ensure_cleanup_task();
|
ensure_cleanup_task();
|
||||||
|
|
||||||
|
service.lock().unwrap().needs_session_sync = true;
|
||||||
|
|
||||||
Ok(service)
|
Ok(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,6 +542,7 @@ pub struct PersistentTerminalService {
|
|||||||
pub created_at: Instant,
|
pub created_at: Instant,
|
||||||
last_activity: Instant,
|
last_activity: Instant,
|
||||||
pub is_persistent: bool,
|
pub is_persistent: bool,
|
||||||
|
needs_session_sync: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PersistentTerminalService {
|
impl PersistentTerminalService {
|
||||||
@@ -550,6 +553,7 @@ impl PersistentTerminalService {
|
|||||||
created_at: Instant::now(),
|
created_at: Instant::now(),
|
||||||
last_activity: Instant::now(),
|
last_activity: Instant::now(),
|
||||||
is_persistent,
|
is_persistent,
|
||||||
|
needs_session_sync: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,6 +700,19 @@ impl TerminalServiceProxy {
|
|||||||
if self.is_persistent {
|
if self.is_persistent {
|
||||||
opened.service_id = self.service_id.clone();
|
opened.service_id = self.service_id.clone();
|
||||||
}
|
}
|
||||||
|
if service.needs_session_sync {
|
||||||
|
if service.sessions.len() > 1 {
|
||||||
|
// No need to include the current terminal in the list.
|
||||||
|
// Because the `persistent_sessions` is used to restore the other sessions.
|
||||||
|
opened.persistent_sessions = service
|
||||||
|
.sessions
|
||||||
|
.keys()
|
||||||
|
.filter(|&id| *id != open.terminal_id)
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
service.needs_session_sync = false;
|
||||||
|
}
|
||||||
response.set_opened(opened);
|
response.set_opened(opened);
|
||||||
|
|
||||||
// Send buffered output
|
// Send buffered output
|
||||||
@@ -856,6 +873,12 @@ impl TerminalServiceProxy {
|
|||||||
if self.is_persistent {
|
if self.is_persistent {
|
||||||
opened.service_id = service.service_id.clone();
|
opened.service_id = service.service_id.clone();
|
||||||
}
|
}
|
||||||
|
if service.needs_session_sync {
|
||||||
|
if !service.sessions.is_empty() {
|
||||||
|
opened.persistent_sessions = service.sessions.keys().cloned().collect();
|
||||||
|
}
|
||||||
|
service.needs_session_sync = false;
|
||||||
|
}
|
||||||
response.set_opened(opened);
|
response.set_opened(opened);
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|||||||
Reference in New Issue
Block a user