463 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			463 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:convert';
 | |
| 
 | |
| 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';
 | |
| import 'package:flutter_hbb/main.dart';
 | |
| import 'package:flutter_hbb/models/input_model.dart';
 | |
| 
 | |
| /// must keep the order
 | |
| // ignore: constant_identifier_names
 | |
| 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;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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
 | |
| class RustDeskMultiWindowManager {
 | |
|   RustDeskMultiWindowManager._();
 | |
| 
 | |
|   static final instance = RustDeskMultiWindowManager._();
 | |
| 
 | |
|   final Set<int> _inactiveWindows = {};
 | |
|   final Set<int> _activeWindows = {};
 | |
|   final List<AsyncCallback> _windowActiveCallbacks = List.empty(growable: true);
 | |
|   final List<int> _remoteDesktopWindows = List.empty(growable: true);
 | |
|   final List<int> _fileTransferWindows = List.empty(growable: true);
 | |
|   final List<int> _portForwardWindows = List.empty(growable: true);
 | |
| 
 | |
|   moveTabToNewWindow(int windowId, String peerId, String sessionId) async {
 | |
|     var params = {
 | |
|       'type': WindowType.RemoteDesktop.index,
 | |
|       'id': peerId,
 | |
|       'tab_window_id': windowId,
 | |
|       'session_id': sessionId,
 | |
|     };
 | |
|     await _newSession(
 | |
|       false,
 | |
|       WindowType.RemoteDesktop,
 | |
|       kWindowEventNewRemoteDesktop,
 | |
|       peerId,
 | |
|       _remoteDesktopWindows,
 | |
|       jsonEncode(params),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   // This function must be called in the main window thread.
 | |
|   // Because the _remoteDesktopWindows is managed in that thread.
 | |
|   openMonitorSession(int windowId, String peerId, int display, int displayCount,
 | |
|       Rect? screenRect) async {
 | |
|     if (_remoteDesktopWindows.length > 1) {
 | |
|       for (final windowId in _remoteDesktopWindows) {
 | |
|         if (await DesktopMultiWindow.invokeMethod(
 | |
|             windowId,
 | |
|             kWindowEventActiveDisplaySession,
 | |
|             jsonEncode({
 | |
|               'id': peerId,
 | |
|               'display': display,
 | |
|             }))) {
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     final displays = display == kAllDisplayValue
 | |
|         ? List.generate(displayCount, (index) => index)
 | |
|         : [display];
 | |
|     var params = {
 | |
|       'type': WindowType.RemoteDesktop.index,
 | |
|       'id': peerId,
 | |
|       'tab_window_id': windowId,
 | |
|       'display': display,
 | |
|       'displays': displays,
 | |
|     };
 | |
|     if (screenRect != null) {
 | |
|       params['screen_rect'] = {
 | |
|         'l': screenRect.left,
 | |
|         't': screenRect.top,
 | |
|         'r': screenRect.right,
 | |
|         'b': screenRect.bottom,
 | |
|       };
 | |
|     }
 | |
|     await _newSession(
 | |
|       false,
 | |
|       WindowType.RemoteDesktop,
 | |
|       kWindowEventNewRemoteDesktop,
 | |
|       peerId,
 | |
|       _remoteDesktopWindows,
 | |
|       jsonEncode(params),
 | |
|       screenRect: screenRect,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Future<int> newSessionWindow(
 | |
|     WindowType type,
 | |
|     String remoteId,
 | |
|     String msg,
 | |
|     List<int> windows,
 | |
|     bool withScreenRect,
 | |
|   ) async {
 | |
|     final windowController = await DesktopMultiWindow.createWindow(msg);
 | |
|     if (isWindows) {
 | |
|       windowController.setInitBackgroundColor(Colors.black);
 | |
|     }
 | |
|     final windowId = windowController.windowId;
 | |
|     if (!withScreenRect) {
 | |
|       windowController
 | |
|         ..setFrame(const Offset(0, 0) &
 | |
|             Size(1280 + windowId * 20, 720 + windowId * 20))
 | |
|         ..center()
 | |
|         ..setTitle(getWindowNameWithId(
 | |
|           remoteId,
 | |
|           overrideType: type,
 | |
|         ));
 | |
|     } else {
 | |
|       windowController.setTitle(getWindowNameWithId(
 | |
|         remoteId,
 | |
|         overrideType: type,
 | |
|       ));
 | |
|     }
 | |
|     if (isMacOS) {
 | |
|       Future.microtask(() => windowController.show());
 | |
|     }
 | |
|     registerActiveWindow(windowId);
 | |
|     windows.add(windowId);
 | |
|     return windowId;
 | |
|   }
 | |
| 
 | |
|   Future<MultiWindowCallResult> _newSession(
 | |
|     bool openInTabs,
 | |
|     WindowType type,
 | |
|     String methodName,
 | |
|     String remoteId,
 | |
|     List<int> windows,
 | |
|     String msg, {
 | |
|     Rect? screenRect,
 | |
|   }) async {
 | |
|     if (openInTabs) {
 | |
|       if (windows.isEmpty) {
 | |
|         final windowId = await newSessionWindow(
 | |
|             type, remoteId, msg, windows, screenRect != null);
 | |
|         return MultiWindowCallResult(windowId, null);
 | |
|       } else {
 | |
|         return call(type, methodName, msg);
 | |
|       }
 | |
|     } else {
 | |
|       if (_inactiveWindows.isNotEmpty) {
 | |
|         for (final windowId in windows) {
 | |
|           if (_inactiveWindows.contains(windowId)) {
 | |
|             if (screenRect == null) {
 | |
|               await restoreWindowPosition(type,
 | |
|                   windowId: windowId, peerId: remoteId);
 | |
|             }
 | |
|             await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
 | |
|             if (methodName != kWindowEventNewRemoteDesktop) {
 | |
|               WindowController.fromWindowId(windowId).show();
 | |
|             }
 | |
|             registerActiveWindow(windowId);
 | |
|             return MultiWindowCallResult(windowId, null);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       final windowId = await newSessionWindow(
 | |
|           type, remoteId, msg, windows, screenRect != null);
 | |
|       return MultiWindowCallResult(windowId, null);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Future<MultiWindowCallResult> newSession(
 | |
|     WindowType type,
 | |
|     String methodName,
 | |
|     String remoteId,
 | |
|     List<int> windows, {
 | |
|     String? password,
 | |
|     bool? forceRelay,
 | |
|     String? switchUuid,
 | |
|     bool? isRDP,
 | |
|     bool? isSharedPassword,
 | |
|   }) 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;
 | |
|     }
 | |
|     if (isSharedPassword != null) {
 | |
|       params['isSharedPassword'] = isSharedPassword;
 | |
|     }
 | |
|     final msg = jsonEncode(params);
 | |
| 
 | |
|     // separate window for file transfer is not supported
 | |
|     bool openInTabs = type != WindowType.RemoteDesktop ||
 | |
|         mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
 | |
| 
 | |
|     if (windows.length > 1 || !openInTabs) {
 | |
|       for (final windowId in windows) {
 | |
|         if (await DesktopMultiWindow.invokeMethod(
 | |
|             windowId, kWindowEventActiveSession, remoteId)) {
 | |
|           return MultiWindowCallResult(windowId, null);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return _newSession(openInTabs, type, methodName, remoteId, windows, msg);
 | |
|   }
 | |
| 
 | |
|   Future<MultiWindowCallResult> newRemoteDesktop(
 | |
|     String remoteId, {
 | |
|     String? password,
 | |
|     bool? isSharedPassword,
 | |
|     String? switchUuid,
 | |
|     bool? forceRelay,
 | |
|   }) async {
 | |
|     return await newSession(
 | |
|       WindowType.RemoteDesktop,
 | |
|       kWindowEventNewRemoteDesktop,
 | |
|       remoteId,
 | |
|       _remoteDesktopWindows,
 | |
|       password: password,
 | |
|       forceRelay: forceRelay,
 | |
|       switchUuid: switchUuid,
 | |
|       isSharedPassword: isSharedPassword,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Future<MultiWindowCallResult> newFileTransfer(String remoteId,
 | |
|       {String? password, bool? isSharedPassword, bool? forceRelay}) async {
 | |
|     return await newSession(
 | |
|       WindowType.FileTransfer,
 | |
|       kWindowEventNewFileTransfer,
 | |
|       remoteId,
 | |
|       _fileTransferWindows,
 | |
|       password: password,
 | |
|       forceRelay: forceRelay,
 | |
|       isSharedPassword: isSharedPassword,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Future<MultiWindowCallResult> newPortForward(String remoteId, bool isRDP,
 | |
|       {String? password, bool? isSharedPassword, bool? forceRelay}) async {
 | |
|     return await newSession(
 | |
|       WindowType.PortForward,
 | |
|       kWindowEventNewPortForward,
 | |
|       remoteId,
 | |
|       _portForwardWindows,
 | |
|       password: password,
 | |
|       forceRelay: forceRelay,
 | |
|       isRDP: isRDP,
 | |
|       isSharedPassword: isSharedPassword,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Future<MultiWindowCallResult> call(
 | |
|       WindowType type, String methodName, dynamic args) async {
 | |
|     final wnds = _findWindowsByType(type);
 | |
|     if (wnds.isEmpty) {
 | |
|       return MultiWindowCallResult(kInvalidWindowId, null);
 | |
|     }
 | |
|     for (final windowId in wnds) {
 | |
|       if (_activeWindows.contains(windowId)) {
 | |
|         final res =
 | |
|             await DesktopMultiWindow.invokeMethod(windowId, methodName, args);
 | |
|         return MultiWindowCallResult(windowId, res);
 | |
|       }
 | |
|     }
 | |
|     final res =
 | |
|         await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
 | |
|     return MultiWindowCallResult(wnds[0], res);
 | |
|   }
 | |
| 
 | |
|   List<int> _findWindowsByType(WindowType type) {
 | |
|     switch (type) {
 | |
|       case WindowType.Main:
 | |
|         return [kMainWindowId];
 | |
|       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<dynamic> Function(MethodCall call, int fromWindowId)? handler) {
 | |
|     DesktopMultiWindow.setMethodHandler(handler);
 | |
|   }
 | |
| 
 | |
|   Future<void> closeAllSubWindows() async {
 | |
|     await Future.wait(WindowType.values.map((e) => _closeWindows(e)));
 | |
|   }
 | |
| 
 | |
|   Future<void> _closeWindows(WindowType type) async {
 | |
|     if (type == WindowType.Main) {
 | |
|       // skip main window, use window manager instead
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     List<int> windows = [];
 | |
|     try {
 | |
|       windows = _findWindowsByType(type);
 | |
|     } catch (e) {
 | |
|       debugPrint('Failed to getAllSubWindowIds of $type, $e');
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (windows.isEmpty) {
 | |
|       return;
 | |
|     }
 | |
|     for (final wId in windows) {
 | |
|       debugPrint("closing multi window, type: ${type.toString()} id: $wId");
 | |
|       await saveWindowPosition(type, windowId: wId);
 | |
|       try {
 | |
|         await WindowController.fromWindowId(wId).setPreventClose(false);
 | |
|         await WindowController.fromWindowId(wId).close();
 | |
|         _activeWindows.remove(wId);
 | |
|       } catch (e) {
 | |
|         debugPrint("$e");
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|     clearWindowType(type);
 | |
|   }
 | |
| 
 | |
|   Future<List<int>> getAllSubWindowIds() async {
 | |
|     try {
 | |
|       final windows = await DesktopMultiWindow.getAllSubWindowIds();
 | |
|       return windows;
 | |
|     } catch (err) {
 | |
|       if (err is AssertionError) {
 | |
|         return [];
 | |
|       } else {
 | |
|         rethrow;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Set<int> getActiveWindows() {
 | |
|     return _activeWindows;
 | |
|   }
 | |
| 
 | |
|   Future<void> _notifyActiveWindow() async {
 | |
|     for (final callback in _windowActiveCallbacks) {
 | |
|       await callback.call();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Future<void> registerActiveWindow(int windowId) async {
 | |
|     _activeWindows.add(windowId);
 | |
|     _inactiveWindows.remove(windowId);
 | |
|     await _notifyActiveWindow();
 | |
|   }
 | |
| 
 | |
|   /// 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<void> 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);
 | |
|   }
 | |
| 
 | |
|   // This function is called from the main window.
 | |
|   // It will query the active remote windows to get their coords.
 | |
|   Future<List<String>> getOtherRemoteWindowCoords(int wId) async {
 | |
|     List<String> coords = [];
 | |
|     for (final windowId in _remoteDesktopWindows) {
 | |
|       if (windowId != wId) {
 | |
|         if (_activeWindows.contains(windowId)) {
 | |
|           final res = await DesktopMultiWindow.invokeMethod(
 | |
|               windowId, kWindowEventRemoteWindowCoords, '');
 | |
|           if (res != null) {
 | |
|             coords.add(res);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return coords;
 | |
|   }
 | |
| 
 | |
|   // This function is called from one remote window.
 | |
|   // Only the main window knows `_remoteDesktopWindows` and `_activeWindows`.
 | |
|   // So we need to call the main window to get the other remote windows' coords.
 | |
|   Future<List<RemoteWindowCoords>> getOtherRemoteWindowCoordsFromMain() async {
 | |
|     List<RemoteWindowCoords> coords = [];
 | |
|     // Call the main window to get the coords of other remote windows.
 | |
|     String res = await DesktopMultiWindow.invokeMethod(
 | |
|         kMainWindowId, kWindowEventRemoteWindowCoords, kWindowId.toString());
 | |
|     List<dynamic> list = jsonDecode(res);
 | |
|     for (var item in list) {
 | |
|       coords.add(RemoteWindowCoords.fromJson(jsonDecode(item)));
 | |
|     }
 | |
|     return coords;
 | |
|   }
 | |
| }
 | |
| 
 | |
| final rustDeskWinManager = RustDeskMultiWindowManager.instance;
 |