import 'dart:convert'; import 'dart:io'; 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 enum WindowType { Main, RemoteDesktop, FileTransfer, PortForward, Unknown } extension Index on int { WindowType get windowType { switch (this) { case 0: return WindowType.Main; case 1: return WindowType.RemoteDesktop; case 2: return WindowType.FileTransfer; case 3: return WindowType.PortForward; default: return WindowType.Unknown; } } } /// Window Manager /// mainly use it in `Main Window` /// use it in sub window is not recommended class RustDeskMultiWindowManager { RustDeskMultiWindowManager._(); static final instance = RustDeskMultiWindowManager._(); final Set _inactiveWindows = {}; final Set _activeWindows = {}; final List _windowActiveCallbacks = List.empty(growable: true); final Map> _remoteDesktopWindows = {}; final Map> _fileTransferWindows = {}; final Map> _portForwardWindows = {}; Future newSession( WindowType type, String methodName, String remoteId, Map> windows, { String? password, bool? forceRelay, String? switchUuid, bool? isRDP, }) async { var params = { "type": type.index, "id": remoteId, "password": password, "forceRelay": forceRelay }; if (switchUuid != null) { params['switch_uuid'] = switchUuid; } if (isRDP != null) { params['isRDP'] = isRDP; } final msg = jsonEncode(params); newSessionWindow() async { final windowController = await DesktopMultiWindow.createWindow(msg); windowController ..setFrame(const Offset(0, 0) & const Size(1280, 720)) ..center() ..setTitle(getWindowNameWithId( remoteId, overrideType: type, )); if (Platform.isMacOS) { Future.microtask(() => windowController.show()); } registerActiveWindow(windowController.windowId); windows[windowController.windowId] = {remoteId}; } // separate window for file transfer is not supported bool separateWindow = type != WindowType.FileTransfer && mainGetBoolOptionSync(kOptionSeparateRemoteWindow); if (separateWindow) { for (final item in windows.entries) { if (_activeWindows.contains(item.key) && item.value.contains(remoteId)) { // already has a window for this remote final windowController = WindowController.fromWindowId(item.key); windowController.show(); // to-do: macos? // if (Platform.isMacOS) { // Future.microtask(() => windowController.show()); // } return; } } 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[windowController.windowId] = {remoteId}; return invokeRes; } else { await newSessionWindow(); } } else { if (windows.isEmpty) { await newSessionWindow(); } else { return call(type, methodName, msg); } } } Future newRemoteDesktop( String remoteId, { String? password, String? switchUuid, bool? forceRelay, }) async { return await newSession( WindowType.RemoteDesktop, 'new_remote_desktop', remoteId, _remoteDesktopWindows, password: password, forceRelay: forceRelay, switchUuid: switchUuid, ); } Future newFileTransfer(String remoteId, {String? password, bool? forceRelay}) async { return await newSession( WindowType.FileTransfer, 'new_file_transfer', remoteId, _fileTransferWindows, password: password, forceRelay: forceRelay, ); } Future newPortForward(String remoteId, bool isRDP, {String? password, bool? forceRelay}) async { return await newSession( WindowType.PortForward, 'new_port_forward', remoteId, _portForwardWindows, password: password, forceRelay: forceRelay, isRDP: isRDP, ); } Future call(WindowType type, String methodName, dynamic args) async { final wnds = _findWindowsByType(type); if (wnds.isEmpty) { return; } return await DesktopMultiWindow.invokeMethod( wnds.keys.toList()[0], methodName, args); } Map> _findWindowsByType(WindowType type) { switch (type) { case WindowType.Main: return { 0: {''} }; case WindowType.RemoteDesktop: return _remoteDesktopWindows; case WindowType.FileTransfer: return _fileTransferWindows; case WindowType.PortForward: return _portForwardWindows; case WindowType.Unknown: break; } return {}; } void clearWindowType(WindowType type) { switch (type) { case WindowType.Main: return; case WindowType.RemoteDesktop: _remoteDesktopWindows.clear(); break; case WindowType.FileTransfer: _fileTransferWindows.clear(); break; case WindowType.PortForward: _portForwardWindows.clear(); break; case WindowType.Unknown: break; } } void setMethodHandler( Future Function(MethodCall call, int fromWindowId)? handler) { DesktopMultiWindow.setMethodHandler(handler); } Future closeAllSubWindows() async { await Future.wait(WindowType.values.map((e) => closeWindows(e))); } Future closeWindows(WindowType type) async { if (type == WindowType.Main) { // skip main window, use window manager instead return; } 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; // } await WindowController.fromWindowId(wId).setPreventClose(false); await WindowController.fromWindowId(wId).close(); _activeWindows.remove(wId); } catch (e) { debugPrint("$e"); return; } } await _notifyActiveWindow(); clearWindowType(type); } Future> getAllSubWindowIds() async { try { final windows = await DesktopMultiWindow.getAllSubWindowIds(); return windows; } catch (err) { if (err is AssertionError) { return []; } else { rethrow; } } } Set getActiveWindows() { return _activeWindows; } Future _notifyActiveWindow() async { for (final callback in _windowActiveCallbacks) { await callback.call(); } } Future registerActiveWindow(int windowId) async { _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] /// This function should only be called from main window. /// 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 { _activeWindows.remove(windowId); if (windowId != kMainWindowId) { _inactiveWindows.add(windowId); } await _notifyActiveWindow(); } void registerActiveWindowListener(AsyncCallback callback) { _windowActiveCallbacks.add(callback); } void unregisterActiveWindowListener(AsyncCallback callback) { _windowActiveCallbacks.remove(callback); } } final rustDeskWinManager = RustDeskMultiWindowManager.instance;