terminal works basically. (#12189)

* terminal works basically.
todo:
- persistent
- sessions restore
- web
- mobile

* missed terminal persistent option change

* android sdk 34 -> 35

* +#![cfg_attr(lt_1_77, feature(c_str_literals))]

* fixing ci

* fix ci

* fix ci for android

* try "Fix Android SDK Platform 35"

* fix android 34

* revert flutter_plugin_android_lifecycle to 2.0.17 which used in rustdesk 1.4.0

* refactor, but break something of desktop terminal (new tab showing loading)

* fix connecting...
This commit is contained in:
RustDesk
2025-07-01 13:12:55 +08:00
committed by GitHub
parent ee5cdc3155
commit 5faf0ad3cf
130 changed files with 4064 additions and 4247 deletions

View File

@@ -23,6 +23,7 @@ import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/terminal_model.dart';
import 'package:flutter_hbb/plugin/event.dart';
import 'package:flutter_hbb/plugin/manager.dart';
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
@@ -311,6 +312,8 @@ class FfiModel with ChangeNotifier {
} else if (name == 'chat_server_mode') {
parent.target?.chatModel
.receive(int.parse(evt['id'] as String), evt['text'] ?? '');
} else if (name == 'terminal_response') {
parent.target?.routeTerminalResponse(evt);
} else if (name == 'file_dir') {
parent.target?.fileModel.receiveFileDir(evt);
} else if (name == 'empty_dirs') {
@@ -1076,9 +1079,14 @@ class FfiModel with ChangeNotifier {
sessionId: sessionId, arg: kOptionTouchMode) !=
'';
}
// FIXME: handle ViewCamera ConnType independently.
if (connType == ConnType.fileTransfer) {
parent.target?.fileModel.onReady();
} else if (connType == ConnType.terminal) {
// Call onReady on all registered terminal models
final models = parent.target?._terminalModels.values ?? [];
for (final model in models) {
model.onReady();
}
} else if (connType == ConnType.defaultConn ||
connType == ConnType.viewCamera) {
List<Display> newDisplays = [];
@@ -2828,7 +2836,14 @@ class ElevationModel with ChangeNotifier {
}
// The index values of `ConnType` are same as rust protobuf.
enum ConnType { defaultConn, fileTransfer, portForward, rdp, viewCamera }
enum ConnType {
defaultConn,
fileTransfer,
portForward,
rdp,
viewCamera,
terminal
}
/// Flutter state manager and data communication with the Rust core.
class FFI {
@@ -2863,6 +2878,12 @@ class FFI {
late final Peers favoritePeersModel; // global
late final Peers lanPeersModel; // global
// Terminal model registry for multiple terminals
final Map<int, TerminalModel> _terminalModels = {};
// Getter for terminal models
Map<int, TerminalModel> get terminalModels => _terminalModels;
FFI(SessionID? sId) {
sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId);
imageModel = ImageModel(WeakReference(this));
@@ -2910,6 +2931,7 @@ class FFI {
bool isViewCamera = false,
bool isPortForward = false,
bool isRdp = false,
bool isTerminal = false,
String? switchUuid,
String? password,
bool? isSharedPassword,
@@ -2925,7 +2947,10 @@ class FFI {
assert(
(!(isPortForward && isViewCamera)) &&
(!(isViewCamera && isPortForward)) &&
(!(isPortForward && isFileTransfer)),
(!(isPortForward && isFileTransfer)) &&
(!(isTerminal && isFileTransfer)) &&
(!(isTerminal && isViewCamera)) &&
(!(isTerminal && isPortForward)),
'more than one connect type');
if (isFileTransfer) {
connType = ConnType.fileTransfer;
@@ -2933,6 +2958,8 @@ class FFI {
connType = ConnType.viewCamera;
} else if (isPortForward) {
connType = ConnType.portForward;
} else if (isTerminal) {
connType = ConnType.terminal;
} else {
chatModel.resetClientMode();
connType = ConnType.defaultConn;
@@ -2953,6 +2980,7 @@ class FFI {
isViewCamera: isViewCamera,
isPortForward: isPortForward,
isRdp: isRdp,
isTerminal: isTerminal,
switchUuid: switchUuid ?? '',
forceRelay: forceRelay ?? false,
password: password ?? '',
@@ -3132,6 +3160,11 @@ class FFI {
Future<void> close({bool closeSession = true}) async {
closed = true;
chatModel.close();
// Close all terminal models
for (final model in _terminalModels.values) {
model.dispose();
}
_terminalModels.clear();
if (imageModel.image != null && !isWebDesktop) {
await setCanvasConfig(
sessionId,
@@ -3162,6 +3195,27 @@ class FFI {
Future<bool> invokeMethod(String method, [dynamic arguments]) async {
return await platformFFI.invokeMethod(method, arguments);
}
// Terminal model management
void registerTerminalModel(int terminalId, TerminalModel model) {
debugPrint('[FFI] Registering terminal model for terminal $terminalId');
_terminalModels[terminalId] = model;
}
void unregisterTerminalModel(int terminalId) {
debugPrint('[FFI] Unregistering terminal model for terminal $terminalId');
_terminalModels.remove(terminalId);
}
void routeTerminalResponse(Map<String, dynamic> evt) {
final int terminalId = evt['terminal_id'] ?? 0;
// Route to specific terminal model if it exists
final model = _terminalModels[terminalId];
if (model != null) {
model.handleTerminalResponse(evt);
}
}
}
const kInvalidResolutionValue = -1;
@@ -3266,9 +3320,6 @@ class PeerInfo with ChangeNotifier {
bool get isAmyuniIdd =>
platformAdditions[kPlatformAdditionsIddImpl] == 'amyuni_idd';
bool get isSupportViewCamera =>
platformAdditions[kPlatformAdditionsSupportViewCamera] == true;
Display? tryGetDisplay({int? display}) {
if (displays.isEmpty) {
return null;