Merge pull request #5256 from dignow/refact/separate_remote_window

Refact/separate remote window
This commit is contained in:
RustDesk 2023-08-06 09:31:05 +08:00 committed by GitHub
commit 152616a261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 727 additions and 320 deletions

View File

@ -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 {

View File

@ -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') {

View File

@ -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";

View File

@ -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'],
); );
} }
}); });

View File

@ -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();
} }
} }

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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");

View File

@ -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),
); );

View File

@ -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,
);
}); });
} }

View File

@ -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()) {

View File

@ -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);
} }
} }
} }

View File

@ -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;

View 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;

View File

@ -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 = '';
} }

View File

@ -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();

View File

@ -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();
} }

View File

@ -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 {

View File

@ -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)

View File

@ -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) {

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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")]

View File

@ -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")))]

View File

@ -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);
} }