diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index b3ec3aa9d..0621bc807 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -5,6 +5,7 @@ import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/models/state_model.dart'; const double kDesktopRemoteTabBarHeight = 28.0; +const int kInvalidWindowId = -1; const int kMainWindowId = 0; const String kPeerPlatformWindows = "Windows"; @@ -39,6 +40,7 @@ const String kWindowEventGetSessionIdList = "get_session_id_list"; const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window"; const String kWindowEventCloseForSeparateWindow = "close_for_separate_window"; +const String kWindowEventSendNewWindowData = "send_new_window_data"; const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs"; const String kOptionOpenInTabs = "allow-open-in-tabs"; diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 3762a2b52..01f4e5ca0 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/desktop/pages/remote_page.dart'; import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; @@ -148,9 +149,40 @@ class _ConnectionTabPageState extends State { .toList() .join(';'); } else if (call.method == kWindowEventCloseForSeparateWindow) { - final peerId = call.arguments; + debugPrint('REMOVE ME ============================= ${call.arguments}'); + final peerId = call.arguments['peerId']; + final newWindowId = call.arguments['newWindowId']; + late RemotePage page; + try { + page = tabController.state.value.tabs.firstWhere((tab) { + return tab.key == peerId; + }).page as RemotePage; + } catch (e) { + debugPrint('Failed to find tab for peerId $peerId'); + return false; + } + final sendRes = await rustDeskWinManager.call( + newWindowId, + kWindowEventSendNewWindowData, + page.ffi.ffiModel.cachedPeerData) as bool; + if (!sendRes) { + return false; + } + // Pass the required data to new window. closeSessionOnDispose[peerId] = false; tabController.closeBy(peerId); + return true; + } else if (call.method == kWindowEventSendNewWindowData) { + if (peerId == null) { + return false; + } + if (tabController.state.value.tabs.isEmpty) { + return false; + } + final page = tabController.state.value.tabs[0].page as RemotePage; + page.ffi.ffiModel + .handleCachedPeerData(call.arguments as CachedPeerData, peerId!); + return true; } _update_remote_count(); }); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 4dc17f9c2..286e89a39 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -41,7 +41,19 @@ final _waitForImageDialogShow = {}; final _waitForFirstImage = {}; final _constSessionId = Uuid().v4obj(); +class CachedPeerData { + Map updatePrivacyMode = {}; + Map peerInfo = {}; + List> cursorDataList = []; + Map lastCursorId = {}; + bool secure = false; + bool direct = false; + + CachedPeerData(); +} + class FfiModel with ChangeNotifier { + CachedPeerData cachedPeerData = CachedPeerData(); PeerInfo _pi = PeerInfo(); Display _display = Display(); @@ -117,6 +129,8 @@ class FfiModel with ChangeNotifier { } setConnectionType(String peerId, bool secure, bool direct) { + cachedPeerData.secure = secure; + cachedPeerData.direct = direct; _secure = secure; _direct = direct; try { @@ -143,6 +157,22 @@ class FfiModel with ChangeNotifier { _permissions.clear(); } + handleCachedPeerData(CachedPeerData data, String peerId) async { + handleMsgBox({ + 'type': 'success', + 'title': 'Successful', + 'text': 'Connected, waiting for image...', + 'link': '', + }, sessionId, peerId); + updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId); + setConnectionType(peerId, data.secure, data.direct); + handlePeerInfo(data.peerInfo, peerId); + for (var element in data.cursorDataList) { + handleCursorData(element); + } + handleCursorId(data.lastCursorId); + } + // todo: why called by two position StreamEventHandler startEventListener(SessionID sessionId, String peerId) { return (evt) async { @@ -159,9 +189,9 @@ class FfiModel with ChangeNotifier { } else if (name == 'switch_display') { handleSwitchDisplay(evt, sessionId, peerId); } else if (name == 'cursor_data') { - await parent.target?.cursorModel.updateCursorData(evt); + await handleCursorData(evt); } else if (name == 'cursor_id') { - await parent.target?.cursorModel.updateCursorId(evt); + await handleCursorId(evt); } else if (name == 'cursor_position') { await parent.target?.cursorModel.updateCursorPosition(evt, peerId); } else if (name == 'clipboard') { @@ -453,6 +483,8 @@ class FfiModel with ChangeNotifier { /// Handle the peer info event based on [evt]. handlePeerInfo(Map evt, String peerId) async { + cachedPeerData.peerInfo = evt; + // recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs) bind.mainLoadRecentPeers(); @@ -568,9 +600,20 @@ class FfiModel with ChangeNotifier { return d; } + handleCursorId(Map evt) async { + cachedPeerData.lastCursorId = evt; + await parent.target?.cursorModel.updateCursorId(evt); + } + + handleCursorData(Map evt) async { + cachedPeerData.cursorDataList.add(evt); + await parent.target?.cursorModel.updateCursorData(evt); + } + /// Handle the peer info synchronization event based on [evt]. handleSyncPeerInfo(Map evt, SessionID sessionId) async { if (evt['displays'] != null) { + cachedPeerData.peerInfo['displays'] = evt['displays']; List displays = json.decode(evt['displays']); List newDisplays = []; for (int i = 0; i < displays.length; ++i) { @@ -1667,7 +1710,8 @@ class FFI { stream.listen((message) { if (closed) return; if (isSessionAdded && !isToNewWindowNotified.value) { - bind.sessionReadyToNewWindow(sessionId: sessionId); + // bind.sessionReadyToNewWindow(sessionId: sessionId); + bind.sessionRefresh(sessionId: sessionId); isToNewWindowNotified.value = true; } () async { diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 3f7d995b7..4230182d6 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -28,6 +28,13 @@ extension Index on int { } } +class MultiWindowCallResult { + int windowId; + dynamic result; + + MultiWindowCallResult(this.windowId, this.result); +} + /// Window Manager /// mainly use it in `Main Window` /// use it in sub window is not recommended @@ -49,7 +56,10 @@ class RustDeskMultiWindowManager { 'id': peerId, 'session_id': sessionId, }; - await _newSession( + // It's better to use the window id that returned by _newSession. + // Do not pass original window id to _newSession, + // as this function cann't promise the necessary data is passed to new window. + final multiWindowRes = await _newSession( false, WindowType.RemoteDesktop, kWindowEventNewRemoteDesktop, @@ -57,17 +67,21 @@ class RustDeskMultiWindowManager { _remoteDesktopWindows, jsonEncode(params), ); + // kWindowEventCloseForSeparateWindow will not only close the tab, but also pass the required data to new window. await DesktopMultiWindow.invokeMethod( - windowId, kWindowEventCloseForSeparateWindow, peerId); + windowId, kWindowEventCloseForSeparateWindow, { + 'peerId': peerId, + 'newWindowId': multiWindowRes.windowId, + }); } - newSessionWindow( + Future newSessionWindow( WindowType type, String remoteId, String msg, List windows) async { final windowController = await DesktopMultiWindow.createWindow(msg); + final windowId = windowController.windowId; windowController - ..setFrame(const Offset(0, 0) & - Size(1280 + windowController.windowId * 20, - 720 + windowController.windowId * 20)) + ..setFrame( + const Offset(0, 0) & Size(1280 + windowId * 20, 720 + windowId * 20)) ..center() ..setTitle(getWindowNameWithId( remoteId, @@ -76,11 +90,12 @@ class RustDeskMultiWindowManager { if (Platform.isMacOS) { Future.microtask(() => windowController.show()); } - registerActiveWindow(windowController.windowId); - windows.add(windowController.windowId); + registerActiveWindow(windowId); + windows.add(windowId); + return windowId; } - _newSession( + Future _newSession( bool openInTabs, WindowType type, String methodName, @@ -90,9 +105,10 @@ class RustDeskMultiWindowManager { ) async { if (openInTabs) { if (windows.isEmpty) { - await newSessionWindow(type, remoteId, msg, windows); + final windowId = await newSessionWindow(type, remoteId, msg, windows); + return MultiWindowCallResult(windowId, null); } else { - call(type, methodName, msg); + return call(type, methodName, msg); } } else { if (_inactiveWindows.isNotEmpty) { @@ -103,15 +119,16 @@ class RustDeskMultiWindowManager { await DesktopMultiWindow.invokeMethod(windowId, methodName, msg); WindowController.fromWindowId(windowId).show(); registerActiveWindow(windowId); - return; + return MultiWindowCallResult(windowId, null); } } } - await newSessionWindow(type, remoteId, msg, windows); + final windowId = await newSessionWindow(type, remoteId, msg, windows); + return MultiWindowCallResult(windowId, null); } } - Future newSession( + Future newSession( WindowType type, String methodName, String remoteId, @@ -143,15 +160,15 @@ class RustDeskMultiWindowManager { for (final windowId in windows) { if (await DesktopMultiWindow.invokeMethod( windowId, kWindowEventActiveSession, remoteId)) { - return; + return MultiWindowCallResult(windowId, null); } } } - await _newSession(openInTabs, type, methodName, remoteId, windows, msg); + return _newSession(openInTabs, type, methodName, remoteId, windows, msg); } - Future newRemoteDesktop( + Future newRemoteDesktop( String remoteId, { String? password, String? switchUuid, @@ -168,7 +185,7 @@ class RustDeskMultiWindowManager { ); } - Future newFileTransfer(String remoteId, + Future newFileTransfer(String remoteId, {String? password, bool? forceRelay}) async { return await newSession( WindowType.FileTransfer, @@ -180,7 +197,7 @@ class RustDeskMultiWindowManager { ); } - Future newPortForward(String remoteId, bool isRDP, + Future newPortForward(String remoteId, bool isRDP, {String? password, bool? forceRelay}) async { return await newSession( WindowType.PortForward, @@ -193,18 +210,22 @@ class RustDeskMultiWindowManager { ); } - Future call(WindowType type, String methodName, dynamic args) async { + Future call( + WindowType type, String methodName, dynamic args) async { final wnds = _findWindowsByType(type); if (wnds.isEmpty) { - return; + return MultiWindowCallResult(kInvalidWindowId, null); } for (final windowId in wnds) { if (_activeWindows.contains(windowId)) { - return await DesktopMultiWindow.invokeMethod( - windowId, methodName, args); + final res = + await DesktopMultiWindow.invokeMethod(windowId, methodName, args); + return MultiWindowCallResult(windowId, res); } } - return await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args); + final res = + await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args); + return MultiWindowCallResult(wnds[0], res); } List _findWindowsByType(WindowType type) {