diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index cf7506535..e96f02772 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -19,7 +19,6 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/platform_channel.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; -import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'; import 'package:uni_links/uni_links.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; @@ -47,11 +46,6 @@ var isMobile = isAndroid || isIOS; var version = ""; int androidVersion = 0; -/// Incriment count for textureId. -int _textureId = 0; -int get newTextureId => _textureId++; -final textureRenderer = TextureRgbaRenderer(); - /// only available for Windows target int windowsBuildNumber = 0; DesktopType? desktopType; @@ -549,7 +543,7 @@ closeConnection({String? id}) { } } -void window_on_top(int? id) async { +void windowOnTop(int? id) async { if (!isDesktop) { return; } @@ -1225,7 +1219,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); @@ -1417,8 +1411,24 @@ Future saveWindowPosition(WindowType type, {int? windowId}) async { sz.width, sz.height, position.dx, position.dy, isMaximized); debugPrint( "Saving frame: $windowId: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}"); + await bind.setLocalFlutterConfig( k: kWindowPrefix + type.name, v: pos.toString()); + + if (type == WindowType.RemoteDesktop && windowId != null) { + await _saveSessionWindowPosition(windowId, pos); + } +} + +Future _saveSessionWindowPosition(int windowId, LastWindowPosition pos) async { + final remoteList = await DesktopMultiWindow.invokeMethod( + windowId, kWindowEventGetRemoteList, null); + if (remoteList != null) { + for (final peerId in remoteList.split(',')) { + bind.sessionSetFlutterConfigByPeerId( + id: peerId, k: kWindowPrefix, v: pos.toString()); + } + } } Future _adjustRestoreMainWindowSize(double? width, double? height) async { @@ -1508,7 +1518,8 @@ Future _adjustRestoreMainWindowOffset( /// Restore window position and size on start /// Note that windowId must be provided if it's subwindow -Future restoreWindowPosition(WindowType type, {int? windowId}) async { +Future restoreWindowPosition(WindowType type, + {int? windowId, String? peerId}) async { if (bind .mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION") .isNotEmpty) { @@ -1517,13 +1528,31 @@ Future restoreWindowPosition(WindowType type, {int? windowId}) async { if (type != WindowType.Main && windowId == null) { debugPrint( "Error: windowId cannot be null when saving positions for sub window"); + return false; } - final pos = bind.getLocalFlutterConfig(k: kWindowPrefix + type.name); + + bool isRemotePeerPos = false; + String? pos; + if (type == WindowType.RemoteDesktop && windowId != null && peerId != null) { + pos = await bind.sessionGetFlutterConfigByPeerId( + id: peerId, k: kWindowPrefix); + isRemotePeerPos = pos != null; + } + pos ??= bind.getLocalFlutterConfig(k: kWindowPrefix + type.name); + var lpos = LastWindowPosition.loadFromString(pos); if (lpos == null) { debugPrint("no window position saved, ignoring position restoration"); return false; } + if (type == WindowType.RemoteDesktop && !isRemotePeerPos && windowId != null) { + if (lpos.offsetWidth != null) { + lpos.offsetWidth = lpos.offsetWidth! + windowId * 20; + } + if (lpos.offsetHeight != null) { + lpos.offsetHeight = lpos.offsetHeight! + windowId * 20; + } + } switch (type) { case WindowType.Main: @@ -1701,7 +1730,7 @@ bool handleUriLink({List? cmdArgs, Uri? uri, String? uriString}) { Future.delayed(Duration.zero, () { rustDeskWinManager.newRemoteDesktop(id!, password: password, - switch_uuid: switchUuid, + switchUuid: switchUuid, forceRelay: forceRelay); }); break; @@ -1766,17 +1795,20 @@ List? urlLinkToCmdArgs(Uri uri) { return null; } -connectMainDesktop(String id, - {required bool isFileTransfer, - required bool isTcpTunneling, - required bool isRDP, - bool? forceRelay}) async { +connectMainDesktop( + String id, { + required bool isFileTransfer, + required bool isTcpTunneling, + required bool isRDP, + bool? forceRelay, + bool forceSeparateWindow = false, +}) async { if (isFileTransfer) { await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay); } else if (isTcpTunneling || isRDP) { await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay); } else { - await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay); + await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay, forceSeparateWindow: forceSeparateWindow); } } @@ -1784,10 +1816,14 @@ connectMainDesktop(String id, /// If [isFileTransfer], starts a session only for file transfer. /// If [isTcpTunneling], starts a session only for tcp tunneling. /// If [isRDP], starts a session only for rdp. -connect(BuildContext context, String id, - {bool isFileTransfer = false, - bool isTcpTunneling = false, - bool isRDP = false}) async { +connect( + BuildContext context, + String id, { + bool isFileTransfer = false, + bool isTcpTunneling = false, + bool isRDP = false, + bool forceSeparateWindow = false, +}) async { if (id == '') return; id = id.replaceAll(' ', ''); final oldId = id; @@ -1798,18 +1834,22 @@ connect(BuildContext context, String id, if (isDesktop) { if (desktopType == DesktopType.main) { - await connectMainDesktop(id, - isFileTransfer: isFileTransfer, - isTcpTunneling: isTcpTunneling, - isRDP: isRDP, - forceRelay: forceRelay); + await connectMainDesktop( + id, + isFileTransfer: isFileTransfer, + isTcpTunneling: isTcpTunneling, + isRDP: isRDP, + forceRelay: forceRelay, + forceSeparateWindow: forceSeparateWindow, + ); } else { await rustDeskWinManager.call(WindowType.Main, kWindowConnect, { 'id': id, 'isFileTransfer': isFileTransfer, 'isTcpTunneling': isTcpTunneling, 'isRDP': isRDP, - "forceRelay": forceRelay, + 'forceRelay': forceRelay, + 'forceSeparateWindow': forceSeparateWindow, }); } } else { diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 44178bd5d..4e03edfbd 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -399,10 +399,14 @@ abstract class BasePeerCard extends StatelessWidget { Future>> _buildMenuItems(BuildContext context); MenuEntryBase _connectCommonAction( - BuildContext context, String id, String title, - {bool isFileTransfer = false, - bool isTcpTunneling = false, - bool isRDP = false}) { + BuildContext context, + String id, + String title, { + bool isFileTransfer = false, + bool isTcpTunneling = false, + bool isRDP = false, + bool forceSeparateWindow = false, + }) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( title, @@ -415,6 +419,7 @@ abstract class BasePeerCard extends StatelessWidget { isFileTransfer: isFileTransfer, isTcpTunneling: isTcpTunneling, isRDP: isRDP, + forceSeparateWindow: forceSeparateWindow, ); }, padding: menuPadding, @@ -423,13 +428,26 @@ abstract class BasePeerCard extends StatelessWidget { } @protected - MenuEntryBase _connectAction(BuildContext context, Peer peer) { + List> _connectActions(BuildContext context, Peer peer) { + final actions = [_connectAction(context, peer, false)]; + if (!mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) { + actions.add(_connectAction(context, peer, true)); + } + return actions; + } + + @protected + MenuEntryBase _connectAction( + BuildContext context, Peer peer, bool forceSeparateWindow) { return _connectCommonAction( - context, - peer.id, - peer.alias.isEmpty - ? translate('Connect') - : "${translate('Connect')} ${peer.id}"); + context, + peer.id, + (peer.alias.isEmpty + ? translate('Connect') + : '${translate('Connect')} ${peer.id}') + + (forceSeparateWindow ? ' (${translate('separate window')})' : ''), + forceSeparateWindow: forceSeparateWindow, + ); } @protected @@ -796,7 +814,7 @@ class RecentPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - _connectAction(context, peer), + ..._connectActions(context, peer), _transferFileAction(context, peer.id), ]; @@ -852,7 +870,7 @@ class FavoritePeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - _connectAction(context, peer), + ..._connectActions(context, peer), _transferFileAction(context, peer.id), ]; if (isDesktop && peer.platform != 'Android') { @@ -902,7 +920,7 @@ class DiscoveredPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - _connectAction(context, peer), + ..._connectActions(context, peer), _transferFileAction(context, peer.id), ]; @@ -954,7 +972,7 @@ class AddressBookPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - _connectAction(context, peer), + ..._connectActions(context, peer), _transferFileAction(context, peer.id), ]; if (isDesktop && peer.platform != 'Android') { @@ -1016,7 +1034,7 @@ class MyGroupPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - _connectAction(context, peer), + ..._connectActions(context, peer), _transferFileAction(context, peer.id), ]; if (isDesktop && peer.platform != 'Android') { diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 9dff28c22..59d0576a1 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -22,6 +22,8 @@ const String kAppTypeDesktopRemote = "remote"; const String kAppTypeDesktopFileTransfer = "file transfer"; const String kAppTypeDesktopPortForward = "port forward"; +const bool kCloseMultiWindowByHide = true; + const String kWindowMainWindowOnTop = "main_window_on_top"; const String kWindowGetWindowInfo = "get_window_info"; const String kWindowDisableGrabKeyboard = "disable_grab_keyboard"; @@ -30,6 +32,17 @@ const String kWindowEventHide = "hide"; const String kWindowEventShow = "show"; const String kWindowConnect = "connect"; +const String kWindowEventNewRemoteDesktop = "new_remote_desktop"; +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"; + const String kUniLinksPrefix = "rustdesk://"; const String kUrlActionClose = "close"; diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 32ca6c20d..43c73b0b5 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -527,7 +527,7 @@ class _DesktopHomePageState extends State debugPrint( "[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId"); if (call.method == kWindowMainWindowOnTop) { - window_on_top(null); + windowOnTop(null); } else if (call.method == kWindowGetWindowInfo) { final screen = (await window_size.getWindowInfo()).screen; if (screen == null) { @@ -554,7 +554,13 @@ class _DesktopHomePageState extends State } else if (call.method == kWindowEventShow) { await rustDeskWinManager.registerActiveWindow(call.arguments["id"]); } else if (call.method == kWindowEventHide) { - await rustDeskWinManager.unregisterActiveWindow(call.arguments["id"]); + final wId = call.arguments['id']; + final isSeparateWindowEnabled = + mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow); + if (isSeparateWindowEnabled && !kCloseMultiWindowByHide) { + await rustDeskWinManager.destroyWindow(wId); + } + await rustDeskWinManager.unregisterActiveWindow(wId); } else if (call.method == kWindowConnect) { await connectMainDesktop( call.arguments['id'], @@ -562,6 +568,7 @@ class _DesktopHomePageState extends State isTcpTunneling: call.arguments['isTcpTunneling'], isRDP: call.arguments['isRDP'], forceRelay: call.arguments['forceRelay'], + forceSeparateWindow: call.arguments['forceSeparateWindow'], ); } }); diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index db3840918..9072643ea 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)) ]); } @@ -316,7 +317,20 @@ class _GeneralState extends State<_General> { _OptionCheckBox(context, 'Confirm before closing multiple tabs', 'enable-confirm-closing-tabs', isServer: false), - _OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr') + _OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr'), + _OptionCheckBox( + context, + 'Separate remote window', + kOptionSeparateRemoteWindow, + isServer: false, + update: () { + final useSeparateWindow = + mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow); + if (useSeparateWindow) { + rustDeskWinManager.separateWindows(); + } + }, + ), ]; // though this is related to GUI, but opengl problem affects all users, so put in config rather than local children.add(Tooltip( @@ -1671,12 +1685,13 @@ Widget _OptionCheckBox(BuildContext context, String label, String key, var ref = value.obs; onChanged(option) async { if (option != null) { - ref.value = option; if (reverse) option = !option; isServer ? await mainSetBoolOption(key, option) : await mainSetLocalBoolOption(key, option); - ; + ref.value = isServer + ? mainGetBoolOptionSync(key) + : mainGetLocalBoolOptionSync(key); 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/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index c011fe48d..1412ba059 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -60,10 +60,10 @@ class _FileManagerTabPageState extends State { print( "[FileTransfer] call ${call.method} with args ${call.arguments} from window $fromWindowId to ${windowId()}"); // for simplify, just replace connectionId - if (call.method == "new_file_transfer") { + if (call.method == kWindowEventNewFileTransfer) { final args = jsonDecode(call.arguments); final id = args['id']; - window_on_top(windowId()); + windowOnTop(windowId()); tabController.add(TabInfo( key: id, label: id, 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/port_forward_tab_page.dart b/flutter/lib/desktop/pages/port_forward_tab_page.dart index df824e431..621f393e0 100644 --- a/flutter/lib/desktop/pages/port_forward_tab_page.dart +++ b/flutter/lib/desktop/pages/port_forward_tab_page.dart @@ -60,11 +60,11 @@ class _PortForwardTabPageState extends State { debugPrint( "[Port Forward] call ${call.method} with args ${call.arguments} from window $fromWindowId"); // for simplify, just replace connectionId - if (call.method == "new_port_forward") { + if (call.method == kWindowEventNewPortForward) { final args = jsonDecode(call.arguments); final id = args['id']; final isRDP = args['isRDP']; - window_on_top(windowId()); + windowOnTop(windowId()); if (tabController.state.value.tabs.indexWhere((e) => e.key == id) >= 0) { debugPrint("port forward $id exists"); diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 35705a283..32faf08ae 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -18,6 +18,7 @@ import '../../common/widgets/remote_input.dart'; import '../../common.dart'; import '../../common/widgets/dialog.dart'; import '../../models/model.dart'; +import '../../models/desktop_render_texture.dart'; import '../../models/platform_model.dart'; import '../../common/shared_state.dart'; import '../../utils/image.dart'; @@ -27,10 +28,13 @@ import '../widgets/tabbar_widget.dart'; final SimpleWrapper _firstEnterImage = SimpleWrapper(false); +final Map closeSessionOnDispose = {}; + class RemotePage extends StatefulWidget { RemotePage({ Key? key, required this.id, + required this.sessionId, required this.password, required this.toolbarState, required this.tabController, @@ -39,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; @@ -66,9 +71,7 @@ class _RemotePageState extends State late RxBool _zoomCursor; late RxBool _remoteCursorMoved; late RxBool _keyboardEnabled; - late RxInt _textureId; - late int _textureKey; - final useTextureRender = bind.mainUseTextureRender(); + late RenderTexture _renderTexture; final _blockableOverlayState = BlockableOverlayState(); @@ -86,15 +89,13 @@ class _RemotePageState extends State _showRemoteCursor = ShowRemoteCursorState.find(id); _keyboardEnabled = KeyboardEnabledState.find(id); _remoteCursorMoved = RemoteCursorMovedState.find(id); - _textureKey = newTextureId; - _textureId = RxInt(-1); } @override 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( @@ -115,17 +116,13 @@ class _RemotePageState extends State Wakelock.enable(); } // Register texture. - _textureId.value = -1; - if (useTextureRender) { - textureRenderer.createTexture(_textureKey).then((id) async { - debugPrint("id: $id, texture_key: $_textureKey"); - if (id != -1) { - final ptr = await textureRenderer.getTexturePtr(_textureKey); - platformFFI.registerTexture(sessionId, ptr); - _textureId.value = id; - } - }); + if (mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) { + _renderTexture = renderTexture; + } else { + _renderTexture = RenderTexture(); } + _renderTexture.create(sessionId); + _ffi.ffiModel.updateEventListener(sessionId, widget.id); bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); _ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId); @@ -206,26 +203,25 @@ class _RemotePageState extends State @override Future dispose() async { + final closeSession = closeSessionOnDispose.remove(widget.id) ?? true; + // https://github.com/flutter/flutter/issues/64935 super.dispose(); - debugPrint("REMOTE PAGE dispose ${widget.id}"); - if (useTextureRender) { - platformFFI.registerTexture(sessionId, 0); - // sleep for a while to avoid the texture is used after it's unregistered. - await Future.delayed(Duration(milliseconds: 100)); - await textureRenderer.closeTexture(_textureKey); - } + debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}"); + await _renderTexture.destroy(); // ensure we leave this session, this is a double check bind.sessionEnterOrLeave(sessionId: sessionId, enter: false); DesktopMultiWindow.removeListener(this); _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(); } @@ -392,8 +388,8 @@ class _RemotePageState extends State cursorOverImage: _cursorOverImage, keyboardEnabled: _keyboardEnabled, remoteCursorMoved: _remoteCursorMoved, - textureId: _textureId, - useTextureRender: useTextureRender, + textureId: _renderTexture.textureId, + useTextureRender: _renderTexture.useTextureRender, listenerBuilder: (child) => _buildRawTouchAndPointerRegion(child, enterView, leaveView), ); diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 2e553e724..080895729 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, @@ -95,11 +97,12 @@ class _ConnectionTabPageState extends State { "[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId"); // for simplify, just replace connectionId - if (call.method == "new_remote_desktop") { + if (call.method == kWindowEventNewRemoteDesktop) { final args = jsonDecode(call.arguments); final id = args['id']; final switchUuid = args['switch_uuid']; - window_on_top(windowId()); + final sessionId = args['session_id']; + windowOnTop(windowId()); ConnectionTypeState.init(id); _toolbarState.setShow( bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y'); @@ -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, @@ -125,11 +129,37 @@ class _ConnectionTabPageState extends State { tabController.clear(); } else if (call.method == kWindowActionRebuild) { reloadCurrentWindow(); + } else if (call.method == kWindowEventActiveSession) { + final jumpOk = tabController.jumpToByKey(call.arguments); + if (jumpOk) { + windowOnTop(windowId()); + } + return jumpOk; + } else if (call.method == kWindowEventGetRemoteList) { + return tabController.state.value.tabs + .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; + closeSessionOnDispose[peerId] = false; + tabController.closeBy(peerId); } _update_remote_count(); }); Future.delayed(Duration.zero, () { - restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId()); + restoreWindowPosition( + WindowType.RemoteDesktop, + windowId: windowId(), + peerId: tabController.state.value.tabs.isEmpty + ? null + : tabController.state.value.tabs[0].key, + ); }); } diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index adafaf0aa..2a7d1452e 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -13,6 +13,7 @@ import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; +import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; @@ -146,8 +147,10 @@ class DesktopTabController { /// For addTab, tabPage has not been initialized, set [callOnSelected] to false, /// and call [onSelected] at the end of initState - void jumpTo(int index, {bool callOnSelected = true}) { - if (!isDesktop || index < 0) return; + bool jumpTo(int index, {bool callOnSelected = true}) { + if (!isDesktop || index < 0) { + return false; + } state.update((val) { val!.selected = index; Future.delayed(Duration(milliseconds: 100), (() { @@ -168,8 +171,13 @@ class DesktopTabController { onSelected?.call(key); } } + return true; } + bool jumpToByKey(String key, {bool callOnSelected = true}) => + jumpTo(state.value.tabs.indexWhere((tab) => tab.key == key), + callOnSelected: callOnSelected); + void closeBy(String? key) { if (!isDesktop) return; assert(onRemoved != null); @@ -574,6 +582,8 @@ class WindowActionPanelState extends State } await windowManager.hide(); } else { + renderTexture.destroy(); + // it's safe to hide the subwindow final controller = WindowController.fromWindowId(kWindowId!); if (Platform.isMacOS && await controller.isFullScreen()) { diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 4578c2f21..14db061bd 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -197,7 +197,7 @@ void runMultiWindow( switch (appType) { case kAppTypeDesktopRemote: await restoreWindowPosition(WindowType.RemoteDesktop, - windowId: kWindowId!); + windowId: kWindowId!, peerId: argument['id'] as String?); break; case kAppTypeDesktopFileTransfer: await restoreWindowPosition(WindowType.FileTransfer, @@ -250,7 +250,7 @@ showCmWindow({bool isStartup = false}) async { await windowManager.minimize(); //needed await windowManager.setSizeAlignment( kConnectionManagerWindowSizeClosedChat, Alignment.topRight); - window_on_top(null); + windowOnTop(null); } } } diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index fe1de2512..898427351 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -367,7 +367,7 @@ class ChatModel with ChangeNotifier { // not minisized: add count if (await WindowController.fromWindowId(stateGlobal.windowId) .isMinimized()) { - window_on_top(stateGlobal.windowId); + windowOnTop(stateGlobal.windowId); if (notSelected) { tabController.jumpTo(index); } @@ -386,7 +386,7 @@ class ChatModel with ChangeNotifier { return; } if (isDesktop) { - window_on_top(null); + windowOnTop(null); // disable auto jumpTo other tab when hasFocus, and mark unread message final currentSelectedTab = session.serverModel.tabController.state.value.selectedTabInfo; diff --git a/flutter/lib/models/desktop_render_texture.dart b/flutter/lib/models/desktop_render_texture.dart new file mode 100644 index 000000000..37d387eb2 --- /dev/null +++ b/flutter/lib/models/desktop_render_texture.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'; + +import '../../common.dart'; +import './platform_model.dart'; + +class RenderTexture { + final RxInt textureId = RxInt(-1); + int _textureKey = -1; + SessionID? _sessionId; + final useTextureRender = bind.mainUseTextureRender(); + + final textureRenderer = TextureRgbaRenderer(); + + RenderTexture(); + + create(SessionID sessionId) { + if (useTextureRender) { + _textureKey = bind.getNextTextureKey(); + _sessionId = sessionId; + + textureRenderer.createTexture(_textureKey).then((id) async { + debugPrint("id: $id, texture_key: $_textureKey"); + if (id != -1) { + final ptr = await textureRenderer.getTexturePtr(_textureKey); + platformFFI.registerTexture(sessionId, ptr); + textureId.value = id; + } + }); + } + } + + destroy() async { + if (useTextureRender && _textureKey != -1 && _sessionId != null) { + platformFFI.registerTexture(_sessionId!, 0); + await textureRenderer.closeTexture(_textureKey); + _textureKey = -1; + } + } + + static final RenderTexture instance = RenderTexture(); +} + +// Global instance for separate texture +final renderTexture = RenderTexture.instance; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 4a672f200..5cce43cff 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -260,7 +260,7 @@ class FfiModel with ChangeNotifier { }); break; default: - window_on_top(null); + windowOnTop(null); break; } } @@ -1583,6 +1583,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 @@ -1600,8 +1601,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)); @@ -1641,23 +1643,31 @@ 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(); + + final SimpleWrapper isToNewWindowNotified = SimpleWrapper(false); // Preserved for the rgba data. stream.listen((message) { if (closed) return; + if (isSessionAdded && !isToNewWindowNotified.value) { + bind.sessionReadyToNewWindow(sessionId: sessionId); + isToNewWindowNotified.value = true; + } () async { if (message is EventToUI_Event) { if (message.field0 == "close") { @@ -1717,7 +1727,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) { @@ -1735,7 +1745,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/models/server_model.dart b/flutter/lib/models/server_model.dart index 6867aa6fe..6e6dd2b82 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -473,7 +473,7 @@ class ServerModel with ChangeNotifier { onTap: () {}, page: desktop.buildConnectionCard(client))); Future.delayed(Duration.zero, () async { - if (!hideCm) window_on_top(null); + if (!hideCm) windowOnTop(null); }); // Only do the hidden task when on Desktop. if (client.authorized && isDesktop) { @@ -612,7 +612,7 @@ class ServerModel with ChangeNotifier { if (client.incomingVoiceCall) { // Has incoming phone call, let's set the window on top. Future.delayed(Duration.zero, () { - window_on_top(null); + windowOnTop(null); }); } notifyListeners(); diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 1b5eaf2a1..b0959246b 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -5,6 +5,7 @@ import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/common.dart'; /// must keep the order @@ -35,146 +36,204 @@ class RustDeskMultiWindowManager { static final instance = RustDeskMultiWindowManager._(); - final List _activeWindows = List.empty(growable: true); + final Set _inactiveWindows = {}; + final Set _activeWindows = {}; final List _windowActiveCallbacks = List.empty(growable: true); - int? _remoteDesktopWindowId; - int? _fileTransferWindowId; - int? _portForwardWindowId; + final List _remoteDesktopWindows = List.empty(growable: true); + final List _fileTransferWindows = List.empty(growable: true); + final List _portForwardWindows = List.empty(growable: true); - Future newRemoteDesktop( - String remoteId, { + separateWindows() async { + for (final windowId in _remoteDesktopWindows.toList()) { + final String sessionIdList = await DesktopMultiWindow.invokeMethod( + windowId, kWindowEventGetSessionIdList, null); + final idList = sessionIdList.split(';'); + if (idList.length <= 1) { + continue; + } + for (final idPair in idList.sublist(1)) { + final peerSession = idPair.split(','); + var params = { + 'type': WindowType.RemoteDesktop.index, + 'id': peerSession[0], + 'session_id': peerSession[1], + }; + await _newSession( + true, + WindowType.RemoteDesktop, + kWindowEventNewRemoteDesktop, + peerSession[0], + _remoteDesktopWindows, + jsonEncode(params), + ); + 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); + } + + _newSession( + bool separateWindow, + WindowType type, + String methodName, + String remoteId, + List windows, + String msg, + ) async { + if (separateWindow) { + if (kCloseMultiWindowByHide && _inactiveWindows.isNotEmpty) { + final windowId = _inactiveWindows.first; + final invokeRes = + await DesktopMultiWindow.invokeMethod(windowId, methodName, msg); + final windowController = WindowController.fromWindowId(windowId); + windowController.show(); + registerActiveWindow(windowController.windowId); + windows.add(windowController.windowId); + return invokeRes; + } else { + await newSessionWindow(type, remoteId, msg, windows); + } + } else { + if (windows.isEmpty) { + await newSessionWindow(type, remoteId, msg, windows); + } else { + return call(type, methodName, msg); + } + } + } + + Future newSession( + WindowType type, + String methodName, + String remoteId, + List windows, { String? password, - String? switch_uuid, bool? forceRelay, + String? switchUuid, + bool? isRDP, + bool forceSeparateWindow = false, }) async { var params = { - "type": WindowType.RemoteDesktop.index, + "type": type.index, "id": remoteId, "password": password, "forceRelay": forceRelay }; - if (switch_uuid != null) { - params['switch_uuid'] = switch_uuid; + if (switchUuid != null) { + params['switch_uuid'] = switchUuid; + } + if (isRDP != null) { + params['isRDP'] = isRDP; } final msg = jsonEncode(params); - try { - final ids = await DesktopMultiWindow.getAllSubWindowIds(); - if (!ids.contains(_remoteDesktopWindowId)) { - _remoteDesktopWindowId = null; + // separate window for file transfer is not supported + bool separateWindow = forceSeparateWindow || + (type != WindowType.FileTransfer && + mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)); + + if (windows.length > 1 || separateWindow) { + for (final windowId in windows) { + if (await DesktopMultiWindow.invokeMethod( + windowId, kWindowEventActiveSession, remoteId)) { + return; + } } - } on Error { - _remoteDesktopWindowId = null; - } - if (_remoteDesktopWindowId == null) { - final remoteDesktopController = - await DesktopMultiWindow.createWindow(msg); - remoteDesktopController - ..setFrame(const Offset(0, 0) & const Size(1280, 720)) - ..center() - ..setTitle(getWindowNameWithId(remoteId, - overrideType: WindowType.RemoteDesktop)); - if (Platform.isMacOS) { - Future.microtask(() => remoteDesktopController.show()); - } - registerActiveWindow(remoteDesktopController.windowId); - _remoteDesktopWindowId = remoteDesktopController.windowId; - } else { - return call(WindowType.RemoteDesktop, "new_remote_desktop", msg); } + + await _newSession(separateWindow, type, methodName, remoteId, windows, msg); + } + + Future newRemoteDesktop( + String remoteId, { + String? password, + String? switchUuid, + bool? forceRelay, + bool forceSeparateWindow = false, + }) async { + return await newSession( + WindowType.RemoteDesktop, + kWindowEventNewRemoteDesktop, + remoteId, + _remoteDesktopWindows, + password: password, + forceRelay: forceRelay, + switchUuid: switchUuid, + forceSeparateWindow: forceSeparateWindow, + ); } Future newFileTransfer(String remoteId, {String? password, bool? forceRelay}) async { - var msg = jsonEncode({ - "type": WindowType.FileTransfer.index, - "id": remoteId, - "password": password, - "forceRelay": forceRelay, - }); - - try { - final ids = await DesktopMultiWindow.getAllSubWindowIds(); - if (!ids.contains(_fileTransferWindowId)) { - _fileTransferWindowId = null; - } - } on Error { - _fileTransferWindowId = null; - } - if (_fileTransferWindowId == null) { - final fileTransferController = await DesktopMultiWindow.createWindow(msg); - fileTransferController - ..setFrame(const Offset(0, 0) & const Size(1280, 720)) - ..center() - ..setTitle(getWindowNameWithId(remoteId, - overrideType: WindowType.FileTransfer)); - if (Platform.isMacOS) { - Future.microtask(() => fileTransferController.show()); - } - registerActiveWindow(fileTransferController.windowId); - _fileTransferWindowId = fileTransferController.windowId; - } else { - return call(WindowType.FileTransfer, "new_file_transfer", msg); - } + return await newSession( + WindowType.FileTransfer, + kWindowEventNewFileTransfer, + remoteId, + _fileTransferWindows, + password: password, + forceRelay: forceRelay, + ); } Future newPortForward(String remoteId, bool isRDP, {String? password, bool? forceRelay}) async { - final msg = jsonEncode({ - "type": WindowType.PortForward.index, - "id": remoteId, - "isRDP": isRDP, - "password": password, - "forceRelay": forceRelay, - }); - - try { - final ids = await DesktopMultiWindow.getAllSubWindowIds(); - if (!ids.contains(_portForwardWindowId)) { - _portForwardWindowId = null; - } - } on Error { - _portForwardWindowId = null; - } - if (_portForwardWindowId == null) { - final portForwardController = await DesktopMultiWindow.createWindow(msg); - portForwardController - ..setFrame(const Offset(0, 0) & const Size(1280, 720)) - ..center() - ..setTitle(getWindowNameWithId(remoteId, - overrideType: WindowType.PortForward)); - if (Platform.isMacOS) { - Future.microtask(() => portForwardController.show()); - } - registerActiveWindow(portForwardController.windowId); - _portForwardWindowId = portForwardController.windowId; - } else { - return call(WindowType.PortForward, "new_port_forward", msg); - } + return await newSession( + WindowType.PortForward, + kWindowEventNewPortForward, + remoteId, + _portForwardWindows, + password: password, + forceRelay: forceRelay, + isRDP: isRDP, + ); } Future call(WindowType type, String methodName, dynamic args) async { - int? windowId = findWindowByType(type); - if (windowId == null) { + final wnds = _findWindowsByType(type); + if (wnds.isEmpty) { return; } - return await DesktopMultiWindow.invokeMethod(windowId, methodName, args); + for (final windowId in wnds) { + if (_activeWindows.contains(windowId)) { + return await DesktopMultiWindow.invokeMethod(windowId, methodName, args); + } + } + return await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args); } - int? findWindowByType(WindowType type) { + List _findWindowsByType(WindowType type) { switch (type) { case WindowType.Main: - return 0; + return [0]; case WindowType.RemoteDesktop: - return _remoteDesktopWindowId; + return _remoteDesktopWindows; case WindowType.FileTransfer: - return _fileTransferWindowId; + return _fileTransferWindows; case WindowType.PortForward: - return _portForwardWindowId; + return _portForwardWindows; case WindowType.Unknown: break; } - return null; + return []; } void clearWindowType(WindowType type) { @@ -182,13 +241,13 @@ class RustDeskMultiWindowManager { case WindowType.Main: return; case WindowType.RemoteDesktop: - _remoteDesktopWindowId = null; + _remoteDesktopWindows.clear(); break; case WindowType.FileTransfer: - _fileTransferWindowId = null; + _fileTransferWindows.clear(); break; case WindowType.PortForward: - _portForwardWindowId = null; + _portForwardWindows.clear(); break; case WindowType.Unknown: break; @@ -209,27 +268,37 @@ class RustDeskMultiWindowManager { // skip main window, use window manager instead return; } - int? wId = findWindowByType(type); - if (wId != null) { + + List windows = []; + try { + windows = await DesktopMultiWindow.getAllSubWindowIds(); + } catch (e) { + debugPrint('Failed to getAllSubWindowIds of $type, $e'); + return; + } + + if (windows.isEmpty) { + return; + } + for (final wId in windows) { debugPrint("closing multi window: ${type.toString()}"); await saveWindowPosition(type, windowId: wId); try { - final ids = await DesktopMultiWindow.getAllSubWindowIds(); - if (!ids.contains(wId)) { - // no such window already - return; - } + // final ids = await DesktopMultiWindow.getAllSubWindowIds(); + // if (!ids.contains(wId)) { + // // no such window already + // return; + // } await WindowController.fromWindowId(wId).setPreventClose(false); await WindowController.fromWindowId(wId).close(); - // unregister the sub window in the main window. - unregisterActiveWindow(wId); + _activeWindows.remove(wId); } catch (e) { debugPrint("$e"); return; - } finally { - clearWindowType(type); } } + await _notifyActiveWindow(); + clearWindowType(type); } Future> getAllSubWindowIds() async { @@ -245,7 +314,7 @@ class RustDeskMultiWindowManager { } } - List getActiveWindows() { + Set getActiveWindows() { return _activeWindows; } @@ -256,14 +325,19 @@ class RustDeskMultiWindowManager { } Future registerActiveWindow(int windowId) async { - if (_activeWindows.contains(windowId)) { - // ignore - } else { - _activeWindows.add(windowId); - } + _activeWindows.add(windowId); + _inactiveWindows.remove(windowId); await _notifyActiveWindow(); } + Future destroyWindow(int windowId) async { + await WindowController.fromWindowId(windowId).setPreventClose(false); + await WindowController.fromWindowId(windowId).close(); + _remoteDesktopWindows.remove(windowId); + _fileTransferWindows.remove(windowId); + _portForwardWindows.remove(windowId); + } + /// Remove active window which has [`windowId`] /// /// [Availability] @@ -271,10 +345,9 @@ class RustDeskMultiWindowManager { /// For other windows, please post a unregister(hide) event to main window handler: /// `rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId!});` Future unregisterActiveWindow(int windowId) async { - if (!_activeWindows.contains(windowId)) { - // ignore - } else { - _activeWindows.remove(windowId); + _activeWindows.remove(windowId); + if (windowId != kMainWindowId) { + _inactiveWindows.add(windowId); } await _notifyActiveWindow(); } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 82c3794bb..eee857f60 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1008,6 +1008,11 @@ impl Remote { } } Some(login_response::Union::PeerInfo(pi)) => { + #[cfg(feature = "flutter")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + self.handler.cache_flutter.write().unwrap().pi = pi.clone(); + } self.handler.handle_peer_info(pi); #[cfg(not(feature = "flutter"))] self.check_clipboard_file_context(); @@ -1055,9 +1060,22 @@ impl Remote { _ => {} }, Some(message::Union::CursorData(cd)) => { + #[cfg(feature = "flutter")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + let mut lock = self.handler.cache_flutter.write().unwrap(); + if !lock.cursor_data.contains_key(&cd.id) { + lock.cursor_data.insert(cd.id, cd.clone()); + } + } self.handler.set_cursor_data(cd); } Some(message::Union::CursorId(id)) => { + #[cfg(feature = "flutter")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + self.handler.cache_flutter.write().unwrap().cursor_id = id; + } self.handler.set_cursor_id(id.to_string()); } Some(message::Union::CursorPosition(cp)) => { @@ -1274,6 +1292,16 @@ impl Remote { } } Some(misc::Union::SwitchDisplay(s)) => { + #[cfg(feature = "flutter")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + self.handler + .cache_flutter + .write() + .unwrap() + .sp + .replace(s.clone()); + } self.handler.handle_peer_switch_display(&s); self.video_sender.send(MediaData::Reset).ok(); if s.width > 0 && s.height > 0 { diff --git a/src/flutter.rs b/src/flutter.rs index a754f913b..c0766c008 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -186,7 +186,7 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn( #[derive(Clone)] struct VideoRenderer { // TextureRgba pointer in flutter native. - ptr: usize, + ptr: Arc>, width: usize, height: usize, on_rgba_func: Option>, @@ -214,7 +214,7 @@ impl Default for VideoRenderer { } }; Self { - ptr: 0, + ptr: Default::default(), width: 0, height: 0, on_rgba_func, @@ -231,7 +231,8 @@ impl VideoRenderer { } pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) { - if self.ptr == usize::default() { + let ptr = self.ptr.read().unwrap(); + if *ptr == usize::default() { return; } @@ -243,7 +244,7 @@ impl VideoRenderer { if let Some(func) = &self.on_rgba_func { unsafe { func( - self.ptr as _, + *ptr as _, rgba.raw.as_ptr() as _, rgba.raw.len() as _, rgba.w as _, @@ -328,7 +329,7 @@ impl FlutterHandler { #[inline] #[cfg(feature = "flutter_texture_render")] pub fn register_texture(&mut self, ptr: usize) { - self.renderer.write().unwrap().ptr = ptr; + *self.renderer.read().unwrap().ptr.write().unwrap() = ptr; } #[inline] @@ -789,11 +790,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) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 20ebd55a9..e4ce7da46 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -15,7 +15,7 @@ use flutter_rust_bridge::{StreamSink, SyncReturn}; use hbb_common::allow_err; use hbb_common::{ config::{self, LocalConfig, PeerConfig, PeerInfoSerde}, - fs, log, + fs, lazy_static, log, message_proto::KeyboardMode, ResultType, }; @@ -24,11 +24,19 @@ use std::{ ffi::{CStr, CString}, os::raw::c_char, str::FromStr, + sync::{ + atomic::{AtomicI32, Ordering}, + Arc, + }, time::SystemTime, }; pub type SessionID = uuid::Uuid; +lazy_static::lazy_static! { + static ref TEXTURE_RENDER_KEY: Arc = Arc::new(AtomicI32::new(0)); +} + fn initialize(app_dir: &str) { *config::APP_DIR.write().unwrap() = app_dir.to_owned(); #[cfg(target_os = "android")] @@ -197,6 +205,30 @@ pub fn session_set_flutter_config(session_id: SessionID, k: String, v: String) { } } +pub fn session_get_flutter_config_by_peer_id(id: String, k: String) -> Option { + if let Some((_, session)) = SESSIONS.read().unwrap().iter().find(|(_, s)| s.id == id) { + Some(session.get_flutter_config(k)) + } else { + None + } +} + +pub fn session_set_flutter_config_by_peer_id(id: String, k: String, v: String) { + if let Some((_, session)) = SESSIONS + .write() + .unwrap() + .iter_mut() + .find(|(_, s)| s.id == id) + { + session.save_flutter_config(k, v); + } +} + +pub fn get_next_texture_key() -> SyncReturn { + let k = TEXTURE_RENDER_KEY.fetch_add(1, Ordering::SeqCst) + 1; + SyncReturn(k) +} + pub fn get_local_flutter_config(k: String) -> SyncReturn { SyncReturn(ui_interface::get_local_flutter_config(k)) } @@ -569,6 +601,14 @@ pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32 } } +pub fn session_ready_to_new_window(session_id: SessionID) { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) { + session.restore_flutter_cache(); + session.refresh_video(); + } +} + pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) { #[cfg(feature = "flutter_texture_render")] if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) { diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 51a731c35..326ef5dd0 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index b4a76c62e..f52cb9b61 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "管理的设备数已达到最大值"), ("Sync with recent sessions", "同步最近会话"), ("Sort tags", "对标签进行排序"), + ("Separate remote window", "使用独立远程窗口"), + ("separate window", "独立窗口"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 29898eaf7..a61c46001 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 0a4adaf36..c3f43b7f1 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index c42ac5719..e59eb63d6 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Sie haben die maximale Anzahl der verwalteten Geräte erreicht."), ("Sync with recent sessions", "Synchronisierung mit den letzten Sitzungen"), ("Sort tags", "Tags sortieren"), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index eb91e9a83..83160cae7 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index a576538ef..47792961a 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index c39bcb7f5..b57474793 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Has alcanzado el máximo número de dispositivos administrados."), ("Sync with recent sessions", "Sincronizar con sesiones recientes"), ("Sort tags", "Ordenar etiquetas"), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 112edf32a..6f2531b46 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 57c548e08..679620a99 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 638ed3adf..8eacb75d3 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index ac9df01b3..2d12c31e5 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index c89e6cf72..5118adc16 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Hai raggiunto il numero massimo di dispositivi gestibili."), ("Sync with recent sessions", "Sincronizza con le sessioni recenti"), ("Sort tags", "Ordina etichette"), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 67234039d..ed09d6132 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index c0b71c205..d48347023 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 829c59c73..1bcadec64 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 69936b2b4..accdf9a39 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index c470f2270..b1bcd1d2a 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Het maximum aantal gecontroleerde apparaten is bereikt."), ("Sync with recent sessions", "Recente sessies synchroniseren"), ("Sort tags", "Labels sorteren"), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index af6d11659..1d813ef41 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 52675daf7..6b32a1e48 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 195b79804..c732dd2af 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 378d7c035..4db68ee50 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 6b48ef20b..9dcef1a48 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Достигнуто максимальне количество управляемых устройств."), ("Sync with recent sessions", "Синхронизация последних сессий"), ("Sort tags", "Сортировка меток"), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index f24f55bef..3f8d57169 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index ff5c254e0..e3c17a659 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 797328139..46db68578 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 3bb40f4ce..370865e0a 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index ac898e38f..3ec8535ef 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 5c710624f..7eb168fdf 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 2b2d0f4b2..f125384de 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index d7bd99f25..b4959b158 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index bef733f00..cda76a154 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 3b115a5f3..8d46b61ae 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 59490da1d..c188e2d14 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), + ("Separate remote window", ""), + ("separate window", ""), ].iter().cloned().collect(); } diff --git a/src/ui.rs b/src/ui.rs index 8f036509b..309d0aa6b 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -94,8 +94,7 @@ pub fn start(args: &mut [String]) { args[1] = id; } if args.is_empty() { - let children: Children = Default::default(); - std::thread::spawn(move || check_zombie(children)); + std::thread::spawn(move || check_zombie()); crate::common::check_software_update(); frame.event_handler(UI {}); frame.sciter_handler(UIHostHandler {}); @@ -693,28 +692,6 @@ impl sciter::host::HostHandler for UIHostHandler { } } -pub fn check_zombie(children: Children) { - let mut deads = Vec::new(); - loop { - let mut lock = children.lock().unwrap(); - let mut n = 0; - for (id, c) in lock.1.iter_mut() { - if let Ok(Some(_)) = c.try_wait() { - deads.push(id.clone()); - n += 1; - } - } - for ref id in deads.drain(..) { - lock.1.remove(id); - } - if n > 0 { - lock.0 = true; - } - drop(lock); - std::thread::sleep(std::time::Duration::from_millis(100)); - } -} - #[cfg(not(target_os = "linux"))] fn get_sound_inputs() -> Vec { let mut out = Vec::new(); @@ -748,50 +725,6 @@ pub fn value_crash_workaround(values: &[Value]) -> Arc> { persist } -#[inline] -pub fn new_remote(id: String, remote_type: String, force_relay: bool) { - let mut lock = CHILDREN.lock().unwrap(); - let mut args = vec![format!("--{}", remote_type), id.clone()]; - if force_relay { - args.push("".to_string()); // password - args.push("--relay".to_string()); - } - let key = (id.clone(), remote_type.clone()); - if let Some(c) = lock.1.get_mut(&key) { - if let Ok(Some(_)) = c.try_wait() { - lock.1.remove(&key); - } else { - if remote_type == "rdp" { - allow_err!(c.kill()); - std::thread::sleep(std::time::Duration::from_millis(30)); - c.try_wait().ok(); - lock.1.remove(&key); - } else { - return; - } - } - } - match crate::run_me(args) { - Ok(child) => { - lock.1.insert(key, child); - } - Err(err) => { - log::error!("Failed to spawn remote: {}", err); - } - } -} - -#[inline] -pub fn recent_sessions_updated() -> bool { - let mut children = CHILDREN.lock().unwrap(); - if children.0 { - children.0 = false; - true - } else { - false - } -} - pub fn get_icon() -> String { // 128x128 #[cfg(target_os = "macos")] diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 512589b6e..bcc2ab54f 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -65,6 +65,7 @@ lazy_static::lazy_static! { static ref OPTION_SYNCED: Arc> = Default::default(); static ref OPTIONS : Arc>> = Arc::new(Mutex::new(Config::get_options())); pub static ref SENDER : Mutex> = Mutex::new(check_connect_status(true)); + static ref CHILDREN : Children = Default::default(); } const INIT_ASYNC_JOB_STATUS: &str = " "; @@ -827,11 +828,11 @@ pub fn check_super_user_permission() -> bool { return true; } -#[allow(dead_code)] -pub fn check_zombie(children: Children) { +#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))] +pub fn check_zombie() { let mut deads = Vec::new(); loop { - let mut lock = children.lock().unwrap(); + let mut lock = CHILDREN.lock().unwrap(); let mut n = 0; for (id, c) in lock.1.iter_mut() { if let Ok(Some(_)) = c.try_wait() { @@ -850,6 +851,51 @@ pub fn check_zombie(children: Children) { } } +#[inline] +#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))] +pub fn recent_sessions_updated() -> bool { + let mut children = CHILDREN.lock().unwrap(); + if children.0 { + children.0 = false; + true + } else { + false + } +} + +#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))] +pub fn new_remote(id: String, remote_type: String, force_relay: bool) { + let mut lock = CHILDREN.lock().unwrap(); + let mut args = vec![format!("--{}", remote_type), id.clone()]; + if force_relay { + args.push("".to_string()); // password + args.push("--relay".to_string()); + } + let key = (id.clone(), remote_type.clone()); + if let Some(c) = lock.1.get_mut(&key) { + if let Ok(Some(_)) = c.try_wait() { + lock.1.remove(&key); + } else { + if remote_type == "rdp" { + allow_err!(c.kill()); + std::thread::sleep(std::time::Duration::from_millis(30)); + c.try_wait().ok(); + lock.1.remove(&key); + } else { + return; + } + } + } + match crate::run_me(args) { + Ok(child) => { + lock.1.insert(key, child); + } + Err(err) => { + log::error!("Failed to spawn remote: {}", err); + } + } +} + // Make sure `SENDER` is inited here. #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 33cfa2ced..21b9c9d9e 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -48,6 +48,16 @@ pub static IS_IN: AtomicBool = AtomicBool::new(false); const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15; +#[cfg(feature = "flutter")] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +#[derive(Default)] +pub struct CacheFlutter { + pub pi: PeerInfo, + pub sp: Option, + pub cursor_data: HashMap, + pub cursor_id: u64, +} + #[derive(Clone, Default)] pub struct Session { pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass @@ -62,6 +72,9 @@ pub struct Session { pub server_file_transfer_enabled: Arc>, pub server_clipboard_enabled: Arc>, pub last_change_display: Arc>, + #[cfg(feature = "flutter")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + pub cache_flutter: Arc>, } #[derive(Clone)] @@ -1181,12 +1194,26 @@ impl Session { pub fn ctrl_alt_del(&self) { self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del()); } + + #[cfg(feature = "flutter")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + pub fn restore_flutter_cache(&mut self) { + let pi = self.cache_flutter.read().unwrap().pi.clone(); + self.handle_peer_info(pi); + if let Some(sp) = self.cache_flutter.read().unwrap().sp.as_ref() { + self.handle_peer_switch_display(sp); + } + for (_, cd) in self.cache_flutter.read().unwrap().cursor_data.iter() { + self.set_cursor_data(cd.clone()); + } + self.set_cursor_id(self.cache_flutter.read().unwrap().cursor_id.to_string()); + } } #[tokio::main(flavor = "current_thread")] pub async fn io_loop(handler: Session) { // It is ok to call this function multiple times. - #[cfg(target_os ="windows")] + #[cfg(target_os = "windows")] if !handler.is_file_transfer() && !handler.is_port_forward() { clipboard::ContextSend::enable(true); }