multi_window_manager.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import 'dart:convert';
  2. import 'package:desktop_multi_window/desktop_multi_window.dart';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/services.dart';
  6. import 'package:flutter_hbb/consts.dart';
  7. import 'package:flutter_hbb/common.dart';
  8. import 'package:flutter_hbb/main.dart';
  9. import 'package:flutter_hbb/models/input_model.dart';
  10. /// must keep the order
  11. // ignore: constant_identifier_names
  12. enum WindowType { Main, RemoteDesktop, FileTransfer, PortForward, Unknown }
  13. extension Index on int {
  14. WindowType get windowType {
  15. switch (this) {
  16. case 0:
  17. return WindowType.Main;
  18. case 1:
  19. return WindowType.RemoteDesktop;
  20. case 2:
  21. return WindowType.FileTransfer;
  22. case 3:
  23. return WindowType.PortForward;
  24. default:
  25. return WindowType.Unknown;
  26. }
  27. }
  28. }
  29. class MultiWindowCallResult {
  30. int windowId;
  31. dynamic result;
  32. MultiWindowCallResult(this.windowId, this.result);
  33. }
  34. /// Window Manager
  35. /// mainly use it in `Main Window`
  36. /// use it in sub window is not recommended
  37. class RustDeskMultiWindowManager {
  38. RustDeskMultiWindowManager._();
  39. static final instance = RustDeskMultiWindowManager._();
  40. final Set<int> _inactiveWindows = {};
  41. final Set<int> _activeWindows = {};
  42. final List<AsyncCallback> _windowActiveCallbacks = List.empty(growable: true);
  43. final List<int> _remoteDesktopWindows = List.empty(growable: true);
  44. final List<int> _fileTransferWindows = List.empty(growable: true);
  45. final List<int> _portForwardWindows = List.empty(growable: true);
  46. moveTabToNewWindow(int windowId, String peerId, String sessionId) async {
  47. var params = {
  48. 'type': WindowType.RemoteDesktop.index,
  49. 'id': peerId,
  50. 'tab_window_id': windowId,
  51. 'session_id': sessionId,
  52. };
  53. await _newSession(
  54. false,
  55. WindowType.RemoteDesktop,
  56. kWindowEventNewRemoteDesktop,
  57. peerId,
  58. _remoteDesktopWindows,
  59. jsonEncode(params),
  60. );
  61. }
  62. // This function must be called in the main window thread.
  63. // Because the _remoteDesktopWindows is managed in that thread.
  64. openMonitorSession(int windowId, String peerId, int display, int displayCount,
  65. Rect? screenRect) async {
  66. if (_remoteDesktopWindows.length > 1) {
  67. for (final windowId in _remoteDesktopWindows) {
  68. if (await DesktopMultiWindow.invokeMethod(
  69. windowId,
  70. kWindowEventActiveDisplaySession,
  71. jsonEncode({
  72. 'id': peerId,
  73. 'display': display,
  74. }))) {
  75. return;
  76. }
  77. }
  78. }
  79. final displays = display == kAllDisplayValue
  80. ? List.generate(displayCount, (index) => index)
  81. : [display];
  82. var params = {
  83. 'type': WindowType.RemoteDesktop.index,
  84. 'id': peerId,
  85. 'tab_window_id': windowId,
  86. 'display': display,
  87. 'displays': displays,
  88. };
  89. if (screenRect != null) {
  90. params['screen_rect'] = {
  91. 'l': screenRect.left,
  92. 't': screenRect.top,
  93. 'r': screenRect.right,
  94. 'b': screenRect.bottom,
  95. };
  96. }
  97. await _newSession(
  98. false,
  99. WindowType.RemoteDesktop,
  100. kWindowEventNewRemoteDesktop,
  101. peerId,
  102. _remoteDesktopWindows,
  103. jsonEncode(params),
  104. screenRect: screenRect,
  105. );
  106. }
  107. Future<int> newSessionWindow(
  108. WindowType type,
  109. String remoteId,
  110. String msg,
  111. List<int> windows,
  112. bool withScreenRect,
  113. ) async {
  114. final windowController = await DesktopMultiWindow.createWindow(msg);
  115. if (isWindows) {
  116. windowController.setInitBackgroundColor(Colors.black);
  117. }
  118. final windowId = windowController.windowId;
  119. if (!withScreenRect) {
  120. windowController
  121. ..setFrame(const Offset(0, 0) &
  122. Size(1280 + windowId * 20, 720 + windowId * 20))
  123. ..center()
  124. ..setTitle(getWindowNameWithId(
  125. remoteId,
  126. overrideType: type,
  127. ));
  128. } else {
  129. windowController.setTitle(getWindowNameWithId(
  130. remoteId,
  131. overrideType: type,
  132. ));
  133. }
  134. if (isMacOS) {
  135. Future.microtask(() => windowController.show());
  136. }
  137. registerActiveWindow(windowId);
  138. windows.add(windowId);
  139. return windowId;
  140. }
  141. Future<MultiWindowCallResult> _newSession(
  142. bool openInTabs,
  143. WindowType type,
  144. String methodName,
  145. String remoteId,
  146. List<int> windows,
  147. String msg, {
  148. Rect? screenRect,
  149. }) async {
  150. if (openInTabs) {
  151. if (windows.isEmpty) {
  152. final windowId = await newSessionWindow(
  153. type, remoteId, msg, windows, screenRect != null);
  154. return MultiWindowCallResult(windowId, null);
  155. } else {
  156. return call(type, methodName, msg);
  157. }
  158. } else {
  159. if (_inactiveWindows.isNotEmpty) {
  160. for (final windowId in windows) {
  161. if (_inactiveWindows.contains(windowId)) {
  162. if (screenRect == null) {
  163. await restoreWindowPosition(type,
  164. windowId: windowId, peerId: remoteId);
  165. }
  166. await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
  167. if (methodName != kWindowEventNewRemoteDesktop) {
  168. WindowController.fromWindowId(windowId).show();
  169. }
  170. registerActiveWindow(windowId);
  171. return MultiWindowCallResult(windowId, null);
  172. }
  173. }
  174. }
  175. final windowId = await newSessionWindow(
  176. type, remoteId, msg, windows, screenRect != null);
  177. return MultiWindowCallResult(windowId, null);
  178. }
  179. }
  180. Future<MultiWindowCallResult> newSession(
  181. WindowType type,
  182. String methodName,
  183. String remoteId,
  184. List<int> windows, {
  185. String? password,
  186. bool? forceRelay,
  187. String? switchUuid,
  188. bool? isRDP,
  189. bool? isSharedPassword,
  190. String? connToken,
  191. }) async {
  192. var params = {
  193. "type": type.index,
  194. "id": remoteId,
  195. "password": password,
  196. "forceRelay": forceRelay
  197. };
  198. if (switchUuid != null) {
  199. params['switch_uuid'] = switchUuid;
  200. }
  201. if (isRDP != null) {
  202. params['isRDP'] = isRDP;
  203. }
  204. if (isSharedPassword != null) {
  205. params['isSharedPassword'] = isSharedPassword;
  206. }
  207. if (connToken != null) {
  208. params['connToken'] = connToken;
  209. }
  210. final msg = jsonEncode(params);
  211. // separate window for file transfer is not supported
  212. bool openInTabs = type != WindowType.RemoteDesktop ||
  213. mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
  214. if (windows.length > 1 || !openInTabs) {
  215. for (final windowId in windows) {
  216. if (await DesktopMultiWindow.invokeMethod(
  217. windowId, kWindowEventActiveSession, remoteId)) {
  218. return MultiWindowCallResult(windowId, null);
  219. }
  220. }
  221. }
  222. return _newSession(openInTabs, type, methodName, remoteId, windows, msg);
  223. }
  224. Future<MultiWindowCallResult> newRemoteDesktop(
  225. String remoteId, {
  226. String? password,
  227. bool? isSharedPassword,
  228. String? switchUuid,
  229. bool? forceRelay,
  230. }) async {
  231. return await newSession(
  232. WindowType.RemoteDesktop,
  233. kWindowEventNewRemoteDesktop,
  234. remoteId,
  235. _remoteDesktopWindows,
  236. password: password,
  237. forceRelay: forceRelay,
  238. switchUuid: switchUuid,
  239. isSharedPassword: isSharedPassword,
  240. );
  241. }
  242. Future<MultiWindowCallResult> newFileTransfer(
  243. String remoteId, {
  244. String? password,
  245. bool? isSharedPassword,
  246. bool? forceRelay,
  247. String? connToken,
  248. }) async {
  249. return await newSession(
  250. WindowType.FileTransfer,
  251. kWindowEventNewFileTransfer,
  252. remoteId,
  253. _fileTransferWindows,
  254. password: password,
  255. forceRelay: forceRelay,
  256. isSharedPassword: isSharedPassword,
  257. connToken: connToken,
  258. );
  259. }
  260. Future<MultiWindowCallResult> newPortForward(
  261. String remoteId,
  262. bool isRDP, {
  263. String? password,
  264. bool? isSharedPassword,
  265. bool? forceRelay,
  266. String? connToken,
  267. }) async {
  268. return await newSession(
  269. WindowType.PortForward,
  270. kWindowEventNewPortForward,
  271. remoteId,
  272. _portForwardWindows,
  273. password: password,
  274. forceRelay: forceRelay,
  275. isRDP: isRDP,
  276. isSharedPassword: isSharedPassword,
  277. connToken: connToken,
  278. );
  279. }
  280. Future<MultiWindowCallResult> call(
  281. WindowType type, String methodName, dynamic args) async {
  282. final wnds = _findWindowsByType(type);
  283. if (wnds.isEmpty) {
  284. return MultiWindowCallResult(kInvalidWindowId, null);
  285. }
  286. for (final windowId in wnds) {
  287. if (_activeWindows.contains(windowId)) {
  288. final res =
  289. await DesktopMultiWindow.invokeMethod(windowId, methodName, args);
  290. return MultiWindowCallResult(windowId, res);
  291. }
  292. }
  293. final res =
  294. await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
  295. return MultiWindowCallResult(wnds[0], res);
  296. }
  297. List<int> _findWindowsByType(WindowType type) {
  298. switch (type) {
  299. case WindowType.Main:
  300. return [kMainWindowId];
  301. case WindowType.RemoteDesktop:
  302. return _remoteDesktopWindows;
  303. case WindowType.FileTransfer:
  304. return _fileTransferWindows;
  305. case WindowType.PortForward:
  306. return _portForwardWindows;
  307. case WindowType.Unknown:
  308. break;
  309. }
  310. return [];
  311. }
  312. void clearWindowType(WindowType type) {
  313. switch (type) {
  314. case WindowType.Main:
  315. return;
  316. case WindowType.RemoteDesktop:
  317. _remoteDesktopWindows.clear();
  318. break;
  319. case WindowType.FileTransfer:
  320. _fileTransferWindows.clear();
  321. break;
  322. case WindowType.PortForward:
  323. _portForwardWindows.clear();
  324. break;
  325. case WindowType.Unknown:
  326. break;
  327. }
  328. }
  329. void setMethodHandler(
  330. Future<dynamic> Function(MethodCall call, int fromWindowId)? handler) {
  331. DesktopMultiWindow.setMethodHandler(handler);
  332. }
  333. Future<void> closeAllSubWindows() async {
  334. await Future.wait(WindowType.values.map((e) => _closeWindows(e)));
  335. }
  336. Future<void> _closeWindows(WindowType type) async {
  337. if (type == WindowType.Main) {
  338. // skip main window, use window manager instead
  339. return;
  340. }
  341. List<int> windows = [];
  342. try {
  343. windows = _findWindowsByType(type);
  344. } catch (e) {
  345. debugPrint('Failed to getAllSubWindowIds of $type, $e');
  346. return;
  347. }
  348. if (windows.isEmpty) {
  349. return;
  350. }
  351. for (final wId in windows) {
  352. debugPrint("closing multi window, type: ${type.toString()} id: $wId");
  353. await saveWindowPosition(type, windowId: wId);
  354. try {
  355. await WindowController.fromWindowId(wId).setPreventClose(false);
  356. await WindowController.fromWindowId(wId).close();
  357. _activeWindows.remove(wId);
  358. } catch (e) {
  359. debugPrint("$e");
  360. return;
  361. }
  362. }
  363. clearWindowType(type);
  364. }
  365. Future<List<int>> getAllSubWindowIds() async {
  366. try {
  367. final windows = await DesktopMultiWindow.getAllSubWindowIds();
  368. return windows;
  369. } catch (err) {
  370. if (err is AssertionError) {
  371. return [];
  372. } else {
  373. rethrow;
  374. }
  375. }
  376. }
  377. Set<int> getActiveWindows() {
  378. return _activeWindows;
  379. }
  380. Future<void> _notifyActiveWindow() async {
  381. for (final callback in _windowActiveCallbacks) {
  382. await callback.call();
  383. }
  384. }
  385. Future<void> registerActiveWindow(int windowId) async {
  386. _activeWindows.add(windowId);
  387. _inactiveWindows.remove(windowId);
  388. await _notifyActiveWindow();
  389. }
  390. /// Remove active window which has [`windowId`]
  391. ///
  392. /// [Availability]
  393. /// This function should only be called from main window.
  394. /// For other windows, please post a unregister(hide) event to main window handler:
  395. /// `rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId!});`
  396. Future<void> unregisterActiveWindow(int windowId) async {
  397. _activeWindows.remove(windowId);
  398. if (windowId != kMainWindowId) {
  399. _inactiveWindows.add(windowId);
  400. }
  401. await _notifyActiveWindow();
  402. }
  403. void registerActiveWindowListener(AsyncCallback callback) {
  404. _windowActiveCallbacks.add(callback);
  405. }
  406. void unregisterActiveWindowListener(AsyncCallback callback) {
  407. _windowActiveCallbacks.remove(callback);
  408. }
  409. // This function is called from the main window.
  410. // It will query the active remote windows to get their coords.
  411. Future<List<String>> getOtherRemoteWindowCoords(int wId) async {
  412. List<String> coords = [];
  413. for (final windowId in _remoteDesktopWindows) {
  414. if (windowId != wId) {
  415. if (_activeWindows.contains(windowId)) {
  416. final res = await DesktopMultiWindow.invokeMethod(
  417. windowId, kWindowEventRemoteWindowCoords, '');
  418. if (res != null) {
  419. coords.add(res);
  420. }
  421. }
  422. }
  423. }
  424. return coords;
  425. }
  426. // This function is called from one remote window.
  427. // Only the main window knows `_remoteDesktopWindows` and `_activeWindows`.
  428. // So we need to call the main window to get the other remote windows' coords.
  429. Future<List<RemoteWindowCoords>> getOtherRemoteWindowCoordsFromMain() async {
  430. List<RemoteWindowCoords> coords = [];
  431. // Call the main window to get the coords of other remote windows.
  432. String res = await DesktopMultiWindow.invokeMethod(
  433. kMainWindowId, kWindowEventRemoteWindowCoords, kWindowId.toString());
  434. List<dynamic> list = jsonDecode(res);
  435. for (var item in list) {
  436. coords.add(RemoteWindowCoords.fromJson(jsonDecode(item)));
  437. }
  438. return coords;
  439. }
  440. }
  441. final rustDeskWinManager = RustDeskMultiWindowManager.instance;