Merge pull request #5256 from dignow/refact/separate_remote_window
Refact/separate remote window
This commit is contained in:
commit
152616a261
@ -19,7 +19,6 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
|||||||
import 'package:flutter_hbb/utils/platform_channel.dart';
|
import 'package:flutter_hbb/utils/platform_channel.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
|
|
||||||
import 'package:uni_links/uni_links.dart';
|
import 'package:uni_links/uni_links.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
@ -47,11 +46,6 @@ var isMobile = isAndroid || isIOS;
|
|||||||
var version = "";
|
var version = "";
|
||||||
int androidVersion = 0;
|
int androidVersion = 0;
|
||||||
|
|
||||||
/// Incriment count for textureId.
|
|
||||||
int _textureId = 0;
|
|
||||||
int get newTextureId => _textureId++;
|
|
||||||
final textureRenderer = TextureRgbaRenderer();
|
|
||||||
|
|
||||||
/// only available for Windows target
|
/// only available for Windows target
|
||||||
int windowsBuildNumber = 0;
|
int windowsBuildNumber = 0;
|
||||||
DesktopType? desktopType;
|
DesktopType? desktopType;
|
||||||
@ -549,7 +543,7 @@ closeConnection({String? id}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void window_on_top(int? id) async {
|
void windowOnTop(int? id) async {
|
||||||
if (!isDesktop) {
|
if (!isDesktop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1225,7 +1219,7 @@ FFI get gFFI => _globalFFI;
|
|||||||
|
|
||||||
Future<void> initGlobalFFI() async {
|
Future<void> initGlobalFFI() async {
|
||||||
debugPrint("_globalFFI init");
|
debugPrint("_globalFFI init");
|
||||||
_globalFFI = FFI();
|
_globalFFI = FFI(null);
|
||||||
debugPrint("_globalFFI init end");
|
debugPrint("_globalFFI init end");
|
||||||
// after `put`, can also be globally found by Get.find<FFI>();
|
// after `put`, can also be globally found by Get.find<FFI>();
|
||||||
Get.put(_globalFFI, permanent: true);
|
Get.put(_globalFFI, permanent: true);
|
||||||
@ -1417,8 +1411,24 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
|
|||||||
sz.width, sz.height, position.dx, position.dy, isMaximized);
|
sz.width, sz.height, position.dx, position.dy, isMaximized);
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"Saving frame: $windowId: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}");
|
"Saving frame: $windowId: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}");
|
||||||
|
|
||||||
await bind.setLocalFlutterConfig(
|
await bind.setLocalFlutterConfig(
|
||||||
k: kWindowPrefix + type.name, v: pos.toString());
|
k: kWindowPrefix + type.name, v: pos.toString());
|
||||||
|
|
||||||
|
if (type == WindowType.RemoteDesktop && windowId != null) {
|
||||||
|
await _saveSessionWindowPosition(windowId, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _saveSessionWindowPosition(int windowId, LastWindowPosition pos) async {
|
||||||
|
final remoteList = await DesktopMultiWindow.invokeMethod(
|
||||||
|
windowId, kWindowEventGetRemoteList, null);
|
||||||
|
if (remoteList != null) {
|
||||||
|
for (final peerId in remoteList.split(',')) {
|
||||||
|
bind.sessionSetFlutterConfigByPeerId(
|
||||||
|
id: peerId, k: kWindowPrefix, v: pos.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Size> _adjustRestoreMainWindowSize(double? width, double? height) async {
|
Future<Size> _adjustRestoreMainWindowSize(double? width, double? height) async {
|
||||||
@ -1508,7 +1518,8 @@ Future<Offset?> _adjustRestoreMainWindowOffset(
|
|||||||
|
|
||||||
/// Restore window position and size on start
|
/// Restore window position and size on start
|
||||||
/// Note that windowId must be provided if it's subwindow
|
/// Note that windowId must be provided if it's subwindow
|
||||||
Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
|
Future<bool> restoreWindowPosition(WindowType type,
|
||||||
|
{int? windowId, String? peerId}) async {
|
||||||
if (bind
|
if (bind
|
||||||
.mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION")
|
.mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION")
|
||||||
.isNotEmpty) {
|
.isNotEmpty) {
|
||||||
@ -1517,13 +1528,31 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
|
|||||||
if (type != WindowType.Main && windowId == null) {
|
if (type != WindowType.Main && windowId == null) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"Error: windowId cannot be null when saving positions for sub window");
|
"Error: windowId cannot be null when saving positions for sub window");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
final pos = bind.getLocalFlutterConfig(k: kWindowPrefix + type.name);
|
|
||||||
|
bool isRemotePeerPos = false;
|
||||||
|
String? pos;
|
||||||
|
if (type == WindowType.RemoteDesktop && windowId != null && peerId != null) {
|
||||||
|
pos = await bind.sessionGetFlutterConfigByPeerId(
|
||||||
|
id: peerId, k: kWindowPrefix);
|
||||||
|
isRemotePeerPos = pos != null;
|
||||||
|
}
|
||||||
|
pos ??= bind.getLocalFlutterConfig(k: kWindowPrefix + type.name);
|
||||||
|
|
||||||
var lpos = LastWindowPosition.loadFromString(pos);
|
var lpos = LastWindowPosition.loadFromString(pos);
|
||||||
if (lpos == null) {
|
if (lpos == null) {
|
||||||
debugPrint("no window position saved, ignoring position restoration");
|
debugPrint("no window position saved, ignoring position restoration");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (type == WindowType.RemoteDesktop && !isRemotePeerPos && windowId != null) {
|
||||||
|
if (lpos.offsetWidth != null) {
|
||||||
|
lpos.offsetWidth = lpos.offsetWidth! + windowId * 20;
|
||||||
|
}
|
||||||
|
if (lpos.offsetHeight != null) {
|
||||||
|
lpos.offsetHeight = lpos.offsetHeight! + windowId * 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WindowType.Main:
|
case WindowType.Main:
|
||||||
@ -1701,7 +1730,7 @@ bool handleUriLink({List<String>? cmdArgs, Uri? uri, String? uriString}) {
|
|||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
rustDeskWinManager.newRemoteDesktop(id!,
|
rustDeskWinManager.newRemoteDesktop(id!,
|
||||||
password: password,
|
password: password,
|
||||||
switch_uuid: switchUuid,
|
switchUuid: switchUuid,
|
||||||
forceRelay: forceRelay);
|
forceRelay: forceRelay);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -1766,17 +1795,20 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectMainDesktop(String id,
|
connectMainDesktop(
|
||||||
{required bool isFileTransfer,
|
String id, {
|
||||||
required bool isTcpTunneling,
|
required bool isFileTransfer,
|
||||||
required bool isRDP,
|
required bool isTcpTunneling,
|
||||||
bool? forceRelay}) async {
|
required bool isRDP,
|
||||||
|
bool? forceRelay,
|
||||||
|
bool forceSeparateWindow = false,
|
||||||
|
}) async {
|
||||||
if (isFileTransfer) {
|
if (isFileTransfer) {
|
||||||
await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay);
|
await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay);
|
||||||
} else if (isTcpTunneling || isRDP) {
|
} else if (isTcpTunneling || isRDP) {
|
||||||
await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay);
|
await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay);
|
||||||
} else {
|
} else {
|
||||||
await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay);
|
await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay, forceSeparateWindow: forceSeparateWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1784,10 +1816,14 @@ connectMainDesktop(String id,
|
|||||||
/// If [isFileTransfer], starts a session only for file transfer.
|
/// If [isFileTransfer], starts a session only for file transfer.
|
||||||
/// If [isTcpTunneling], starts a session only for tcp tunneling.
|
/// If [isTcpTunneling], starts a session only for tcp tunneling.
|
||||||
/// If [isRDP], starts a session only for rdp.
|
/// If [isRDP], starts a session only for rdp.
|
||||||
connect(BuildContext context, String id,
|
connect(
|
||||||
{bool isFileTransfer = false,
|
BuildContext context,
|
||||||
bool isTcpTunneling = false,
|
String id, {
|
||||||
bool isRDP = false}) async {
|
bool isFileTransfer = false,
|
||||||
|
bool isTcpTunneling = false,
|
||||||
|
bool isRDP = false,
|
||||||
|
bool forceSeparateWindow = false,
|
||||||
|
}) async {
|
||||||
if (id == '') return;
|
if (id == '') return;
|
||||||
id = id.replaceAll(' ', '');
|
id = id.replaceAll(' ', '');
|
||||||
final oldId = id;
|
final oldId = id;
|
||||||
@ -1798,18 +1834,22 @@ connect(BuildContext context, String id,
|
|||||||
|
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
if (desktopType == DesktopType.main) {
|
if (desktopType == DesktopType.main) {
|
||||||
await connectMainDesktop(id,
|
await connectMainDesktop(
|
||||||
isFileTransfer: isFileTransfer,
|
id,
|
||||||
isTcpTunneling: isTcpTunneling,
|
isFileTransfer: isFileTransfer,
|
||||||
isRDP: isRDP,
|
isTcpTunneling: isTcpTunneling,
|
||||||
forceRelay: forceRelay);
|
isRDP: isRDP,
|
||||||
|
forceRelay: forceRelay,
|
||||||
|
forceSeparateWindow: forceSeparateWindow,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await rustDeskWinManager.call(WindowType.Main, kWindowConnect, {
|
await rustDeskWinManager.call(WindowType.Main, kWindowConnect, {
|
||||||
'id': id,
|
'id': id,
|
||||||
'isFileTransfer': isFileTransfer,
|
'isFileTransfer': isFileTransfer,
|
||||||
'isTcpTunneling': isTcpTunneling,
|
'isTcpTunneling': isTcpTunneling,
|
||||||
'isRDP': isRDP,
|
'isRDP': isRDP,
|
||||||
"forceRelay": forceRelay,
|
'forceRelay': forceRelay,
|
||||||
|
'forceSeparateWindow': forceSeparateWindow,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -399,10 +399,14 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(BuildContext context);
|
Future<List<MenuEntryBase<String>>> _buildMenuItems(BuildContext context);
|
||||||
|
|
||||||
MenuEntryBase<String> _connectCommonAction(
|
MenuEntryBase<String> _connectCommonAction(
|
||||||
BuildContext context, String id, String title,
|
BuildContext context,
|
||||||
{bool isFileTransfer = false,
|
String id,
|
||||||
bool isTcpTunneling = false,
|
String title, {
|
||||||
bool isRDP = false}) {
|
bool isFileTransfer = false,
|
||||||
|
bool isTcpTunneling = false,
|
||||||
|
bool isRDP = false,
|
||||||
|
bool forceSeparateWindow = false,
|
||||||
|
}) {
|
||||||
return MenuEntryButton<String>(
|
return MenuEntryButton<String>(
|
||||||
childBuilder: (TextStyle? style) => Text(
|
childBuilder: (TextStyle? style) => Text(
|
||||||
title,
|
title,
|
||||||
@ -415,6 +419,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
isFileTransfer: isFileTransfer,
|
isFileTransfer: isFileTransfer,
|
||||||
isTcpTunneling: isTcpTunneling,
|
isTcpTunneling: isTcpTunneling,
|
||||||
isRDP: isRDP,
|
isRDP: isRDP,
|
||||||
|
forceSeparateWindow: forceSeparateWindow,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
padding: menuPadding,
|
padding: menuPadding,
|
||||||
@ -423,13 +428,26 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
MenuEntryBase<String> _connectAction(BuildContext context, Peer peer) {
|
List<MenuEntryBase<String>> _connectActions(BuildContext context, Peer peer) {
|
||||||
|
final actions = [_connectAction(context, peer, false)];
|
||||||
|
if (!mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) {
|
||||||
|
actions.add(_connectAction(context, peer, true));
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
MenuEntryBase<String> _connectAction(
|
||||||
|
BuildContext context, Peer peer, bool forceSeparateWindow) {
|
||||||
return _connectCommonAction(
|
return _connectCommonAction(
|
||||||
context,
|
context,
|
||||||
peer.id,
|
peer.id,
|
||||||
peer.alias.isEmpty
|
(peer.alias.isEmpty
|
||||||
? translate('Connect')
|
? translate('Connect')
|
||||||
: "${translate('Connect')} ${peer.id}");
|
: '${translate('Connect')} ${peer.id}') +
|
||||||
|
(forceSeparateWindow ? ' (${translate('separate window')})' : ''),
|
||||||
|
forceSeparateWindow: forceSeparateWindow,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
@ -796,7 +814,7 @@ class RecentPeerCard extends BasePeerCard {
|
|||||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||||
BuildContext context) async {
|
BuildContext context) async {
|
||||||
final List<MenuEntryBase<String>> menuItems = [
|
final List<MenuEntryBase<String>> menuItems = [
|
||||||
_connectAction(context, peer),
|
..._connectActions(context, peer),
|
||||||
_transferFileAction(context, peer.id),
|
_transferFileAction(context, peer.id),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -852,7 +870,7 @@ class FavoritePeerCard extends BasePeerCard {
|
|||||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||||
BuildContext context) async {
|
BuildContext context) async {
|
||||||
final List<MenuEntryBase<String>> menuItems = [
|
final List<MenuEntryBase<String>> menuItems = [
|
||||||
_connectAction(context, peer),
|
..._connectActions(context, peer),
|
||||||
_transferFileAction(context, peer.id),
|
_transferFileAction(context, peer.id),
|
||||||
];
|
];
|
||||||
if (isDesktop && peer.platform != 'Android') {
|
if (isDesktop && peer.platform != 'Android') {
|
||||||
@ -902,7 +920,7 @@ class DiscoveredPeerCard extends BasePeerCard {
|
|||||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||||
BuildContext context) async {
|
BuildContext context) async {
|
||||||
final List<MenuEntryBase<String>> menuItems = [
|
final List<MenuEntryBase<String>> menuItems = [
|
||||||
_connectAction(context, peer),
|
..._connectActions(context, peer),
|
||||||
_transferFileAction(context, peer.id),
|
_transferFileAction(context, peer.id),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -954,7 +972,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
|||||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||||
BuildContext context) async {
|
BuildContext context) async {
|
||||||
final List<MenuEntryBase<String>> menuItems = [
|
final List<MenuEntryBase<String>> menuItems = [
|
||||||
_connectAction(context, peer),
|
..._connectActions(context, peer),
|
||||||
_transferFileAction(context, peer.id),
|
_transferFileAction(context, peer.id),
|
||||||
];
|
];
|
||||||
if (isDesktop && peer.platform != 'Android') {
|
if (isDesktop && peer.platform != 'Android') {
|
||||||
@ -1016,7 +1034,7 @@ class MyGroupPeerCard extends BasePeerCard {
|
|||||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||||
BuildContext context) async {
|
BuildContext context) async {
|
||||||
final List<MenuEntryBase<String>> menuItems = [
|
final List<MenuEntryBase<String>> menuItems = [
|
||||||
_connectAction(context, peer),
|
..._connectActions(context, peer),
|
||||||
_transferFileAction(context, peer.id),
|
_transferFileAction(context, peer.id),
|
||||||
];
|
];
|
||||||
if (isDesktop && peer.platform != 'Android') {
|
if (isDesktop && peer.platform != 'Android') {
|
||||||
|
@ -22,6 +22,8 @@ const String kAppTypeDesktopRemote = "remote";
|
|||||||
const String kAppTypeDesktopFileTransfer = "file transfer";
|
const String kAppTypeDesktopFileTransfer = "file transfer";
|
||||||
const String kAppTypeDesktopPortForward = "port forward";
|
const String kAppTypeDesktopPortForward = "port forward";
|
||||||
|
|
||||||
|
const bool kCloseMultiWindowByHide = true;
|
||||||
|
|
||||||
const String kWindowMainWindowOnTop = "main_window_on_top";
|
const String kWindowMainWindowOnTop = "main_window_on_top";
|
||||||
const String kWindowGetWindowInfo = "get_window_info";
|
const String kWindowGetWindowInfo = "get_window_info";
|
||||||
const String kWindowDisableGrabKeyboard = "disable_grab_keyboard";
|
const String kWindowDisableGrabKeyboard = "disable_grab_keyboard";
|
||||||
@ -30,6 +32,17 @@ const String kWindowEventHide = "hide";
|
|||||||
const String kWindowEventShow = "show";
|
const String kWindowEventShow = "show";
|
||||||
const String kWindowConnect = "connect";
|
const String kWindowConnect = "connect";
|
||||||
|
|
||||||
|
const String kWindowEventNewRemoteDesktop = "new_remote_desktop";
|
||||||
|
const String kWindowEventNewFileTransfer = "new_file_transfer";
|
||||||
|
const String kWindowEventNewPortForward = "new_port_forward";
|
||||||
|
const String kWindowEventActiveSession = "active_session";
|
||||||
|
const String kWindowEventGetRemoteList = "get_remote_list";
|
||||||
|
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
||||||
|
|
||||||
|
const String kWindowEventCloseForSeparateWindow = "close_for_separate_window";
|
||||||
|
|
||||||
|
const String kOptionSeparateRemoteWindow = "enable-separate-remote-window";
|
||||||
|
|
||||||
const String kUniLinksPrefix = "rustdesk://";
|
const String kUniLinksPrefix = "rustdesk://";
|
||||||
const String kUrlActionClose = "close";
|
const String kUrlActionClose = "close";
|
||||||
|
|
||||||
|
@ -527,7 +527,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
"[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
"[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||||
if (call.method == kWindowMainWindowOnTop) {
|
if (call.method == kWindowMainWindowOnTop) {
|
||||||
window_on_top(null);
|
windowOnTop(null);
|
||||||
} else if (call.method == kWindowGetWindowInfo) {
|
} else if (call.method == kWindowGetWindowInfo) {
|
||||||
final screen = (await window_size.getWindowInfo()).screen;
|
final screen = (await window_size.getWindowInfo()).screen;
|
||||||
if (screen == null) {
|
if (screen == null) {
|
||||||
@ -554,7 +554,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
} else if (call.method == kWindowEventShow) {
|
} else if (call.method == kWindowEventShow) {
|
||||||
await rustDeskWinManager.registerActiveWindow(call.arguments["id"]);
|
await rustDeskWinManager.registerActiveWindow(call.arguments["id"]);
|
||||||
} else if (call.method == kWindowEventHide) {
|
} else if (call.method == kWindowEventHide) {
|
||||||
await rustDeskWinManager.unregisterActiveWindow(call.arguments["id"]);
|
final wId = call.arguments['id'];
|
||||||
|
final isSeparateWindowEnabled =
|
||||||
|
mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow);
|
||||||
|
if (isSeparateWindowEnabled && !kCloseMultiWindowByHide) {
|
||||||
|
await rustDeskWinManager.destroyWindow(wId);
|
||||||
|
}
|
||||||
|
await rustDeskWinManager.unregisterActiveWindow(wId);
|
||||||
} else if (call.method == kWindowConnect) {
|
} else if (call.method == kWindowConnect) {
|
||||||
await connectMainDesktop(
|
await connectMainDesktop(
|
||||||
call.arguments['id'],
|
call.arguments['id'],
|
||||||
@ -562,6 +568,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
isTcpTunneling: call.arguments['isTcpTunneling'],
|
isTcpTunneling: call.arguments['isTcpTunneling'],
|
||||||
isRDP: call.arguments['isRDP'],
|
isRDP: call.arguments['isRDP'],
|
||||||
forceRelay: call.arguments['forceRelay'],
|
forceRelay: call.arguments['forceRelay'],
|
||||||
|
forceSeparateWindow: call.arguments['forceSeparateWindow'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
@ -248,7 +249,7 @@ class _General extends StatefulWidget {
|
|||||||
|
|
||||||
class _GeneralState extends State<_General> {
|
class _GeneralState extends State<_General> {
|
||||||
final RxBool serviceStop = Get.find<RxBool>(tag: 'stop-service');
|
final RxBool serviceStop = Get.find<RxBool>(tag: 'stop-service');
|
||||||
RxBool serviceBtnEabled = true.obs;
|
RxBool serviceBtnEnabled = true.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -300,14 +301,14 @@ class _GeneralState extends State<_General> {
|
|||||||
return _Card(title: 'Service', children: [
|
return _Card(title: 'Service', children: [
|
||||||
Obx(() => _Button(serviceStop.value ? 'Start' : 'Stop', () {
|
Obx(() => _Button(serviceStop.value ? 'Start' : 'Stop', () {
|
||||||
() async {
|
() async {
|
||||||
serviceBtnEabled.value = false;
|
serviceBtnEnabled.value = false;
|
||||||
await start_service(serviceStop.value);
|
await start_service(serviceStop.value);
|
||||||
// enable the button after 1 second
|
// enable the button after 1 second
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
serviceBtnEabled.value = true;
|
serviceBtnEnabled.value = true;
|
||||||
});
|
});
|
||||||
}();
|
}();
|
||||||
}, enabled: serviceBtnEabled.value))
|
}, enabled: serviceBtnEnabled.value))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +317,20 @@ class _GeneralState extends State<_General> {
|
|||||||
_OptionCheckBox(context, 'Confirm before closing multiple tabs',
|
_OptionCheckBox(context, 'Confirm before closing multiple tabs',
|
||||||
'enable-confirm-closing-tabs',
|
'enable-confirm-closing-tabs',
|
||||||
isServer: false),
|
isServer: false),
|
||||||
_OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr')
|
_OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr'),
|
||||||
|
_OptionCheckBox(
|
||||||
|
context,
|
||||||
|
'Separate remote window',
|
||||||
|
kOptionSeparateRemoteWindow,
|
||||||
|
isServer: false,
|
||||||
|
update: () {
|
||||||
|
final useSeparateWindow =
|
||||||
|
mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow);
|
||||||
|
if (useSeparateWindow) {
|
||||||
|
rustDeskWinManager.separateWindows();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
// though this is related to GUI, but opengl problem affects all users, so put in config rather than local
|
// though this is related to GUI, but opengl problem affects all users, so put in config rather than local
|
||||||
children.add(Tooltip(
|
children.add(Tooltip(
|
||||||
@ -1671,12 +1685,13 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
|
|||||||
var ref = value.obs;
|
var ref = value.obs;
|
||||||
onChanged(option) async {
|
onChanged(option) async {
|
||||||
if (option != null) {
|
if (option != null) {
|
||||||
ref.value = option;
|
|
||||||
if (reverse) option = !option;
|
if (reverse) option = !option;
|
||||||
isServer
|
isServer
|
||||||
? await mainSetBoolOption(key, option)
|
? await mainSetBoolOption(key, option)
|
||||||
: await mainSetLocalBoolOption(key, option);
|
: await mainSetLocalBoolOption(key, option);
|
||||||
;
|
ref.value = isServer
|
||||||
|
? mainGetBoolOptionSync(key)
|
||||||
|
: mainGetLocalBoolOptionSync(key);
|
||||||
update?.call();
|
update?.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_ffi = FFI();
|
_ffi = FFI(null);
|
||||||
_ffi.start(widget.id,
|
_ffi.start(widget.id,
|
||||||
isFileTransfer: true,
|
isFileTransfer: true,
|
||||||
password: widget.password,
|
password: widget.password,
|
||||||
|
@ -60,10 +60,10 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
print(
|
print(
|
||||||
"[FileTransfer] call ${call.method} with args ${call.arguments} from window $fromWindowId to ${windowId()}");
|
"[FileTransfer] call ${call.method} with args ${call.arguments} from window $fromWindowId to ${windowId()}");
|
||||||
// for simplify, just replace connectionId
|
// for simplify, just replace connectionId
|
||||||
if (call.method == "new_file_transfer") {
|
if (call.method == kWindowEventNewFileTransfer) {
|
||||||
final args = jsonDecode(call.arguments);
|
final args = jsonDecode(call.arguments);
|
||||||
final id = args['id'];
|
final id = args['id'];
|
||||||
window_on_top(windowId());
|
windowOnTop(windowId());
|
||||||
tabController.add(TabInfo(
|
tabController.add(TabInfo(
|
||||||
key: id,
|
key: id,
|
||||||
label: id,
|
label: id,
|
||||||
|
@ -54,7 +54,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_ffi = FFI();
|
_ffi = FFI(null);
|
||||||
_ffi.start(widget.id,
|
_ffi.start(widget.id,
|
||||||
isPortForward: true,
|
isPortForward: true,
|
||||||
password: widget.password,
|
password: widget.password,
|
||||||
|
@ -60,11 +60,11 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
"[Port Forward] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
"[Port Forward] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||||
// for simplify, just replace connectionId
|
// for simplify, just replace connectionId
|
||||||
if (call.method == "new_port_forward") {
|
if (call.method == kWindowEventNewPortForward) {
|
||||||
final args = jsonDecode(call.arguments);
|
final args = jsonDecode(call.arguments);
|
||||||
final id = args['id'];
|
final id = args['id'];
|
||||||
final isRDP = args['isRDP'];
|
final isRDP = args['isRDP'];
|
||||||
window_on_top(windowId());
|
windowOnTop(windowId());
|
||||||
if (tabController.state.value.tabs.indexWhere((e) => e.key == id) >=
|
if (tabController.state.value.tabs.indexWhere((e) => e.key == id) >=
|
||||||
0) {
|
0) {
|
||||||
debugPrint("port forward $id exists");
|
debugPrint("port forward $id exists");
|
||||||
|
@ -18,6 +18,7 @@ import '../../common/widgets/remote_input.dart';
|
|||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
|
import '../../models/desktop_render_texture.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../common/shared_state.dart';
|
import '../../common/shared_state.dart';
|
||||||
import '../../utils/image.dart';
|
import '../../utils/image.dart';
|
||||||
@ -27,10 +28,13 @@ import '../widgets/tabbar_widget.dart';
|
|||||||
|
|
||||||
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
|
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
|
||||||
|
|
||||||
|
final Map<String, bool> closeSessionOnDispose = {};
|
||||||
|
|
||||||
class RemotePage extends StatefulWidget {
|
class RemotePage extends StatefulWidget {
|
||||||
RemotePage({
|
RemotePage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.id,
|
required this.id,
|
||||||
|
required this.sessionId,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.toolbarState,
|
required this.toolbarState,
|
||||||
required this.tabController,
|
required this.tabController,
|
||||||
@ -39,6 +43,7 @@ class RemotePage extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
|
final SessionID? sessionId;
|
||||||
final String? password;
|
final String? password;
|
||||||
final ToolbarState toolbarState;
|
final ToolbarState toolbarState;
|
||||||
final String? switchUuid;
|
final String? switchUuid;
|
||||||
@ -66,9 +71,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
late RxBool _zoomCursor;
|
late RxBool _zoomCursor;
|
||||||
late RxBool _remoteCursorMoved;
|
late RxBool _remoteCursorMoved;
|
||||||
late RxBool _keyboardEnabled;
|
late RxBool _keyboardEnabled;
|
||||||
late RxInt _textureId;
|
late RenderTexture _renderTexture;
|
||||||
late int _textureKey;
|
|
||||||
final useTextureRender = bind.mainUseTextureRender();
|
|
||||||
|
|
||||||
final _blockableOverlayState = BlockableOverlayState();
|
final _blockableOverlayState = BlockableOverlayState();
|
||||||
|
|
||||||
@ -86,15 +89,13 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
_showRemoteCursor = ShowRemoteCursorState.find(id);
|
_showRemoteCursor = ShowRemoteCursorState.find(id);
|
||||||
_keyboardEnabled = KeyboardEnabledState.find(id);
|
_keyboardEnabled = KeyboardEnabledState.find(id);
|
||||||
_remoteCursorMoved = RemoteCursorMovedState.find(id);
|
_remoteCursorMoved = RemoteCursorMovedState.find(id);
|
||||||
_textureKey = newTextureId;
|
|
||||||
_textureId = RxInt(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initStates(widget.id);
|
_initStates(widget.id);
|
||||||
_ffi = FFI();
|
_ffi = FFI(widget.sessionId);
|
||||||
Get.put(_ffi, tag: widget.id);
|
Get.put(_ffi, tag: widget.id);
|
||||||
_ffi.imageModel.addCallbackOnFirstImage((String peerId) {
|
_ffi.imageModel.addCallbackOnFirstImage((String peerId) {
|
||||||
showKBLayoutTypeChooserIfNeeded(
|
showKBLayoutTypeChooserIfNeeded(
|
||||||
@ -115,17 +116,13 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
Wakelock.enable();
|
Wakelock.enable();
|
||||||
}
|
}
|
||||||
// Register texture.
|
// Register texture.
|
||||||
_textureId.value = -1;
|
if (mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) {
|
||||||
if (useTextureRender) {
|
_renderTexture = renderTexture;
|
||||||
textureRenderer.createTexture(_textureKey).then((id) async {
|
} else {
|
||||||
debugPrint("id: $id, texture_key: $_textureKey");
|
_renderTexture = RenderTexture();
|
||||||
if (id != -1) {
|
|
||||||
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
|
||||||
platformFFI.registerTexture(sessionId, ptr);
|
|
||||||
_textureId.value = id;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
_renderTexture.create(sessionId);
|
||||||
|
|
||||||
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
||||||
bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
||||||
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||||
@ -206,26 +203,25 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
final closeSession = closeSessionOnDispose.remove(widget.id) ?? true;
|
||||||
|
|
||||||
// https://github.com/flutter/flutter/issues/64935
|
// https://github.com/flutter/flutter/issues/64935
|
||||||
super.dispose();
|
super.dispose();
|
||||||
debugPrint("REMOTE PAGE dispose ${widget.id}");
|
debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}");
|
||||||
if (useTextureRender) {
|
await _renderTexture.destroy();
|
||||||
platformFFI.registerTexture(sessionId, 0);
|
|
||||||
// sleep for a while to avoid the texture is used after it's unregistered.
|
|
||||||
await Future.delayed(Duration(milliseconds: 100));
|
|
||||||
await textureRenderer.closeTexture(_textureKey);
|
|
||||||
}
|
|
||||||
// ensure we leave this session, this is a double check
|
// ensure we leave this session, this is a double check
|
||||||
bind.sessionEnterOrLeave(sessionId: sessionId, enter: false);
|
bind.sessionEnterOrLeave(sessionId: sessionId, enter: false);
|
||||||
DesktopMultiWindow.removeListener(this);
|
DesktopMultiWindow.removeListener(this);
|
||||||
_ffi.dialogManager.hideMobileActionsOverlay();
|
_ffi.dialogManager.hideMobileActionsOverlay();
|
||||||
_ffi.recordingModel.onClose();
|
_ffi.recordingModel.onClose();
|
||||||
_rawKeyFocusNode.dispose();
|
_rawKeyFocusNode.dispose();
|
||||||
await _ffi.close();
|
await _ffi.close(closeSession: closeSession);
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_ffi.dialogManager.dismissAll();
|
_ffi.dialogManager.dismissAll();
|
||||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
if (closeSession) {
|
||||||
overlays: SystemUiOverlay.values);
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||||
|
overlays: SystemUiOverlay.values);
|
||||||
|
}
|
||||||
if (!Platform.isLinux) {
|
if (!Platform.isLinux) {
|
||||||
await Wakelock.disable();
|
await Wakelock.disable();
|
||||||
}
|
}
|
||||||
@ -392,8 +388,8 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
cursorOverImage: _cursorOverImage,
|
cursorOverImage: _cursorOverImage,
|
||||||
keyboardEnabled: _keyboardEnabled,
|
keyboardEnabled: _keyboardEnabled,
|
||||||
remoteCursorMoved: _remoteCursorMoved,
|
remoteCursorMoved: _remoteCursorMoved,
|
||||||
textureId: _textureId,
|
textureId: _renderTexture.textureId,
|
||||||
useTextureRender: useTextureRender,
|
useTextureRender: _renderTexture.useTextureRender,
|
||||||
listenerBuilder: (child) =>
|
listenerBuilder: (child) =>
|
||||||
_buildRawTouchAndPointerRegion(child, enterView, leaveView),
|
_buildRawTouchAndPointerRegion(child, enterView, leaveView),
|
||||||
);
|
);
|
||||||
|
@ -52,6 +52,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
_toolbarState = ToolbarState();
|
_toolbarState = ToolbarState();
|
||||||
RemoteCountState.init();
|
RemoteCountState.init();
|
||||||
final peerId = params['id'];
|
final peerId = params['id'];
|
||||||
|
final sessionId = params['session_id'];
|
||||||
if (peerId != null) {
|
if (peerId != null) {
|
||||||
ConnectionTypeState.init(peerId);
|
ConnectionTypeState.init(peerId);
|
||||||
tabController.onSelected = (id) {
|
tabController.onSelected = (id) {
|
||||||
@ -73,6 +74,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
page: RemotePage(
|
page: RemotePage(
|
||||||
key: ValueKey(peerId),
|
key: ValueKey(peerId),
|
||||||
id: peerId,
|
id: peerId,
|
||||||
|
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||||
password: params['password'],
|
password: params['password'],
|
||||||
toolbarState: _toolbarState,
|
toolbarState: _toolbarState,
|
||||||
tabController: tabController,
|
tabController: tabController,
|
||||||
@ -95,11 +97,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
"[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
"[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||||
|
|
||||||
// for simplify, just replace connectionId
|
// for simplify, just replace connectionId
|
||||||
if (call.method == "new_remote_desktop") {
|
if (call.method == kWindowEventNewRemoteDesktop) {
|
||||||
final args = jsonDecode(call.arguments);
|
final args = jsonDecode(call.arguments);
|
||||||
final id = args['id'];
|
final id = args['id'];
|
||||||
final switchUuid = args['switch_uuid'];
|
final switchUuid = args['switch_uuid'];
|
||||||
window_on_top(windowId());
|
final sessionId = args['session_id'];
|
||||||
|
windowOnTop(windowId());
|
||||||
ConnectionTypeState.init(id);
|
ConnectionTypeState.init(id);
|
||||||
_toolbarState.setShow(
|
_toolbarState.setShow(
|
||||||
bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y');
|
bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y');
|
||||||
@ -112,6 +115,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
page: RemotePage(
|
page: RemotePage(
|
||||||
key: ValueKey(id),
|
key: ValueKey(id),
|
||||||
id: id,
|
id: id,
|
||||||
|
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||||
password: args['password'],
|
password: args['password'],
|
||||||
toolbarState: _toolbarState,
|
toolbarState: _toolbarState,
|
||||||
tabController: tabController,
|
tabController: tabController,
|
||||||
@ -125,11 +129,37 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
tabController.clear();
|
tabController.clear();
|
||||||
} else if (call.method == kWindowActionRebuild) {
|
} else if (call.method == kWindowActionRebuild) {
|
||||||
reloadCurrentWindow();
|
reloadCurrentWindow();
|
||||||
|
} else if (call.method == kWindowEventActiveSession) {
|
||||||
|
final jumpOk = tabController.jumpToByKey(call.arguments);
|
||||||
|
if (jumpOk) {
|
||||||
|
windowOnTop(windowId());
|
||||||
|
}
|
||||||
|
return jumpOk;
|
||||||
|
} else if (call.method == kWindowEventGetRemoteList) {
|
||||||
|
return tabController.state.value.tabs
|
||||||
|
.map((e) => e.key)
|
||||||
|
.toList()
|
||||||
|
.join(',');
|
||||||
|
} else if (call.method == kWindowEventGetSessionIdList) {
|
||||||
|
return tabController.state.value.tabs
|
||||||
|
.map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}')
|
||||||
|
.toList()
|
||||||
|
.join(';');
|
||||||
|
} else if (call.method == kWindowEventCloseForSeparateWindow) {
|
||||||
|
final peerId = call.arguments;
|
||||||
|
closeSessionOnDispose[peerId] = false;
|
||||||
|
tabController.closeBy(peerId);
|
||||||
}
|
}
|
||||||
_update_remote_count();
|
_update_remote_count();
|
||||||
});
|
});
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId());
|
restoreWindowPosition(
|
||||||
|
WindowType.RemoteDesktop,
|
||||||
|
windowId: windowId(),
|
||||||
|
peerId: tabController.state.value.tabs.isEmpty
|
||||||
|
? null
|
||||||
|
: tabController.state.value.tabs[0].key,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import 'package:flutter_hbb/consts.dart';
|
|||||||
import 'package:flutter_hbb/main.dart';
|
import 'package:flutter_hbb/main.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
|
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||||
@ -146,8 +147,10 @@ class DesktopTabController {
|
|||||||
|
|
||||||
/// For addTab, tabPage has not been initialized, set [callOnSelected] to false,
|
/// For addTab, tabPage has not been initialized, set [callOnSelected] to false,
|
||||||
/// and call [onSelected] at the end of initState
|
/// and call [onSelected] at the end of initState
|
||||||
void jumpTo(int index, {bool callOnSelected = true}) {
|
bool jumpTo(int index, {bool callOnSelected = true}) {
|
||||||
if (!isDesktop || index < 0) return;
|
if (!isDesktop || index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
state.update((val) {
|
state.update((val) {
|
||||||
val!.selected = index;
|
val!.selected = index;
|
||||||
Future.delayed(Duration(milliseconds: 100), (() {
|
Future.delayed(Duration(milliseconds: 100), (() {
|
||||||
@ -168,8 +171,13 @@ class DesktopTabController {
|
|||||||
onSelected?.call(key);
|
onSelected?.call(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool jumpToByKey(String key, {bool callOnSelected = true}) =>
|
||||||
|
jumpTo(state.value.tabs.indexWhere((tab) => tab.key == key),
|
||||||
|
callOnSelected: callOnSelected);
|
||||||
|
|
||||||
void closeBy(String? key) {
|
void closeBy(String? key) {
|
||||||
if (!isDesktop) return;
|
if (!isDesktop) return;
|
||||||
assert(onRemoved != null);
|
assert(onRemoved != null);
|
||||||
@ -574,6 +582,8 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
}
|
}
|
||||||
await windowManager.hide();
|
await windowManager.hide();
|
||||||
} else {
|
} else {
|
||||||
|
renderTexture.destroy();
|
||||||
|
|
||||||
// it's safe to hide the subwindow
|
// it's safe to hide the subwindow
|
||||||
final controller = WindowController.fromWindowId(kWindowId!);
|
final controller = WindowController.fromWindowId(kWindowId!);
|
||||||
if (Platform.isMacOS && await controller.isFullScreen()) {
|
if (Platform.isMacOS && await controller.isFullScreen()) {
|
||||||
|
@ -197,7 +197,7 @@ void runMultiWindow(
|
|||||||
switch (appType) {
|
switch (appType) {
|
||||||
case kAppTypeDesktopRemote:
|
case kAppTypeDesktopRemote:
|
||||||
await restoreWindowPosition(WindowType.RemoteDesktop,
|
await restoreWindowPosition(WindowType.RemoteDesktop,
|
||||||
windowId: kWindowId!);
|
windowId: kWindowId!, peerId: argument['id'] as String?);
|
||||||
break;
|
break;
|
||||||
case kAppTypeDesktopFileTransfer:
|
case kAppTypeDesktopFileTransfer:
|
||||||
await restoreWindowPosition(WindowType.FileTransfer,
|
await restoreWindowPosition(WindowType.FileTransfer,
|
||||||
@ -250,7 +250,7 @@ showCmWindow({bool isStartup = false}) async {
|
|||||||
await windowManager.minimize(); //needed
|
await windowManager.minimize(); //needed
|
||||||
await windowManager.setSizeAlignment(
|
await windowManager.setSizeAlignment(
|
||||||
kConnectionManagerWindowSizeClosedChat, Alignment.topRight);
|
kConnectionManagerWindowSizeClosedChat, Alignment.topRight);
|
||||||
window_on_top(null);
|
windowOnTop(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,7 +367,7 @@ class ChatModel with ChangeNotifier {
|
|||||||
// not minisized: add count
|
// not minisized: add count
|
||||||
if (await WindowController.fromWindowId(stateGlobal.windowId)
|
if (await WindowController.fromWindowId(stateGlobal.windowId)
|
||||||
.isMinimized()) {
|
.isMinimized()) {
|
||||||
window_on_top(stateGlobal.windowId);
|
windowOnTop(stateGlobal.windowId);
|
||||||
if (notSelected) {
|
if (notSelected) {
|
||||||
tabController.jumpTo(index);
|
tabController.jumpTo(index);
|
||||||
}
|
}
|
||||||
@ -386,7 +386,7 @@ class ChatModel with ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
window_on_top(null);
|
windowOnTop(null);
|
||||||
// disable auto jumpTo other tab when hasFocus, and mark unread message
|
// disable auto jumpTo other tab when hasFocus, and mark unread message
|
||||||
final currentSelectedTab =
|
final currentSelectedTab =
|
||||||
session.serverModel.tabController.state.value.selectedTabInfo;
|
session.serverModel.tabController.state.value.selectedTabInfo;
|
||||||
|
46
flutter/lib/models/desktop_render_texture.dart
Normal file
46
flutter/lib/models/desktop_render_texture.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
|
||||||
|
|
||||||
|
import '../../common.dart';
|
||||||
|
import './platform_model.dart';
|
||||||
|
|
||||||
|
class RenderTexture {
|
||||||
|
final RxInt textureId = RxInt(-1);
|
||||||
|
int _textureKey = -1;
|
||||||
|
SessionID? _sessionId;
|
||||||
|
final useTextureRender = bind.mainUseTextureRender();
|
||||||
|
|
||||||
|
final textureRenderer = TextureRgbaRenderer();
|
||||||
|
|
||||||
|
RenderTexture();
|
||||||
|
|
||||||
|
create(SessionID sessionId) {
|
||||||
|
if (useTextureRender) {
|
||||||
|
_textureKey = bind.getNextTextureKey();
|
||||||
|
_sessionId = sessionId;
|
||||||
|
|
||||||
|
textureRenderer.createTexture(_textureKey).then((id) async {
|
||||||
|
debugPrint("id: $id, texture_key: $_textureKey");
|
||||||
|
if (id != -1) {
|
||||||
|
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
||||||
|
platformFFI.registerTexture(sessionId, ptr);
|
||||||
|
textureId.value = id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() async {
|
||||||
|
if (useTextureRender && _textureKey != -1 && _sessionId != null) {
|
||||||
|
platformFFI.registerTexture(_sessionId!, 0);
|
||||||
|
await textureRenderer.closeTexture(_textureKey);
|
||||||
|
_textureKey = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final RenderTexture instance = RenderTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global instance for separate texture
|
||||||
|
final renderTexture = RenderTexture.instance;
|
@ -260,7 +260,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
window_on_top(null);
|
windowOnTop(null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1583,6 +1583,7 @@ class FFI {
|
|||||||
/// dialogManager use late to ensure init after main page binding [globalKey]
|
/// dialogManager use late to ensure init after main page binding [globalKey]
|
||||||
late final dialogManager = OverlayDialogManager();
|
late final dialogManager = OverlayDialogManager();
|
||||||
|
|
||||||
|
late final bool isSessionAdded;
|
||||||
late final SessionID sessionId;
|
late final SessionID sessionId;
|
||||||
late final ImageModel imageModel; // session
|
late final ImageModel imageModel; // session
|
||||||
late final FfiModel ffiModel; // session
|
late final FfiModel ffiModel; // session
|
||||||
@ -1600,8 +1601,9 @@ class FFI {
|
|||||||
late final InputModel inputModel; // session
|
late final InputModel inputModel; // session
|
||||||
late final ElevationModel elevationModel; // session
|
late final ElevationModel elevationModel; // session
|
||||||
|
|
||||||
FFI() {
|
FFI(SessionID? sId) {
|
||||||
sessionId = isDesktop ? Uuid().v4obj() : _constSessionId;
|
isSessionAdded = sId != null;
|
||||||
|
sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId);
|
||||||
imageModel = ImageModel(WeakReference(this));
|
imageModel = ImageModel(WeakReference(this));
|
||||||
ffiModel = FfiModel(WeakReference(this));
|
ffiModel = FfiModel(WeakReference(this));
|
||||||
cursorModel = CursorModel(WeakReference(this));
|
cursorModel = CursorModel(WeakReference(this));
|
||||||
@ -1641,23 +1643,31 @@ class FFI {
|
|||||||
imageModel.id = id;
|
imageModel.id = id;
|
||||||
cursorModel.id = id;
|
cursorModel.id = id;
|
||||||
}
|
}
|
||||||
// ignore: unused_local_variable
|
if (!isSessionAdded) {
|
||||||
final addRes = bind.sessionAddSync(
|
// ignore: unused_local_variable
|
||||||
sessionId: sessionId,
|
final addRes = bind.sessionAddSync(
|
||||||
id: id,
|
sessionId: sessionId,
|
||||||
isFileTransfer: isFileTransfer,
|
id: id,
|
||||||
isPortForward: isPortForward,
|
isFileTransfer: isFileTransfer,
|
||||||
isRdp: isRdp,
|
isPortForward: isPortForward,
|
||||||
switchUuid: switchUuid ?? "",
|
isRdp: isRdp,
|
||||||
forceRelay: forceRelay ?? false,
|
switchUuid: switchUuid ?? '',
|
||||||
password: password ?? "",
|
forceRelay: forceRelay ?? false,
|
||||||
);
|
password: password ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
final stream = bind.sessionStart(sessionId: sessionId, id: id);
|
final stream = bind.sessionStart(sessionId: sessionId, id: id);
|
||||||
final cb = ffiModel.startEventListener(sessionId, id);
|
final cb = ffiModel.startEventListener(sessionId, id);
|
||||||
final useTextureRender = bind.mainUseTextureRender();
|
final useTextureRender = bind.mainUseTextureRender();
|
||||||
|
|
||||||
|
final SimpleWrapper<bool> isToNewWindowNotified = SimpleWrapper(false);
|
||||||
// Preserved for the rgba data.
|
// Preserved for the rgba data.
|
||||||
stream.listen((message) {
|
stream.listen((message) {
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
|
if (isSessionAdded && !isToNewWindowNotified.value) {
|
||||||
|
bind.sessionReadyToNewWindow(sessionId: sessionId);
|
||||||
|
isToNewWindowNotified.value = true;
|
||||||
|
}
|
||||||
() async {
|
() async {
|
||||||
if (message is EventToUI_Event) {
|
if (message is EventToUI_Event) {
|
||||||
if (message.field0 == "close") {
|
if (message.field0 == "close") {
|
||||||
@ -1717,7 +1727,7 @@ class FFI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Close the remote session.
|
/// Close the remote session.
|
||||||
Future<void> close() async {
|
Future<void> close({bool closeSession = true}) async {
|
||||||
closed = true;
|
closed = true;
|
||||||
chatModel.close();
|
chatModel.close();
|
||||||
if (imageModel.image != null && !isWebDesktop) {
|
if (imageModel.image != null && !isWebDesktop) {
|
||||||
@ -1735,7 +1745,9 @@ class FFI {
|
|||||||
ffiModel.clear();
|
ffiModel.clear();
|
||||||
canvasModel.clear();
|
canvasModel.clear();
|
||||||
inputModel.resetModifiers();
|
inputModel.resetModifiers();
|
||||||
await bind.sessionClose(sessionId: sessionId);
|
if (closeSession) {
|
||||||
|
await bind.sessionClose(sessionId: sessionId);
|
||||||
|
}
|
||||||
debugPrint('model $id closed');
|
debugPrint('model $id closed');
|
||||||
id = '';
|
id = '';
|
||||||
}
|
}
|
||||||
|
@ -473,7 +473,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
page: desktop.buildConnectionCard(client)));
|
page: desktop.buildConnectionCard(client)));
|
||||||
Future.delayed(Duration.zero, () async {
|
Future.delayed(Duration.zero, () async {
|
||||||
if (!hideCm) window_on_top(null);
|
if (!hideCm) windowOnTop(null);
|
||||||
});
|
});
|
||||||
// Only do the hidden task when on Desktop.
|
// Only do the hidden task when on Desktop.
|
||||||
if (client.authorized && isDesktop) {
|
if (client.authorized && isDesktop) {
|
||||||
@ -612,7 +612,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
if (client.incomingVoiceCall) {
|
if (client.incomingVoiceCall) {
|
||||||
// Has incoming phone call, let's set the window on top.
|
// Has incoming phone call, let's set the window on top.
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
window_on_top(null);
|
windowOnTop(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -5,6 +5,7 @@ import 'package:desktop_multi_window/desktop_multi_window.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
|
|
||||||
/// must keep the order
|
/// must keep the order
|
||||||
@ -35,146 +36,204 @@ class RustDeskMultiWindowManager {
|
|||||||
|
|
||||||
static final instance = RustDeskMultiWindowManager._();
|
static final instance = RustDeskMultiWindowManager._();
|
||||||
|
|
||||||
final List<int> _activeWindows = List.empty(growable: true);
|
final Set<int> _inactiveWindows = {};
|
||||||
|
final Set<int> _activeWindows = {};
|
||||||
final List<AsyncCallback> _windowActiveCallbacks = List.empty(growable: true);
|
final List<AsyncCallback> _windowActiveCallbacks = List.empty(growable: true);
|
||||||
int? _remoteDesktopWindowId;
|
final List<int> _remoteDesktopWindows = List.empty(growable: true);
|
||||||
int? _fileTransferWindowId;
|
final List<int> _fileTransferWindows = List.empty(growable: true);
|
||||||
int? _portForwardWindowId;
|
final List<int> _portForwardWindows = List.empty(growable: true);
|
||||||
|
|
||||||
Future<dynamic> newRemoteDesktop(
|
separateWindows() async {
|
||||||
String remoteId, {
|
for (final windowId in _remoteDesktopWindows.toList()) {
|
||||||
|
final String sessionIdList = await DesktopMultiWindow.invokeMethod(
|
||||||
|
windowId, kWindowEventGetSessionIdList, null);
|
||||||
|
final idList = sessionIdList.split(';');
|
||||||
|
if (idList.length <= 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (final idPair in idList.sublist(1)) {
|
||||||
|
final peerSession = idPair.split(',');
|
||||||
|
var params = {
|
||||||
|
'type': WindowType.RemoteDesktop.index,
|
||||||
|
'id': peerSession[0],
|
||||||
|
'session_id': peerSession[1],
|
||||||
|
};
|
||||||
|
await _newSession(
|
||||||
|
true,
|
||||||
|
WindowType.RemoteDesktop,
|
||||||
|
kWindowEventNewRemoteDesktop,
|
||||||
|
peerSession[0],
|
||||||
|
_remoteDesktopWindows,
|
||||||
|
jsonEncode(params),
|
||||||
|
);
|
||||||
|
await DesktopMultiWindow.invokeMethod(
|
||||||
|
windowId, kWindowEventCloseForSeparateWindow, peerSession[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
_newSession(
|
||||||
|
bool separateWindow,
|
||||||
|
WindowType type,
|
||||||
|
String methodName,
|
||||||
|
String remoteId,
|
||||||
|
List<int> windows,
|
||||||
|
String msg,
|
||||||
|
) async {
|
||||||
|
if (separateWindow) {
|
||||||
|
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.add(windowController.windowId);
|
||||||
|
return invokeRes;
|
||||||
|
} else {
|
||||||
|
await newSessionWindow(type, remoteId, msg, windows);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (windows.isEmpty) {
|
||||||
|
await newSessionWindow(type, remoteId, msg, windows);
|
||||||
|
} else {
|
||||||
|
return call(type, methodName, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> newSession(
|
||||||
|
WindowType type,
|
||||||
|
String methodName,
|
||||||
|
String remoteId,
|
||||||
|
List<int> windows, {
|
||||||
String? password,
|
String? password,
|
||||||
String? switch_uuid,
|
|
||||||
bool? forceRelay,
|
bool? forceRelay,
|
||||||
|
String? switchUuid,
|
||||||
|
bool? isRDP,
|
||||||
|
bool forceSeparateWindow = false,
|
||||||
}) async {
|
}) async {
|
||||||
var params = {
|
var params = {
|
||||||
"type": WindowType.RemoteDesktop.index,
|
"type": type.index,
|
||||||
"id": remoteId,
|
"id": remoteId,
|
||||||
"password": password,
|
"password": password,
|
||||||
"forceRelay": forceRelay
|
"forceRelay": forceRelay
|
||||||
};
|
};
|
||||||
if (switch_uuid != null) {
|
if (switchUuid != null) {
|
||||||
params['switch_uuid'] = switch_uuid;
|
params['switch_uuid'] = switchUuid;
|
||||||
|
}
|
||||||
|
if (isRDP != null) {
|
||||||
|
params['isRDP'] = isRDP;
|
||||||
}
|
}
|
||||||
final msg = jsonEncode(params);
|
final msg = jsonEncode(params);
|
||||||
|
|
||||||
try {
|
// separate window for file transfer is not supported
|
||||||
final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
bool separateWindow = forceSeparateWindow ||
|
||||||
if (!ids.contains(_remoteDesktopWindowId)) {
|
(type != WindowType.FileTransfer &&
|
||||||
_remoteDesktopWindowId = null;
|
mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow));
|
||||||
|
|
||||||
|
if (windows.length > 1 || separateWindow) {
|
||||||
|
for (final windowId in windows) {
|
||||||
|
if (await DesktopMultiWindow.invokeMethod(
|
||||||
|
windowId, kWindowEventActiveSession, remoteId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} on Error {
|
|
||||||
_remoteDesktopWindowId = null;
|
|
||||||
}
|
|
||||||
if (_remoteDesktopWindowId == null) {
|
|
||||||
final remoteDesktopController =
|
|
||||||
await DesktopMultiWindow.createWindow(msg);
|
|
||||||
remoteDesktopController
|
|
||||||
..setFrame(const Offset(0, 0) & const Size(1280, 720))
|
|
||||||
..center()
|
|
||||||
..setTitle(getWindowNameWithId(remoteId,
|
|
||||||
overrideType: WindowType.RemoteDesktop));
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
Future.microtask(() => remoteDesktopController.show());
|
|
||||||
}
|
|
||||||
registerActiveWindow(remoteDesktopController.windowId);
|
|
||||||
_remoteDesktopWindowId = remoteDesktopController.windowId;
|
|
||||||
} else {
|
|
||||||
return call(WindowType.RemoteDesktop, "new_remote_desktop", msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _newSession(separateWindow, type, methodName, remoteId, windows, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> newRemoteDesktop(
|
||||||
|
String remoteId, {
|
||||||
|
String? password,
|
||||||
|
String? switchUuid,
|
||||||
|
bool? forceRelay,
|
||||||
|
bool forceSeparateWindow = false,
|
||||||
|
}) async {
|
||||||
|
return await newSession(
|
||||||
|
WindowType.RemoteDesktop,
|
||||||
|
kWindowEventNewRemoteDesktop,
|
||||||
|
remoteId,
|
||||||
|
_remoteDesktopWindows,
|
||||||
|
password: password,
|
||||||
|
forceRelay: forceRelay,
|
||||||
|
switchUuid: switchUuid,
|
||||||
|
forceSeparateWindow: forceSeparateWindow,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> newFileTransfer(String remoteId,
|
Future<dynamic> newFileTransfer(String remoteId,
|
||||||
{String? password, bool? forceRelay}) async {
|
{String? password, bool? forceRelay}) async {
|
||||||
var msg = jsonEncode({
|
return await newSession(
|
||||||
"type": WindowType.FileTransfer.index,
|
WindowType.FileTransfer,
|
||||||
"id": remoteId,
|
kWindowEventNewFileTransfer,
|
||||||
"password": password,
|
remoteId,
|
||||||
"forceRelay": forceRelay,
|
_fileTransferWindows,
|
||||||
});
|
password: password,
|
||||||
|
forceRelay: forceRelay,
|
||||||
try {
|
);
|
||||||
final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
|
||||||
if (!ids.contains(_fileTransferWindowId)) {
|
|
||||||
_fileTransferWindowId = null;
|
|
||||||
}
|
|
||||||
} on Error {
|
|
||||||
_fileTransferWindowId = null;
|
|
||||||
}
|
|
||||||
if (_fileTransferWindowId == null) {
|
|
||||||
final fileTransferController = await DesktopMultiWindow.createWindow(msg);
|
|
||||||
fileTransferController
|
|
||||||
..setFrame(const Offset(0, 0) & const Size(1280, 720))
|
|
||||||
..center()
|
|
||||||
..setTitle(getWindowNameWithId(remoteId,
|
|
||||||
overrideType: WindowType.FileTransfer));
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
Future.microtask(() => fileTransferController.show());
|
|
||||||
}
|
|
||||||
registerActiveWindow(fileTransferController.windowId);
|
|
||||||
_fileTransferWindowId = fileTransferController.windowId;
|
|
||||||
} else {
|
|
||||||
return call(WindowType.FileTransfer, "new_file_transfer", msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> newPortForward(String remoteId, bool isRDP,
|
Future<dynamic> newPortForward(String remoteId, bool isRDP,
|
||||||
{String? password, bool? forceRelay}) async {
|
{String? password, bool? forceRelay}) async {
|
||||||
final msg = jsonEncode({
|
return await newSession(
|
||||||
"type": WindowType.PortForward.index,
|
WindowType.PortForward,
|
||||||
"id": remoteId,
|
kWindowEventNewPortForward,
|
||||||
"isRDP": isRDP,
|
remoteId,
|
||||||
"password": password,
|
_portForwardWindows,
|
||||||
"forceRelay": forceRelay,
|
password: password,
|
||||||
});
|
forceRelay: forceRelay,
|
||||||
|
isRDP: isRDP,
|
||||||
try {
|
);
|
||||||
final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
|
||||||
if (!ids.contains(_portForwardWindowId)) {
|
|
||||||
_portForwardWindowId = null;
|
|
||||||
}
|
|
||||||
} on Error {
|
|
||||||
_portForwardWindowId = null;
|
|
||||||
}
|
|
||||||
if (_portForwardWindowId == null) {
|
|
||||||
final portForwardController = await DesktopMultiWindow.createWindow(msg);
|
|
||||||
portForwardController
|
|
||||||
..setFrame(const Offset(0, 0) & const Size(1280, 720))
|
|
||||||
..center()
|
|
||||||
..setTitle(getWindowNameWithId(remoteId,
|
|
||||||
overrideType: WindowType.PortForward));
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
Future.microtask(() => portForwardController.show());
|
|
||||||
}
|
|
||||||
registerActiveWindow(portForwardController.windowId);
|
|
||||||
_portForwardWindowId = portForwardController.windowId;
|
|
||||||
} else {
|
|
||||||
return call(WindowType.PortForward, "new_port_forward", msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> call(WindowType type, String methodName, dynamic args) async {
|
Future<dynamic> call(WindowType type, String methodName, dynamic args) async {
|
||||||
int? windowId = findWindowByType(type);
|
final wnds = _findWindowsByType(type);
|
||||||
if (windowId == null) {
|
if (wnds.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return await DesktopMultiWindow.invokeMethod(windowId, methodName, args);
|
for (final windowId in wnds) {
|
||||||
|
if (_activeWindows.contains(windowId)) {
|
||||||
|
return await DesktopMultiWindow.invokeMethod(windowId, methodName, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
int? findWindowByType(WindowType type) {
|
List<int> _findWindowsByType(WindowType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WindowType.Main:
|
case WindowType.Main:
|
||||||
return 0;
|
return [0];
|
||||||
case WindowType.RemoteDesktop:
|
case WindowType.RemoteDesktop:
|
||||||
return _remoteDesktopWindowId;
|
return _remoteDesktopWindows;
|
||||||
case WindowType.FileTransfer:
|
case WindowType.FileTransfer:
|
||||||
return _fileTransferWindowId;
|
return _fileTransferWindows;
|
||||||
case WindowType.PortForward:
|
case WindowType.PortForward:
|
||||||
return _portForwardWindowId;
|
return _portForwardWindows;
|
||||||
case WindowType.Unknown:
|
case WindowType.Unknown:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearWindowType(WindowType type) {
|
void clearWindowType(WindowType type) {
|
||||||
@ -182,13 +241,13 @@ class RustDeskMultiWindowManager {
|
|||||||
case WindowType.Main:
|
case WindowType.Main:
|
||||||
return;
|
return;
|
||||||
case WindowType.RemoteDesktop:
|
case WindowType.RemoteDesktop:
|
||||||
_remoteDesktopWindowId = null;
|
_remoteDesktopWindows.clear();
|
||||||
break;
|
break;
|
||||||
case WindowType.FileTransfer:
|
case WindowType.FileTransfer:
|
||||||
_fileTransferWindowId = null;
|
_fileTransferWindows.clear();
|
||||||
break;
|
break;
|
||||||
case WindowType.PortForward:
|
case WindowType.PortForward:
|
||||||
_portForwardWindowId = null;
|
_portForwardWindows.clear();
|
||||||
break;
|
break;
|
||||||
case WindowType.Unknown:
|
case WindowType.Unknown:
|
||||||
break;
|
break;
|
||||||
@ -209,27 +268,37 @@ class RustDeskMultiWindowManager {
|
|||||||
// skip main window, use window manager instead
|
// skip main window, use window manager instead
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int? wId = findWindowByType(type);
|
|
||||||
if (wId != null) {
|
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) {
|
||||||
debugPrint("closing multi window: ${type.toString()}");
|
debugPrint("closing multi window: ${type.toString()}");
|
||||||
await saveWindowPosition(type, windowId: wId);
|
await saveWindowPosition(type, windowId: wId);
|
||||||
try {
|
try {
|
||||||
final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
// final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
||||||
if (!ids.contains(wId)) {
|
// if (!ids.contains(wId)) {
|
||||||
// no such window already
|
// // no such window already
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
await WindowController.fromWindowId(wId).setPreventClose(false);
|
await WindowController.fromWindowId(wId).setPreventClose(false);
|
||||||
await WindowController.fromWindowId(wId).close();
|
await WindowController.fromWindowId(wId).close();
|
||||||
// unregister the sub window in the main window.
|
_activeWindows.remove(wId);
|
||||||
unregisterActiveWindow(wId);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("$e");
|
debugPrint("$e");
|
||||||
return;
|
return;
|
||||||
} finally {
|
|
||||||
clearWindowType(type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await _notifyActiveWindow();
|
||||||
|
clearWindowType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>> getAllSubWindowIds() async {
|
Future<List<int>> getAllSubWindowIds() async {
|
||||||
@ -245,7 +314,7 @@ class RustDeskMultiWindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> getActiveWindows() {
|
Set<int> getActiveWindows() {
|
||||||
return _activeWindows;
|
return _activeWindows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,14 +325,19 @@ class RustDeskMultiWindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> registerActiveWindow(int windowId) async {
|
Future<void> registerActiveWindow(int windowId) async {
|
||||||
if (_activeWindows.contains(windowId)) {
|
_activeWindows.add(windowId);
|
||||||
// ignore
|
_inactiveWindows.remove(windowId);
|
||||||
} else {
|
|
||||||
_activeWindows.add(windowId);
|
|
||||||
}
|
|
||||||
await _notifyActiveWindow();
|
await _notifyActiveWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove active window which has [`windowId`]
|
/// Remove active window which has [`windowId`]
|
||||||
///
|
///
|
||||||
/// [Availability]
|
/// [Availability]
|
||||||
@ -271,10 +345,9 @@ class RustDeskMultiWindowManager {
|
|||||||
/// For other windows, please post a unregister(hide) event to main window handler:
|
/// For other windows, please post a unregister(hide) event to main window handler:
|
||||||
/// `rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId!});`
|
/// `rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId!});`
|
||||||
Future<void> unregisterActiveWindow(int windowId) async {
|
Future<void> unregisterActiveWindow(int windowId) async {
|
||||||
if (!_activeWindows.contains(windowId)) {
|
_activeWindows.remove(windowId);
|
||||||
// ignore
|
if (windowId != kMainWindowId) {
|
||||||
} else {
|
_inactiveWindows.add(windowId);
|
||||||
_activeWindows.remove(windowId);
|
|
||||||
}
|
}
|
||||||
await _notifyActiveWindow();
|
await _notifyActiveWindow();
|
||||||
}
|
}
|
||||||
|
@ -1008,6 +1008,11 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(login_response::Union::PeerInfo(pi)) => {
|
Some(login_response::Union::PeerInfo(pi)) => {
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
self.handler.cache_flutter.write().unwrap().pi = pi.clone();
|
||||||
|
}
|
||||||
self.handler.handle_peer_info(pi);
|
self.handler.handle_peer_info(pi);
|
||||||
#[cfg(not(feature = "flutter"))]
|
#[cfg(not(feature = "flutter"))]
|
||||||
self.check_clipboard_file_context();
|
self.check_clipboard_file_context();
|
||||||
@ -1055,9 +1060,22 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Some(message::Union::CursorData(cd)) => {
|
Some(message::Union::CursorData(cd)) => {
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
let mut lock = self.handler.cache_flutter.write().unwrap();
|
||||||
|
if !lock.cursor_data.contains_key(&cd.id) {
|
||||||
|
lock.cursor_data.insert(cd.id, cd.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
self.handler.set_cursor_data(cd);
|
self.handler.set_cursor_data(cd);
|
||||||
}
|
}
|
||||||
Some(message::Union::CursorId(id)) => {
|
Some(message::Union::CursorId(id)) => {
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
self.handler.cache_flutter.write().unwrap().cursor_id = id;
|
||||||
|
}
|
||||||
self.handler.set_cursor_id(id.to_string());
|
self.handler.set_cursor_id(id.to_string());
|
||||||
}
|
}
|
||||||
Some(message::Union::CursorPosition(cp)) => {
|
Some(message::Union::CursorPosition(cp)) => {
|
||||||
@ -1274,6 +1292,16 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(misc::Union::SwitchDisplay(s)) => {
|
Some(misc::Union::SwitchDisplay(s)) => {
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
self.handler
|
||||||
|
.cache_flutter
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.sp
|
||||||
|
.replace(s.clone());
|
||||||
|
}
|
||||||
self.handler.handle_peer_switch_display(&s);
|
self.handler.handle_peer_switch_display(&s);
|
||||||
self.video_sender.send(MediaData::Reset).ok();
|
self.video_sender.send(MediaData::Reset).ok();
|
||||||
if s.width > 0 && s.height > 0 {
|
if s.width > 0 && s.height > 0 {
|
||||||
|
@ -186,7 +186,7 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct VideoRenderer {
|
struct VideoRenderer {
|
||||||
// TextureRgba pointer in flutter native.
|
// TextureRgba pointer in flutter native.
|
||||||
ptr: usize,
|
ptr: Arc<RwLock<usize>>,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
||||||
@ -214,7 +214,7 @@ impl Default for VideoRenderer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
ptr: 0,
|
ptr: Default::default(),
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
on_rgba_func,
|
on_rgba_func,
|
||||||
@ -231,7 +231,8 @@ impl VideoRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
||||||
if self.ptr == usize::default() {
|
let ptr = self.ptr.read().unwrap();
|
||||||
|
if *ptr == usize::default() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +244,7 @@ impl VideoRenderer {
|
|||||||
if let Some(func) = &self.on_rgba_func {
|
if let Some(func) = &self.on_rgba_func {
|
||||||
unsafe {
|
unsafe {
|
||||||
func(
|
func(
|
||||||
self.ptr as _,
|
*ptr as _,
|
||||||
rgba.raw.as_ptr() as _,
|
rgba.raw.as_ptr() as _,
|
||||||
rgba.raw.len() as _,
|
rgba.raw.len() as _,
|
||||||
rgba.w as _,
|
rgba.w as _,
|
||||||
@ -328,7 +329,7 @@ impl FlutterHandler {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
pub fn register_texture(&mut self, ptr: usize) {
|
pub fn register_texture(&mut self, ptr: usize) {
|
||||||
self.renderer.write().unwrap().ptr = ptr;
|
*self.renderer.read().unwrap().ptr.write().unwrap() = ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -789,11 +790,15 @@ pub fn session_start_(
|
|||||||
);
|
);
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
log::info!("Session {} start, render by flutter paint widget", id);
|
log::info!("Session {} start, render by flutter paint widget", id);
|
||||||
|
let is_pre_added = session.event_stream.read().unwrap().is_some();
|
||||||
|
session.close_event_stream();
|
||||||
*session.event_stream.write().unwrap() = Some(event_stream);
|
*session.event_stream.write().unwrap() = Some(event_stream);
|
||||||
let session = session.clone();
|
if !is_pre_added {
|
||||||
std::thread::spawn(move || {
|
let session = session.clone();
|
||||||
io_loop(session);
|
std::thread::spawn(move || {
|
||||||
});
|
io_loop(session);
|
||||||
|
});
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
bail!("No session with peer id {}", id)
|
bail!("No session with peer id {}", id)
|
||||||
|
@ -15,7 +15,7 @@ use flutter_rust_bridge::{StreamSink, SyncReturn};
|
|||||||
use hbb_common::allow_err;
|
use hbb_common::allow_err;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
|
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
|
||||||
fs, log,
|
fs, lazy_static, log,
|
||||||
message_proto::KeyboardMode,
|
message_proto::KeyboardMode,
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
@ -24,11 +24,19 @@ use std::{
|
|||||||
ffi::{CStr, CString},
|
ffi::{CStr, CString},
|
||||||
os::raw::c_char,
|
os::raw::c_char,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicI32, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type SessionID = uuid::Uuid;
|
pub type SessionID = uuid::Uuid;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref TEXTURE_RENDER_KEY: Arc<AtomicI32> = Arc::new(AtomicI32::new(0));
|
||||||
|
}
|
||||||
|
|
||||||
fn initialize(app_dir: &str) {
|
fn initialize(app_dir: &str) {
|
||||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
@ -197,6 +205,30 @@ pub fn session_set_flutter_config(session_id: SessionID, k: String, v: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_get_flutter_config_by_peer_id(id: String, k: String) -> Option<String> {
|
||||||
|
if let Some((_, session)) = SESSIONS.read().unwrap().iter().find(|(_, s)| s.id == id) {
|
||||||
|
Some(session.get_flutter_config(k))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_set_flutter_config_by_peer_id(id: String, k: String, v: String) {
|
||||||
|
if let Some((_, session)) = SESSIONS
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.iter_mut()
|
||||||
|
.find(|(_, s)| s.id == id)
|
||||||
|
{
|
||||||
|
session.save_flutter_config(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_next_texture_key() -> SyncReturn<i32> {
|
||||||
|
let k = TEXTURE_RENDER_KEY.fetch_add(1, Ordering::SeqCst) + 1;
|
||||||
|
SyncReturn(k)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_local_flutter_config(k: String) -> SyncReturn<String> {
|
pub fn get_local_flutter_config(k: String) -> SyncReturn<String> {
|
||||||
SyncReturn(ui_interface::get_local_flutter_config(k))
|
SyncReturn(ui_interface::get_local_flutter_config(k))
|
||||||
}
|
}
|
||||||
@ -569,6 +601,14 @@ pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_ready_to_new_window(session_id: SessionID) {
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||||
|
session.restore_flutter_cache();
|
||||||
|
session.refresh_video();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
|
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", "管理的设备数已达到最大值"),
|
("exceed_max_devices", "管理的设备数已达到最大值"),
|
||||||
("Sync with recent sessions", "同步最近会话"),
|
("Sync with recent sessions", "同步最近会话"),
|
||||||
("Sort tags", "对标签进行排序"),
|
("Sort tags", "对标签进行排序"),
|
||||||
|
("Separate remote window", "使用独立远程窗口"),
|
||||||
|
("separate window", "独立窗口"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", "Sie haben die maximale Anzahl der verwalteten Geräte erreicht."),
|
("exceed_max_devices", "Sie haben die maximale Anzahl der verwalteten Geräte erreicht."),
|
||||||
("Sync with recent sessions", "Synchronisierung mit den letzten Sitzungen"),
|
("Sync with recent sessions", "Synchronisierung mit den letzten Sitzungen"),
|
||||||
("Sort tags", "Tags sortieren"),
|
("Sort tags", "Tags sortieren"),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", "Has alcanzado el máximo número de dispositivos administrados."),
|
("exceed_max_devices", "Has alcanzado el máximo número de dispositivos administrados."),
|
||||||
("Sync with recent sessions", "Sincronizar con sesiones recientes"),
|
("Sync with recent sessions", "Sincronizar con sesiones recientes"),
|
||||||
("Sort tags", "Ordenar etiquetas"),
|
("Sort tags", "Ordenar etiquetas"),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", "Hai raggiunto il numero massimo di dispositivi gestibili."),
|
("exceed_max_devices", "Hai raggiunto il numero massimo di dispositivi gestibili."),
|
||||||
("Sync with recent sessions", "Sincronizza con le sessioni recenti"),
|
("Sync with recent sessions", "Sincronizza con le sessioni recenti"),
|
||||||
("Sort tags", "Ordina etichette"),
|
("Sort tags", "Ordina etichette"),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", "Het maximum aantal gecontroleerde apparaten is bereikt."),
|
("exceed_max_devices", "Het maximum aantal gecontroleerde apparaten is bereikt."),
|
||||||
("Sync with recent sessions", "Recente sessies synchroniseren"),
|
("Sync with recent sessions", "Recente sessies synchroniseren"),
|
||||||
("Sort tags", "Labels sorteren"),
|
("Sort tags", "Labels sorteren"),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", "Достигнуто максимальне количество управляемых устройств."),
|
("exceed_max_devices", "Достигнуто максимальне количество управляемых устройств."),
|
||||||
("Sync with recent sessions", "Синхронизация последних сессий"),
|
("Sync with recent sessions", "Синхронизация последних сессий"),
|
||||||
("Sort tags", "Сортировка меток"),
|
("Sort tags", "Сортировка меток"),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -524,5 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", ""),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", ""),
|
||||||
("Sort tags", ""),
|
("Sort tags", ""),
|
||||||
|
("Separate remote window", ""),
|
||||||
|
("separate window", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
69
src/ui.rs
69
src/ui.rs
@ -94,8 +94,7 @@ pub fn start(args: &mut [String]) {
|
|||||||
args[1] = id;
|
args[1] = id;
|
||||||
}
|
}
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
let children: Children = Default::default();
|
std::thread::spawn(move || check_zombie());
|
||||||
std::thread::spawn(move || check_zombie(children));
|
|
||||||
crate::common::check_software_update();
|
crate::common::check_software_update();
|
||||||
frame.event_handler(UI {});
|
frame.event_handler(UI {});
|
||||||
frame.sciter_handler(UIHostHandler {});
|
frame.sciter_handler(UIHostHandler {});
|
||||||
@ -693,28 +692,6 @@ impl sciter::host::HostHandler for UIHostHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_zombie(children: Children) {
|
|
||||||
let mut deads = Vec::new();
|
|
||||||
loop {
|
|
||||||
let mut lock = children.lock().unwrap();
|
|
||||||
let mut n = 0;
|
|
||||||
for (id, c) in lock.1.iter_mut() {
|
|
||||||
if let Ok(Some(_)) = c.try_wait() {
|
|
||||||
deads.push(id.clone());
|
|
||||||
n += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ref id in deads.drain(..) {
|
|
||||||
lock.1.remove(id);
|
|
||||||
}
|
|
||||||
if n > 0 {
|
|
||||||
lock.0 = true;
|
|
||||||
}
|
|
||||||
drop(lock);
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
fn get_sound_inputs() -> Vec<String> {
|
fn get_sound_inputs() -> Vec<String> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
@ -748,50 +725,6 @@ pub fn value_crash_workaround(values: &[Value]) -> Arc<Vec<Value>> {
|
|||||||
persist
|
persist
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_remote(id: String, remote_type: String, force_relay: bool) {
|
|
||||||
let mut lock = CHILDREN.lock().unwrap();
|
|
||||||
let mut args = vec![format!("--{}", remote_type), id.clone()];
|
|
||||||
if force_relay {
|
|
||||||
args.push("".to_string()); // password
|
|
||||||
args.push("--relay".to_string());
|
|
||||||
}
|
|
||||||
let key = (id.clone(), remote_type.clone());
|
|
||||||
if let Some(c) = lock.1.get_mut(&key) {
|
|
||||||
if let Ok(Some(_)) = c.try_wait() {
|
|
||||||
lock.1.remove(&key);
|
|
||||||
} else {
|
|
||||||
if remote_type == "rdp" {
|
|
||||||
allow_err!(c.kill());
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
|
||||||
c.try_wait().ok();
|
|
||||||
lock.1.remove(&key);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match crate::run_me(args) {
|
|
||||||
Ok(child) => {
|
|
||||||
lock.1.insert(key, child);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Failed to spawn remote: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn recent_sessions_updated() -> bool {
|
|
||||||
let mut children = CHILDREN.lock().unwrap();
|
|
||||||
if children.0 {
|
|
||||||
children.0 = false;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_icon() -> String {
|
pub fn get_icon() -> String {
|
||||||
// 128x128
|
// 128x128
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
@ -65,6 +65,7 @@ lazy_static::lazy_static! {
|
|||||||
static ref OPTION_SYNCED: Arc<Mutex<bool>> = Default::default();
|
static ref OPTION_SYNCED: Arc<Mutex<bool>> = Default::default();
|
||||||
static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options()));
|
static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options()));
|
||||||
pub static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(check_connect_status(true));
|
pub static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(check_connect_status(true));
|
||||||
|
static ref CHILDREN : Children = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
const INIT_ASYNC_JOB_STATUS: &str = " ";
|
const INIT_ASYNC_JOB_STATUS: &str = " ";
|
||||||
@ -827,11 +828,11 @@ pub fn check_super_user_permission() -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
|
||||||
pub fn check_zombie(children: Children) {
|
pub fn check_zombie() {
|
||||||
let mut deads = Vec::new();
|
let mut deads = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
let mut lock = children.lock().unwrap();
|
let mut lock = CHILDREN.lock().unwrap();
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
for (id, c) in lock.1.iter_mut() {
|
for (id, c) in lock.1.iter_mut() {
|
||||||
if let Ok(Some(_)) = c.try_wait() {
|
if let Ok(Some(_)) = c.try_wait() {
|
||||||
@ -850,6 +851,51 @@ pub fn check_zombie(children: Children) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
|
||||||
|
pub fn recent_sessions_updated() -> bool {
|
||||||
|
let mut children = CHILDREN.lock().unwrap();
|
||||||
|
if children.0 {
|
||||||
|
children.0 = false;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
|
||||||
|
pub fn new_remote(id: String, remote_type: String, force_relay: bool) {
|
||||||
|
let mut lock = CHILDREN.lock().unwrap();
|
||||||
|
let mut args = vec![format!("--{}", remote_type), id.clone()];
|
||||||
|
if force_relay {
|
||||||
|
args.push("".to_string()); // password
|
||||||
|
args.push("--relay".to_string());
|
||||||
|
}
|
||||||
|
let key = (id.clone(), remote_type.clone());
|
||||||
|
if let Some(c) = lock.1.get_mut(&key) {
|
||||||
|
if let Ok(Some(_)) = c.try_wait() {
|
||||||
|
lock.1.remove(&key);
|
||||||
|
} else {
|
||||||
|
if remote_type == "rdp" {
|
||||||
|
allow_err!(c.kill());
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||||
|
c.try_wait().ok();
|
||||||
|
lock.1.remove(&key);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match crate::run_me(args) {
|
||||||
|
Ok(child) => {
|
||||||
|
lock.1.insert(key, child);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to spawn remote: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure `SENDER` is inited here.
|
// Make sure `SENDER` is inited here.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
@ -48,6 +48,16 @@ pub static IS_IN: AtomicBool = AtomicBool::new(false);
|
|||||||
|
|
||||||
const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
|
const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
|
||||||
|
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CacheFlutter {
|
||||||
|
pub pi: PeerInfo,
|
||||||
|
pub sp: Option<SwitchDisplay>,
|
||||||
|
pub cursor_data: HashMap<u64, CursorData>,
|
||||||
|
pub cursor_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Session<T: InvokeUiSession> {
|
pub struct Session<T: InvokeUiSession> {
|
||||||
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
|
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
|
||||||
@ -62,6 +72,9 @@ pub struct Session<T: InvokeUiSession> {
|
|||||||
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
|
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
|
||||||
pub server_clipboard_enabled: Arc<RwLock<bool>>,
|
pub server_clipboard_enabled: Arc<RwLock<bool>>,
|
||||||
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
|
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
pub cache_flutter: Arc<RwLock<CacheFlutter>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -1181,12 +1194,26 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
pub fn ctrl_alt_del(&self) {
|
pub fn ctrl_alt_del(&self) {
|
||||||
self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del());
|
self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
pub fn restore_flutter_cache(&mut self) {
|
||||||
|
let pi = self.cache_flutter.read().unwrap().pi.clone();
|
||||||
|
self.handle_peer_info(pi);
|
||||||
|
if let Some(sp) = self.cache_flutter.read().unwrap().sp.as_ref() {
|
||||||
|
self.handle_peer_switch_display(sp);
|
||||||
|
}
|
||||||
|
for (_, cd) in self.cache_flutter.read().unwrap().cursor_data.iter() {
|
||||||
|
self.set_cursor_data(cd.clone());
|
||||||
|
}
|
||||||
|
self.set_cursor_id(self.cache_flutter.read().unwrap().cursor_id.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
|
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
|
||||||
// It is ok to call this function multiple times.
|
// It is ok to call this function multiple times.
|
||||||
#[cfg(target_os ="windows")]
|
#[cfg(target_os = "windows")]
|
||||||
if !handler.is_file_transfer() && !handler.is_port_forward() {
|
if !handler.is_file_transfer() && !handler.is_port_forward() {
|
||||||
clipboard::ContextSend::enable(true);
|
clipboard::ContextSend::enable(true);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user