123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- 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,
- String? connToken,
- }) 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;
- }
- if (connToken != null) {
- params['connToken'] = connToken;
- }
- 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,
- String? connToken,
- }) async {
- return await newSession(
- WindowType.FileTransfer,
- kWindowEventNewFileTransfer,
- remoteId,
- _fileTransferWindows,
- password: password,
- forceRelay: forceRelay,
- isSharedPassword: isSharedPassword,
- connToken: connToken,
- );
- }
- Future<MultiWindowCallResult> newPortForward(
- String remoteId,
- bool isRDP, {
- String? password,
- bool? isSharedPassword,
- bool? forceRelay,
- String? connToken,
- }) async {
- return await newSession(
- WindowType.PortForward,
- kWindowEventNewPortForward,
- remoteId,
- _portForwardWindows,
- password: password,
- forceRelay: forceRelay,
- isRDP: isRDP,
- isSharedPassword: isSharedPassword,
- connToken: connToken,
- );
- }
- 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;
|