2022-05-29 17:19:50 +08:00
|
|
|
import 'dart:convert';
|
2023-01-28 09:33:57 +08:00
|
|
|
import 'dart:io';
|
2022-05-29 17:19:50 +08:00
|
|
|
|
|
|
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
2023-01-07 12:40:29 +08:00
|
|
|
import 'package:flutter/foundation.dart';
|
2022-08-09 13:39:30 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2022-05-29 17:19:50 +08:00
|
|
|
import 'package:flutter/services.dart';
|
2023-08-01 22:19:38 +08:00
|
|
|
import 'package:flutter_hbb/consts.dart';
|
2022-10-14 18:48:41 +08:00
|
|
|
import 'package:flutter_hbb/common.dart';
|
2022-05-29 17:19:50 +08:00
|
|
|
|
|
|
|
/// 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._();
|
|
|
|
|
2023-08-01 22:19:38 +08:00
|
|
|
final Set<int> _inactiveWindows = {};
|
|
|
|
final Set<int> _activeWindows = {};
|
2023-01-07 12:40:29 +08:00
|
|
|
final List<AsyncCallback> _windowActiveCallbacks = List.empty(growable: true);
|
2023-08-02 20:38:09 +08:00
|
|
|
final List<int> _remoteDesktopWindows = List.empty(growable: true);
|
|
|
|
final List<int> _fileTransferWindows = List.empty(growable: true);
|
|
|
|
final List<int> _portForwardWindows = List.empty(growable: true);
|
2022-05-29 17:19:50 +08:00
|
|
|
|
2023-08-06 11:21:07 +08:00
|
|
|
moveTabToNewWindow(int windowId, String peerId, String sessionId) async {
|
2023-08-06 10:19:30 +08:00
|
|
|
var params = {
|
|
|
|
'type': WindowType.RemoteDesktop.index,
|
|
|
|
'id': peerId,
|
|
|
|
'session_id': sessionId,
|
|
|
|
};
|
|
|
|
await _newSession(
|
|
|
|
true,
|
|
|
|
WindowType.RemoteDesktop,
|
|
|
|
kWindowEventNewRemoteDesktop,
|
|
|
|
peerId,
|
|
|
|
_remoteDesktopWindows,
|
|
|
|
jsonEncode(params),
|
|
|
|
);
|
|
|
|
await DesktopMultiWindow.invokeMethod(
|
|
|
|
windowId, kWindowEventCloseForSeparateWindow, peerId);
|
2023-08-03 23:14:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
newSessionWindow(
|
|
|
|
WindowType type, String remoteId, String msg, List<int> 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);
|
|
|
|
}
|
|
|
|
|
2023-08-04 00:17:11 +08:00
|
|
|
_newSession(
|
2023-08-08 12:12:35 +08:00
|
|
|
bool openInTabs,
|
2023-08-04 00:17:11 +08:00
|
|
|
WindowType type,
|
|
|
|
String methodName,
|
|
|
|
String remoteId,
|
|
|
|
List<int> windows,
|
|
|
|
String msg,
|
|
|
|
) async {
|
2023-08-08 12:12:35 +08:00
|
|
|
if (openInTabs) {
|
|
|
|
if (windows.isEmpty) {
|
|
|
|
await newSessionWindow(type, remoteId, msg, windows);
|
|
|
|
} else {
|
|
|
|
call(type, methodName, msg);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (_inactiveWindows.isNotEmpty) {
|
2023-08-06 16:47:41 +08:00
|
|
|
for (final windowId in windows) {
|
|
|
|
if (_inactiveWindows.contains(windowId)) {
|
2023-08-08 12:12:35 +08:00
|
|
|
await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
|
2023-08-06 16:47:41 +08:00
|
|
|
WindowController.fromWindowId(windowId).show();
|
|
|
|
registerActiveWindow(windowId);
|
2023-08-06 18:31:02 +08:00
|
|
|
return;
|
2023-08-06 16:47:41 +08:00
|
|
|
}
|
|
|
|
}
|
2023-08-04 00:17:11 +08:00
|
|
|
}
|
2023-08-06 16:47:41 +08:00
|
|
|
await newSessionWindow(type, remoteId, msg, windows);
|
2023-08-04 00:17:11 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-01 22:19:38 +08:00
|
|
|
Future<dynamic> newSession(
|
|
|
|
WindowType type,
|
|
|
|
String methodName,
|
|
|
|
String remoteId,
|
2023-08-02 20:38:09 +08:00
|
|
|
List<int> windows, {
|
2023-03-20 00:16:06 +08:00
|
|
|
String? password,
|
2023-02-13 16:40:24 +08:00
|
|
|
bool? forceRelay,
|
2023-08-01 22:19:38 +08:00
|
|
|
String? switchUuid,
|
|
|
|
bool? isRDP,
|
2023-02-13 16:40:24 +08:00
|
|
|
}) async {
|
2023-02-01 19:49:41 +08:00
|
|
|
var params = {
|
2023-08-01 22:19:38 +08:00
|
|
|
"type": type.index,
|
2023-01-17 13:28:33 +08:00
|
|
|
"id": remoteId,
|
2023-03-20 00:16:06 +08:00
|
|
|
"password": password,
|
2023-02-13 16:40:24 +08:00
|
|
|
"forceRelay": forceRelay
|
2023-02-01 19:49:41 +08:00
|
|
|
};
|
2023-08-01 22:19:38 +08:00
|
|
|
if (switchUuid != null) {
|
|
|
|
params['switch_uuid'] = switchUuid;
|
|
|
|
}
|
|
|
|
if (isRDP != null) {
|
|
|
|
params['isRDP'] = isRDP;
|
2023-02-01 19:49:41 +08:00
|
|
|
}
|
|
|
|
final msg = jsonEncode(params);
|
2022-05-29 17:19:50 +08:00
|
|
|
|
2023-08-01 22:19:38 +08:00
|
|
|
// separate window for file transfer is not supported
|
2023-08-08 14:22:32 +08:00
|
|
|
bool openInTabs = type != WindowType.RemoteDesktop ||
|
|
|
|
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
|
2022-06-17 22:57:41 +08:00
|
|
|
|
2023-08-08 12:12:35 +08:00
|
|
|
if (windows.length > 1 || !openInTabs) {
|
2023-08-02 20:38:09 +08:00
|
|
|
for (final windowId in windows) {
|
|
|
|
if (await DesktopMultiWindow.invokeMethod(
|
|
|
|
windowId, kWindowEventActiveSession, remoteId)) {
|
2023-08-01 22:19:38 +08:00
|
|
|
return;
|
|
|
|
}
|
2022-06-17 22:57:41 +08:00
|
|
|
}
|
2023-08-02 20:38:09 +08:00
|
|
|
}
|
|
|
|
|
2023-08-08 12:12:35 +08:00
|
|
|
await _newSession(openInTabs, type, methodName, remoteId, windows, msg);
|
2022-06-17 22:57:41 +08:00
|
|
|
}
|
|
|
|
|
2023-08-01 22:19:38 +08:00
|
|
|
Future<dynamic> newRemoteDesktop(
|
|
|
|
String remoteId, {
|
|
|
|
String? password,
|
|
|
|
String? switchUuid,
|
|
|
|
bool? forceRelay,
|
|
|
|
}) async {
|
|
|
|
return await newSession(
|
|
|
|
WindowType.RemoteDesktop,
|
2023-08-02 20:38:09 +08:00
|
|
|
kWindowEventNewRemoteDesktop,
|
2023-08-01 22:19:38 +08:00
|
|
|
remoteId,
|
|
|
|
_remoteDesktopWindows,
|
|
|
|
password: password,
|
|
|
|
forceRelay: forceRelay,
|
|
|
|
switchUuid: switchUuid,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<dynamic> newFileTransfer(String remoteId,
|
2023-07-07 12:22:39 +08:00
|
|
|
{String? password, bool? forceRelay}) async {
|
2023-08-01 22:19:38 +08:00
|
|
|
return await newSession(
|
|
|
|
WindowType.FileTransfer,
|
2023-08-02 20:38:09 +08:00
|
|
|
kWindowEventNewFileTransfer,
|
2023-08-01 22:19:38 +08:00
|
|
|
remoteId,
|
|
|
|
_fileTransferWindows,
|
|
|
|
password: password,
|
|
|
|
forceRelay: forceRelay,
|
|
|
|
);
|
|
|
|
}
|
2022-08-26 11:35:28 +08:00
|
|
|
|
2023-08-01 22:19:38 +08:00
|
|
|
Future<dynamic> newPortForward(String remoteId, bool isRDP,
|
|
|
|
{String? password, bool? forceRelay}) async {
|
|
|
|
return await newSession(
|
|
|
|
WindowType.PortForward,
|
2023-08-02 20:38:09 +08:00
|
|
|
kWindowEventNewPortForward,
|
2023-08-01 22:19:38 +08:00
|
|
|
remoteId,
|
|
|
|
_portForwardWindows,
|
|
|
|
password: password,
|
|
|
|
forceRelay: forceRelay,
|
|
|
|
isRDP: isRDP,
|
|
|
|
);
|
2022-08-26 11:35:28 +08:00
|
|
|
}
|
|
|
|
|
2022-05-29 17:19:50 +08:00
|
|
|
Future<dynamic> call(WindowType type, String methodName, dynamic args) async {
|
2023-08-01 22:19:38 +08:00
|
|
|
final wnds = _findWindowsByType(type);
|
|
|
|
if (wnds.isEmpty) {
|
2022-05-29 17:19:50 +08:00
|
|
|
return;
|
|
|
|
}
|
2023-08-05 17:57:12 +08:00
|
|
|
for (final windowId in wnds) {
|
|
|
|
if (_activeWindows.contains(windowId)) {
|
2023-08-06 10:19:30 +08:00
|
|
|
return await DesktopMultiWindow.invokeMethod(
|
|
|
|
windowId, methodName, args);
|
2023-08-05 17:57:12 +08:00
|
|
|
}
|
|
|
|
}
|
2023-08-02 23:10:31 +08:00
|
|
|
return await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
|
2022-05-29 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2023-08-02 20:38:09 +08:00
|
|
|
List<int> _findWindowsByType(WindowType type) {
|
2022-05-29 17:19:50 +08:00
|
|
|
switch (type) {
|
|
|
|
case WindowType.Main:
|
2023-08-06 10:19:30 +08:00
|
|
|
return [kMainWindowId];
|
2022-05-29 17:19:50 +08:00
|
|
|
case WindowType.RemoteDesktop:
|
2023-08-01 22:19:38 +08:00
|
|
|
return _remoteDesktopWindows;
|
2022-05-29 17:19:50 +08:00
|
|
|
case WindowType.FileTransfer:
|
2023-08-01 22:19:38 +08:00
|
|
|
return _fileTransferWindows;
|
2022-05-29 17:19:50 +08:00
|
|
|
case WindowType.PortForward:
|
2023-08-01 22:19:38 +08:00
|
|
|
return _portForwardWindows;
|
2022-05-29 17:19:50 +08:00
|
|
|
case WindowType.Unknown:
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 20:38:09 +08:00
|
|
|
return [];
|
2022-05-29 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2023-02-03 17:08:40 +08:00
|
|
|
void clearWindowType(WindowType type) {
|
|
|
|
switch (type) {
|
|
|
|
case WindowType.Main:
|
|
|
|
return;
|
|
|
|
case WindowType.RemoteDesktop:
|
2023-08-01 22:19:38 +08:00
|
|
|
_remoteDesktopWindows.clear();
|
2023-02-03 17:08:40 +08:00
|
|
|
break;
|
|
|
|
case WindowType.FileTransfer:
|
2023-08-01 22:19:38 +08:00
|
|
|
_fileTransferWindows.clear();
|
2023-02-03 17:08:40 +08:00
|
|
|
break;
|
|
|
|
case WindowType.PortForward:
|
2023-08-01 22:19:38 +08:00
|
|
|
_portForwardWindows.clear();
|
2023-02-03 17:08:40 +08:00
|
|
|
break;
|
|
|
|
case WindowType.Unknown:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-29 17:19:50 +08:00
|
|
|
void setMethodHandler(
|
|
|
|
Future<dynamic> Function(MethodCall call, int fromWindowId)? handler) {
|
|
|
|
DesktopMultiWindow.setMethodHandler(handler);
|
|
|
|
}
|
2022-08-09 13:39:30 +08:00
|
|
|
|
|
|
|
Future<void> closeAllSubWindows() async {
|
|
|
|
await Future.wait(WindowType.values.map((e) => closeWindows(e)));
|
|
|
|
}
|
|
|
|
|
2022-08-26 11:35:28 +08:00
|
|
|
Future<void> closeWindows(WindowType type) async {
|
2022-08-09 13:39:30 +08:00
|
|
|
if (type == WindowType.Main) {
|
|
|
|
// skip main window, use window manager instead
|
|
|
|
return;
|
|
|
|
}
|
2023-08-01 22:19:38 +08:00
|
|
|
|
|
|
|
List<int> 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) {
|
2022-08-09 13:39:30 +08:00
|
|
|
debugPrint("closing multi window: ${type.toString()}");
|
2022-10-27 18:40:45 +08:00
|
|
|
await saveWindowPosition(type, windowId: wId);
|
2022-08-09 13:39:30 +08:00
|
|
|
try {
|
2023-08-01 22:19:38 +08:00
|
|
|
// final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
|
|
|
// if (!ids.contains(wId)) {
|
|
|
|
// // no such window already
|
|
|
|
// return;
|
|
|
|
// }
|
2022-11-06 17:39:19 +08:00
|
|
|
await WindowController.fromWindowId(wId).setPreventClose(false);
|
2022-08-30 20:48:03 +08:00
|
|
|
await WindowController.fromWindowId(wId).close();
|
2023-08-01 22:19:38 +08:00
|
|
|
_activeWindows.remove(wId);
|
2023-02-03 17:08:40 +08:00
|
|
|
} catch (e) {
|
|
|
|
debugPrint("$e");
|
2022-08-09 13:39:30 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2023-08-01 22:19:38 +08:00
|
|
|
await _notifyActiveWindow();
|
|
|
|
clearWindowType(type);
|
2022-08-09 13:39:30 +08:00
|
|
|
}
|
2022-11-05 23:41:22 +08:00
|
|
|
|
|
|
|
Future<List<int>> getAllSubWindowIds() async {
|
|
|
|
try {
|
|
|
|
final windows = await DesktopMultiWindow.getAllSubWindowIds();
|
|
|
|
return windows;
|
|
|
|
} catch (err) {
|
|
|
|
if (err is AssertionError) {
|
|
|
|
return [];
|
|
|
|
} else {
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-01 22:19:38 +08:00
|
|
|
Set<int> getActiveWindows() {
|
2022-11-05 23:41:22 +08:00
|
|
|
return _activeWindows;
|
|
|
|
}
|
|
|
|
|
2023-01-07 12:40:29 +08:00
|
|
|
Future<void> _notifyActiveWindow() async {
|
2022-11-05 23:41:22 +08:00
|
|
|
for (final callback in _windowActiveCallbacks) {
|
2023-01-07 12:40:29 +08:00
|
|
|
await callback.call();
|
2022-11-05 23:41:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-07 12:40:29 +08:00
|
|
|
Future<void> registerActiveWindow(int windowId) async {
|
2023-08-01 22:19:38 +08:00
|
|
|
_activeWindows.add(windowId);
|
|
|
|
_inactiveWindows.remove(windowId);
|
2023-01-07 12:40:29 +08:00
|
|
|
await _notifyActiveWindow();
|
2022-11-05 23:41:22 +08:00
|
|
|
}
|
|
|
|
|
2023-08-01 22:19:38 +08:00
|
|
|
Future<void> destroyWindow(int windowId) async {
|
|
|
|
await WindowController.fromWindowId(windowId).setPreventClose(false);
|
|
|
|
await WindowController.fromWindowId(windowId).close();
|
|
|
|
_remoteDesktopWindows.remove(windowId);
|
|
|
|
_fileTransferWindows.remove(windowId);
|
|
|
|
_portForwardWindows.remove(windowId);
|
|
|
|
}
|
|
|
|
|
2022-11-06 17:39:19 +08:00
|
|
|
/// Remove active window which has [`windowId`]
|
2023-01-17 13:28:33 +08:00
|
|
|
///
|
2022-12-26 01:21:13 +08:00
|
|
|
/// [Availability]
|
2022-11-06 17:39:19 +08:00
|
|
|
/// 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!});`
|
2023-01-07 12:40:29 +08:00
|
|
|
Future<void> unregisterActiveWindow(int windowId) async {
|
2023-08-01 22:19:38 +08:00
|
|
|
_activeWindows.remove(windowId);
|
|
|
|
if (windowId != kMainWindowId) {
|
|
|
|
_inactiveWindows.add(windowId);
|
2022-11-05 23:41:22 +08:00
|
|
|
}
|
2023-01-07 12:40:29 +08:00
|
|
|
await _notifyActiveWindow();
|
2022-11-05 23:41:22 +08:00
|
|
|
}
|
|
|
|
|
2023-01-07 12:40:29 +08:00
|
|
|
void registerActiveWindowListener(AsyncCallback callback) {
|
2022-11-05 23:41:22 +08:00
|
|
|
_windowActiveCallbacks.add(callback);
|
|
|
|
}
|
|
|
|
|
2023-01-07 12:40:29 +08:00
|
|
|
void unregisterActiveWindowListener(AsyncCallback callback) {
|
2022-11-05 23:41:22 +08:00
|
|
|
_windowActiveCallbacks.remove(callback);
|
|
|
|
}
|
2022-05-29 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
final rustDeskWinManager = RustDeskMultiWindowManager.instance;
|