diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 7324a8c18..f108fb5fd 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1216,7 +1216,7 @@ FFI get gFFI => _globalFFI; Future initGlobalFFI() async { debugPrint("_globalFFI init"); - _globalFFI = FFI(); + _globalFFI = FFI(null); debugPrint("_globalFFI init end"); // after `put`, can also be globally found by Get.find(); Get.put(_globalFFI, permanent: true); diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index f945a6292..59d0576a1 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -37,6 +37,9 @@ const String kWindowEventNewFileTransfer = "new_file_transfer"; const String kWindowEventNewPortForward = "new_port_forward"; const String kWindowEventActiveSession = "active_session"; const String kWindowEventGetRemoteList = "get_remote_list"; +const String kWindowEventGetSessionIdList = "get_session_id_list"; + +const String kWindowEventCloseForSeparateWindow = "close_for_separate_window"; const String kOptionSeparateRemoteWindow = "enable-separate-remote-window"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 61b5ad072..75ea16335 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; @@ -248,7 +249,7 @@ class _General extends StatefulWidget { class _GeneralState extends State<_General> { final RxBool serviceStop = Get.find(tag: 'stop-service'); - RxBool serviceBtnEabled = true.obs; + RxBool serviceBtnEnabled = true.obs; @override Widget build(BuildContext context) { @@ -300,14 +301,14 @@ class _GeneralState extends State<_General> { return _Card(title: 'Service', children: [ Obx(() => _Button(serviceStop.value ? 'Start' : 'Stop', () { () async { - serviceBtnEabled.value = false; + serviceBtnEnabled.value = false; await start_service(serviceStop.value); // enable the button after 1 second Future.delayed(const Duration(seconds: 1), () { - serviceBtnEabled.value = true; + serviceBtnEnabled.value = true; }); }(); - }, enabled: serviceBtnEabled.value)) + }, enabled: serviceBtnEnabled.value)) ]); } @@ -318,7 +319,14 @@ class _GeneralState extends State<_General> { isServer: false), _OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr'), _OptionCheckBox( - context, 'Separate remote window', kOptionSeparateRemoteWindow, isServer: false), + context, + 'Separate remote window', + kOptionSeparateRemoteWindow, + isServer: false, + update: () { + rustDeskWinManager.separateWindows(); + }, + ), ]; // though this is related to GUI, but opengl problem affects all users, so put in config rather than local children.add(Tooltip( @@ -1678,7 +1686,6 @@ Widget _OptionCheckBox(BuildContext context, String label, String key, isServer ? await mainSetBoolOption(key, option) : await mainSetLocalBoolOption(key, option); - ; update?.call(); } } diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 73d10a957..d684d1535 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -80,7 +80,7 @@ class _FileManagerPageState extends State @override void initState() { super.initState(); - _ffi = FFI(); + _ffi = FFI(null); _ffi.start(widget.id, isFileTransfer: true, password: widget.password, diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart index f4b4a6c3f..2a173c53b 100644 --- a/flutter/lib/desktop/pages/port_forward_page.dart +++ b/flutter/lib/desktop/pages/port_forward_page.dart @@ -54,7 +54,7 @@ class _PortForwardPageState extends State @override void initState() { super.initState(); - _ffi = FFI(); + _ffi = FFI(null); _ffi.start(widget.id, isPortForward: true, password: widget.password, diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 7ffa527fe..ecaa1b29a 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -28,10 +28,13 @@ import '../widgets/tabbar_widget.dart'; final SimpleWrapper _firstEnterImage = SimpleWrapper(false); +final Map noCloseSessionOnDispose = {}; + class RemotePage extends StatefulWidget { RemotePage({ Key? key, required this.id, + required this.sessionId, required this.password, required this.toolbarState, required this.tabController, @@ -40,6 +43,7 @@ class RemotePage extends StatefulWidget { }) : super(key: key); final String id; + final SessionID? sessionId; final String? password; final ToolbarState toolbarState; final String? switchUuid; @@ -91,7 +95,7 @@ class _RemotePageState extends State void initState() { super.initState(); _initStates(widget.id); - _ffi = FFI(); + _ffi = FFI(widget.sessionId); Get.put(_ffi, tag: widget.id); _ffi.imageModel.addCallbackOnFirstImage((String peerId) { showKBLayoutTypeChooserIfNeeded( @@ -199,6 +203,8 @@ class _RemotePageState extends State @override Future dispose() async { + final closeSession = noCloseSessionOnDispose.remove(widget.id) ?? false; + // https://github.com/flutter/flutter/issues/64935 super.dispose(); debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}"); @@ -209,11 +215,13 @@ class _RemotePageState extends State _ffi.dialogManager.hideMobileActionsOverlay(); _ffi.recordingModel.onClose(); _rawKeyFocusNode.dispose(); - await _ffi.close(); + await _ffi.close(closeSession: closeSession); _timer?.cancel(); _ffi.dialogManager.dismissAll(); - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, - overlays: SystemUiOverlay.values); + if (closeSession) { + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + } if (!Platform.isLinux) { await Wakelock.disable(); } diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 4ab731649..3e7f7f464 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -52,6 +52,7 @@ class _ConnectionTabPageState extends State { _toolbarState = ToolbarState(); RemoteCountState.init(); final peerId = params['id']; + final sessionId = params['session_id']; if (peerId != null) { ConnectionTypeState.init(peerId); tabController.onSelected = (id) { @@ -73,6 +74,7 @@ class _ConnectionTabPageState extends State { page: RemotePage( key: ValueKey(peerId), id: peerId, + sessionId: sessionId == null ? null : SessionID(sessionId), password: params['password'], toolbarState: _toolbarState, tabController: tabController, @@ -99,6 +101,7 @@ class _ConnectionTabPageState extends State { final args = jsonDecode(call.arguments); final id = args['id']; final switchUuid = args['switch_uuid']; + final sessionId = args['session_id']; windowOnTop(windowId()); ConnectionTypeState.init(id); _toolbarState.setShow( @@ -112,6 +115,7 @@ class _ConnectionTabPageState extends State { page: RemotePage( key: ValueKey(id), id: id, + sessionId: sessionId == null ? null : SessionID(sessionId), password: args['password'], toolbarState: _toolbarState, tabController: tabController, @@ -136,6 +140,15 @@ class _ConnectionTabPageState extends State { .map((e) => e.key) .toList() .join(','); + } else if (call.method == kWindowEventGetSessionIdList) { + return tabController.state.value.tabs + .map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}') + .toList() + .join(';'); + } else if (call.method == kWindowEventCloseForSeparateWindow) { + final peerId = call.arguments; + noCloseSessionOnDispose[peerId] = true; + tabController.closeBy(peerId); } _update_remote_count(); }); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 9c20a6a95..02c0b8281 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -1579,6 +1579,7 @@ class FFI { /// dialogManager use late to ensure init after main page binding [globalKey] late final dialogManager = OverlayDialogManager(); + late final bool isSessionAdded; late final SessionID sessionId; late final ImageModel imageModel; // session late final FfiModel ffiModel; // session @@ -1596,8 +1597,9 @@ class FFI { late final InputModel inputModel; // session late final ElevationModel elevationModel; // session - FFI() { - sessionId = isDesktop ? Uuid().v4obj() : _constSessionId; + FFI(SessionID? sId) { + isSessionAdded = sId != null; + sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId); imageModel = ImageModel(WeakReference(this)); ffiModel = FfiModel(WeakReference(this)); cursorModel = CursorModel(WeakReference(this)); @@ -1637,17 +1639,19 @@ class FFI { imageModel.id = id; cursorModel.id = id; } - // ignore: unused_local_variable - final addRes = bind.sessionAddSync( - sessionId: sessionId, - id: id, - isFileTransfer: isFileTransfer, - isPortForward: isPortForward, - isRdp: isRdp, - switchUuid: switchUuid ?? "", - forceRelay: forceRelay ?? false, - password: password ?? "", - ); + if (isSessionAdded) { + // ignore: unused_local_variable + final addRes = bind.sessionAddSync( + sessionId: sessionId, + id: id, + isFileTransfer: isFileTransfer, + isPortForward: isPortForward, + isRdp: isRdp, + switchUuid: switchUuid ?? "", + forceRelay: forceRelay ?? false, + password: password ?? "", + ); + } final stream = bind.sessionStart(sessionId: sessionId, id: id); final cb = ffiModel.startEventListener(sessionId, id); final useTextureRender = bind.mainUseTextureRender(); @@ -1712,7 +1716,7 @@ class FFI { } /// Close the remote session. - Future close() async { + Future close({bool closeSession = true}) async { closed = true; chatModel.close(); if (imageModel.image != null && !isWebDesktop) { @@ -1730,7 +1734,9 @@ class FFI { ffiModel.clear(); canvasModel.clear(); inputModel.resetModifiers(); - await bind.sessionClose(sessionId: sessionId); + if (closeSession) { + await bind.sessionClose(sessionId: sessionId); + } debugPrint('model $id closed'); id = ''; } diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 720b7dc95..d0c89cf3b 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -43,6 +43,46 @@ class RustDeskMultiWindowManager { final List _fileTransferWindows = List.empty(growable: true); final List _portForwardWindows = List.empty(growable: true); + separateWindows() async { + for (final windowId in _remoteDesktopWindows) { + final sessionIdList = await DesktopMultiWindow.invokeMethod( + windowId, kWindowEventGetSessionIdList, null); + if (sessionIdList != null) { + for (final idPair in sessionIdList.split(';')) { + final peerSession = idPair.split(','); + var params = { + 'type': WindowType.RemoteDesktop.index, + 'id': peerSession[0], + 'sessionId': peerSession[1], + }; + await newSessionWindow(WindowType.RemoteDesktop, peerSession[0], + jsonEncode(params), _remoteDesktopWindows); + await DesktopMultiWindow.invokeMethod( + windowId, kWindowEventCloseForSeparateWindow, peerSession[0]); + } + } + } + } + + newSessionWindow( + WindowType type, String remoteId, String msg, List windows) async { + final windowController = await DesktopMultiWindow.createWindow(msg); + windowController + ..setFrame(const Offset(0, 0) & + Size(1280 + windowController.windowId * 20, + 720 + windowController.windowId * 20)) + ..center() + ..setTitle(getWindowNameWithId( + remoteId, + overrideType: type, + )); + if (Platform.isMacOS) { + Future.microtask(() => windowController.show()); + } + registerActiveWindow(windowController.windowId); + windows.add(windowController.windowId); + } + Future newSession( WindowType type, String methodName, @@ -68,24 +108,6 @@ class RustDeskMultiWindowManager { } final msg = jsonEncode(params); - newSessionWindow() async { - final windowController = await DesktopMultiWindow.createWindow(msg); - windowController - ..setFrame(const Offset(0, 0) & - Size(1280 + windowController.windowId * 20, - 720 + windowController.windowId * 20)) - ..center() - ..setTitle(getWindowNameWithId( - remoteId, - overrideType: type, - )); - if (Platform.isMacOS) { - Future.microtask(() => windowController.show()); - } - registerActiveWindow(windowController.windowId); - windows.add(windowController.windowId); - } - // separate window for file transfer is not supported bool separateWindow = forceSeparateWindow || (type != WindowType.FileTransfer && @@ -111,11 +133,11 @@ class RustDeskMultiWindowManager { windows.add(windowController.windowId); return invokeRes; } else { - await newSessionWindow(); + await newSessionWindow(type, remoteId, msg, windows); } } else { if (windows.isEmpty) { - await newSessionWindow(); + await newSessionWindow(type, remoteId, msg, windows); } else { return call(type, methodName, msg); } diff --git a/src/flutter.rs b/src/flutter.rs index c35f7340d..f5711b823 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -782,11 +782,15 @@ pub fn session_start_( ); #[cfg(not(feature = "flutter_texture_render"))] log::info!("Session {} start, render by flutter paint widget", id); + let is_pre_added = session.event_stream.read().unwrap().is_some(); + session.close_event_stream(); *session.event_stream.write().unwrap() = Some(event_stream); - let session = session.clone(); - std::thread::spawn(move || { - io_loop(session); - }); + if !is_pre_added { + let session = session.clone(); + std::thread::spawn(move || { + io_loop(session); + }); + } Ok(()) } else { bail!("No session with peer id {}", id)