feat, open multi windows

Signed-off-by: dignow <linlong1265@gmail.com>
This commit is contained in:
dignow 2023-10-17 00:30:34 +08:00
parent f5d8e99fc7
commit bf83d552f8
47 changed files with 363 additions and 80 deletions

View File

@ -14,8 +14,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart'; import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
@ -53,6 +53,9 @@ int androidVersion = 0;
int windowsBuildNumber = 0; int windowsBuildNumber = 0;
DesktopType? desktopType; DesktopType? desktopType;
bool get isMainDesktopWindow =>
desktopType == DesktopType.main || desktopType == DesktopType.cm;
/// Check if the app is running with single view mode. /// Check if the app is running with single view mode.
bool isSingleViewApp() { bool isSingleViewApp() {
return desktopType == DesktopType.cm; return desktopType == DesktopType.cm;
@ -1672,6 +1675,7 @@ Future<Offset?> _adjustRestoreMainWindowOffset(
/// 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, Future<bool> restoreWindowPosition(WindowType type,
{int? windowId, String? peerId}) async { {int? windowId, String? peerId}) async {
debugPrintStack(label: "restoreWindowPosition");
if (bind if (bind
.mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION") .mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION")
.isNotEmpty) { .isNotEmpty) {
@ -2605,3 +2609,109 @@ bool isChooseDisplayToOpenInNewWindow(PeerInfo pi, SessionID sessionId) =>
pi.isSupportMultiDisplay && pi.isSupportMultiDisplay &&
useTextureRender && useTextureRender &&
bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y'; bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y';
Future<List<Rect>> getScreenListWayland() async {
final screenRectList = <Rect>[];
if (isMainDesktopWindow) {
for (var screen in await window_size.getScreenList()) {
final scale = kIgnoreDpi ? 1.0 : screen.scaleFactor;
double l = screen.frame.left;
double t = screen.frame.top;
double r = screen.frame.right;
double b = screen.frame.bottom;
final rect = Rect.fromLTRB(l / scale, t / scale, r / scale, b / scale);
screenRectList.add(rect);
}
} else {
final screenList = await rustDeskWinManager.call(
WindowType.Main, kWindowGetScreenList, '');
try {
for (var screen in jsonDecode(screenList.result) as List<dynamic>) {
final scale = kIgnoreDpi ? 1.0 : screen['scaleFactor'];
double l = screen['frame']['l'];
double t = screen['frame']['t'];
double r = screen['frame']['r'];
double b = screen['frame']['b'];
final rect = Rect.fromLTRB(l / scale, t / scale, r / scale, b / scale);
screenRectList.add(rect);
}
} catch (e) {
debugPrint('Failed to parse screenList: $e');
}
}
return screenRectList;
}
Future<List<Rect>> getScreenListNotWayland() async {
final screenRectList = <Rect>[];
final displays = bind.mainGetDisplays();
if (displays.isEmpty) {
return screenRectList;
}
try {
for (var display in jsonDecode(displays) as List<dynamic>) {
// to-do: scale factor ?
// final scale = kIgnoreDpi ? 1.0 : screen.scaleFactor;
double l = display['x'].toDouble();
double t = display['y'].toDouble();
double r = (display['x'] + display['w']).toDouble();
double b = (display['y'] + display['h']).toDouble();
screenRectList.add(Rect.fromLTRB(l, t, r, b));
}
} catch (e) {
debugPrint('Failed to parse displays: $e');
}
return screenRectList;
}
Future<List<Rect>> getScreenRectList() async {
return bind.mainCurrentIsWayland()
? await getScreenListWayland()
: await getScreenListNotWayland();
}
openMonitorInTheSameTab(int i, FFI ffi, PeerInfo pi) {
final displays = i == kAllDisplayValue
? List.generate(pi.displays.length, (index) => index)
: [i];
bind.sessionSwitchDisplay(
sessionId: ffi.sessionId, value: Int32List.fromList(displays));
ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, ffi.id);
}
// Open new tab or window to show this monitor.
// For now just open new window.
//
// screenRect is used to move the new window to the specified screen and set fullscreen.
openMonitorInNewTabOrWindow(int i, String peerId, PeerInfo pi,
{Rect? screenRect}) {
final args = {
'window_id': stateGlobal.windowId,
'peer_id': peerId,
'display': i,
'display_count': pi.displays.length,
};
if (screenRect != null) {
args['screen_rect'] = {
'l': screenRect.left,
't': screenRect.top,
'r': screenRect.right,
'b': screenRect.bottom,
};
}
DesktopMultiWindow.invokeMethod(
kMainWindowId, kWindowEventOpenMonitorSession, jsonEncode(args));
}
tryMoveToScreenAndSetFullscreen(Rect? screenRect) async {
if (screenRect == null) {
return;
}
final frame =
Rect.fromLTWH(screenRect.left + 30, screenRect.top + 30, 600, 400);
await WindowController.fromWindowId(stateGlobal.windowId).setFrame(frame);
// An duration is needed to avoid the window being restored after fullscreen.
Future.delayed(Duration(milliseconds: 300), () async {
stateGlobal.setFullscreen(true);
});
}

View File

@ -29,6 +29,7 @@ const String kAppTypeDesktopPortForward = "port forward";
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 kWindowGetScreenList = "get_screen_list";
const String kWindowDisableGrabKeyboard = "disable_grab_keyboard"; const String kWindowDisableGrabKeyboard = "disable_grab_keyboard";
const String kWindowActionRebuild = "rebuild"; const String kWindowActionRebuild = "rebuild";
const String kWindowEventHide = "hide"; const String kWindowEventHide = "hide";
@ -65,6 +66,7 @@ const String kPointerEventKindTouch = "touch";
const String kPointerEventKindMouse = "mouse"; const String kPointerEventKindMouse = "mouse";
const String kKeyShowDisplaysAsIndividualWindows = 'displays_as_individual_windows'; const String kKeyShowDisplaysAsIndividualWindows = 'displays_as_individual_windows';
const String kKeyUseAllMyMonitorsWhenConnecting = 'use_all_my_monitors_when_connecting';
const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar'; const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
// the executable name of the portable version // the executable name of the portable version

View File

@ -329,8 +329,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
"Click to download", () async { "Click to download", () async {
final Uri url = Uri.parse('https://rustdesk.com/download'); final Uri url = Uri.parse('https://rustdesk.com/download');
await launchUrl(url); await launchUrl(url);
}, }, closeButton: true);
closeButton: true);
} }
if (systemError.isNotEmpty) { if (systemError.isNotEmpty) {
return buildInstallCard("", systemError, "", () {}); return buildInstallCard("", systemError, "", () {});
@ -397,7 +396,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Widget buildInstallCard(String title, String content, String btnText, Widget buildInstallCard(String title, String content, String btnText,
GestureTapCallback onPressed, GestureTapCallback onPressed,
{String? help, String? link, bool? closeButton}) { {String? help, String? link, bool? closeButton}) {
void closeCard() { void closeCard() {
setState(() { setState(() {
isCardClosed = true; isCardClosed = true;
@ -555,17 +553,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Get.put<RxBool>(svcStopped, tag: 'stop-service'); Get.put<RxBool>(svcStopped, tag: 'stop-service');
rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged); rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async { screenToMap(window_size.Screen screen) => {
debugPrint(
"[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId");
if (call.method == kWindowMainWindowOnTop) {
windowOnTop(null);
} else if (call.method == kWindowGetWindowInfo) {
final screen = (await window_size.getWindowInfo()).screen;
if (screen == null) {
return "";
} else {
return jsonEncode({
'frame': { 'frame': {
'l': screen.frame.left, 'l': screen.frame.left,
't': screen.frame.top, 't': screen.frame.top,
@ -579,8 +567,23 @@ class _DesktopHomePageState extends State<DesktopHomePage>
'b': screen.visibleFrame.bottom, 'b': screen.visibleFrame.bottom,
}, },
'scaleFactor': screen.scaleFactor, 'scaleFactor': screen.scaleFactor,
}); };
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
debugPrint(
"[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId");
if (call.method == kWindowMainWindowOnTop) {
windowOnTop(null);
} else if (call.method == kWindowGetWindowInfo) {
final screen = (await window_size.getWindowInfo()).screen;
if (screen == null) {
return '';
} else {
return jsonEncode(screenToMap(screen));
} }
} else if (call.method == kWindowGetScreenList) {
return jsonEncode(
(await window_size.getScreenList()).map(screenToMap).toList());
} else if (call.method == kWindowActionRebuild) { } else if (call.method == kWindowActionRebuild) {
reloadCurrentWindow(); reloadCurrentWindow();
} else if (call.method == kWindowEventShow) { } else if (call.method == kWindowEventShow) {
@ -613,8 +616,9 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final peerId = args['peer_id'] as String; final peerId = args['peer_id'] as String;
final display = args['display'] as int; final display = args['display'] as int;
final displayCount = args['display_count'] as int; final displayCount = args['display_count'] as int;
final screenRect = args['screen_rect'];
await rustDeskWinManager.openMonitorSession( await rustDeskWinManager.openMonitorSession(
windowId, peerId, display, displayCount); windowId, peerId, display, displayCount, screenRect);
} }
}); });
_uniLinksSubscription = listenUniLinks(); _uniLinksSubscription = listenUniLinks();

View File

@ -1324,6 +1324,8 @@ class _DisplayState extends State<_Display> {
if (useTextureRender) { if (useTextureRender) {
children.add(otherRow('Show displays as individual windows', children.add(otherRow('Show displays as individual windows',
kKeyShowDisplaysAsIndividualWindows)); kKeyShowDisplaysAsIndividualWindows));
children.add(otherRow('Use all my displays when connecting',
kKeyUseAllMyMonitorsWhenConnecting));
} }
return _Card(title: 'Other Default Options', children: children); return _Card(title: 'Other Default Options', children: children);
} }

View File

@ -48,6 +48,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
late ToolbarState _toolbarState; late ToolbarState _toolbarState;
String? peerId; String? peerId;
bool isScreenRectSet = false;
var connectionMap = RxList<Widget>.empty(growable: true); var connectionMap = RxList<Widget>.empty(growable: true);
@ -59,6 +60,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
final tabWindowId = params['tab_window_id']; final tabWindowId = params['tab_window_id'];
final display = params['display']; final display = params['display'];
final displays = params['displays']; final displays = params['displays'];
final screenRect = parseScreenRect(params);
isScreenRectSet = screenRect != null;
tryMoveToScreenAndSetFullscreen(screenRect);
if (peerId != null) { if (peerId != null) {
ConnectionTypeState.init(peerId!); ConnectionTypeState.init(peerId!);
tabController.onSelected = (id) { tabController.onSelected = (id) {
@ -95,6 +99,18 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
} }
} }
parseScreenRect(Map<String, dynamic> params) {
Rect? screenRect;
if (params['screen_rect'] != null) {
double l = params['screen_rect']['l'];
double t = params['screen_rect']['t'];
double r = params['screen_rect']['r'];
double b = params['screen_rect']['b'];
screenRect = Rect.fromLTRB(l, t, r, b);
}
return screenRect;
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -115,7 +131,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
final tabWindowId = args['tab_window_id']; final tabWindowId = args['tab_window_id'];
final display = args['display']; final display = args['display'];
final displays = args['displays']; final displays = args['displays'];
final screenRect = parseScreenRect(args);
windowOnTop(windowId()); windowOnTop(windowId());
tryMoveToScreenAndSetFullscreen(screenRect);
if (tabController.length == 0) { if (tabController.length == 0) {
if (Platform.isMacOS && stateGlobal.closeOnFullscreen) { if (Platform.isMacOS && stateGlobal.closeOnFullscreen) {
stateGlobal.setFullscreen(true); stateGlobal.setFullscreen(true);
@ -196,6 +214,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
_update_remote_count(); _update_remote_count();
return returnValue; return returnValue;
}); });
if (!isScreenRectSet) {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
restoreWindowPosition( restoreWindowPosition(
WindowType.RemoteDesktop, WindowType.RemoteDesktop,
@ -206,6 +225,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
); );
}); });
} }
}
@override @override
void dispose() { void dispose() {
@ -451,6 +471,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
c++; c++;
} }
} }
loopCloseWindow(); loopCloseWindow();
} }
ConnectionTypeState.delete(id); ConnectionTypeState.delete(id);

View File

@ -1,12 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/widgets/toolbar.dart'; import 'package:flutter_hbb/common/widgets/toolbar.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_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_hbb/models/desktop_render_texture.dart';
@ -744,42 +742,14 @@ class _MonitorMenu extends StatelessWidget {
); );
} }
// Open new tab or window to show this monitor.
// For now just open new window.
openMonitorInNewTabOrWindow(int i, PeerInfo pi) {
if (kWindowId == null) {
// unreachable
debugPrint('openMonitorInNewTabOrWindow, unreachable! kWindowId is null');
return;
}
DesktopMultiWindow.invokeMethod(
kMainWindowId,
kWindowEventOpenMonitorSession,
jsonEncode({
'window_id': kWindowId!,
'peer_id': ffi.id,
'display': i,
'display_count': pi.displays.length,
}));
}
openMonitorInTheSameTab(int i, PeerInfo pi) {
final displays = i == kAllDisplayValue
? List.generate(pi.displays.length, (index) => index)
: [i];
bind.sessionSwitchDisplay(
sessionId: ffi.sessionId, value: Int32List.fromList(displays));
ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, id);
}
onPressed(int i, PeerInfo pi) { onPressed(int i, PeerInfo pi) {
_menuDismissCallback(ffi); _menuDismissCallback(ffi);
RxInt display = CurrentDisplayState.find(id); RxInt display = CurrentDisplayState.find(id);
if (display.value != i) { if (display.value != i) {
if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) { if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) {
openMonitorInNewTabOrWindow(i, pi); openMonitorInNewTabOrWindow(i, ffi.id, pi);
} else { } else {
openMonitorInTheSameTab(i, pi); openMonitorInTheSameTab(i, ffi, pi);
} }
} }
} }

View File

@ -198,8 +198,11 @@ void runMultiWindow(
} }
switch (appType) { switch (appType) {
case kAppTypeDesktopRemote: case kAppTypeDesktopRemote:
// If screen rect is set, the window will be moved to the target screen and then set fullscreen.
if (argument['screen_rect'] == null) {
await restoreWindowPosition(WindowType.RemoteDesktop, await restoreWindowPosition(WindowType.RemoteDesktop,
windowId: kWindowId!, peerId: argument['id'] as String?); windowId: kWindowId!, peerId: argument['id'] as String?);
}
break; break;
case kAppTypeDesktopFileTransfer: case kAppTypeDesktopFileTransfer:
await restoreWindowPosition(WindowType.FileTransfer, await restoreWindowPosition(WindowType.FileTransfer,

View File

@ -227,7 +227,7 @@ class FfiModel with ChangeNotifier {
}, sessionId, peerId); }, sessionId, peerId);
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId); updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
setConnectionType(peerId, data.secure, data.direct); setConnectionType(peerId, data.secure, data.direct);
await handlePeerInfo(data.peerInfo, peerId); await handlePeerInfo(data.peerInfo, peerId, true);
for (final element in data.cursorDataList) { for (final element in data.cursorDataList) {
updateLastCursorId(element); updateLastCursorId(element);
await handleCursorData(element); await handleCursorData(element);
@ -245,7 +245,7 @@ class FfiModel with ChangeNotifier {
if (name == 'msgbox') { if (name == 'msgbox') {
handleMsgBox(evt, sessionId, peerId); handleMsgBox(evt, sessionId, peerId);
} else if (name == 'peer_info') { } else if (name == 'peer_info') {
handlePeerInfo(evt, peerId); handlePeerInfo(evt, peerId, false);
} else if (name == 'sync_peer_info') { } else if (name == 'sync_peer_info') {
handleSyncPeerInfo(evt, sessionId, peerId); handleSyncPeerInfo(evt, sessionId, peerId);
} else if (name == 'connection_ready') { } else if (name == 'connection_ready') {
@ -623,7 +623,7 @@ class FfiModel with ChangeNotifier {
} }
/// Handle the peer info event based on [evt]. /// Handle the peer info event based on [evt].
handlePeerInfo(Map<String, dynamic> evt, String peerId) async { handlePeerInfo(Map<String, dynamic> evt, String peerId, bool isCache) async {
cachedPeerData.peerInfo = evt; cachedPeerData.peerInfo = evt;
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs) // recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
@ -703,6 +703,51 @@ class FfiModel with ChangeNotifier {
stateGlobal.resetLastResolutionGroupValues(peerId); stateGlobal.resetLastResolutionGroupValues(peerId);
notifyListeners(); notifyListeners();
if (!isCache) {
tryUseAllMyDisplaysWhenConnecting(peerId);
}
}
tryUseAllMyDisplaysWhenConnecting(String peerId) async {
if (bind.mainGetUserDefaultOption(
key: kKeyUseAllMyMonitorsWhenConnecting) !=
'Y') {
return;
}
if (!_pi.isSupportMultiDisplay || _pi.displays.length <= 1) {
return;
}
final screenRectList = await getScreenRectList();
if (screenRectList.length <= 1) {
return;
}
// to-do: peer currentDisplay is the primary display, but the primary display may not be the first display.
// local primary display also may not be the first display.
//
// 0 is assumed to be the primary display here, for now.
// move to the first display and set fullscreen
bind.sessionSwitchDisplay(
sessionId: sessionId, value: Int32List.fromList([0]));
_pi.currentDisplay = 0;
try {
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
} catch (e) {
//
}
await tryMoveToScreenAndSetFullscreen(screenRectList[0]);
final length = _pi.displays.length < screenRectList.length
? _pi.displays.length
: screenRectList.length;
for (var i = 1; i < length; i++) {
openMonitorInNewTabOrWindow(i, peerId, _pi,
screenRect: screenRectList[i]);
}
} }
tryShowAndroidActionsOverlay({int delayMSecs = 10}) { tryShowAndroidActionsOverlay({int delayMSecs = 10}) {

View File

@ -64,13 +64,14 @@ class RustDeskMultiWindowManager {
peerId, peerId,
_remoteDesktopWindows, _remoteDesktopWindows,
jsonEncode(params), jsonEncode(params),
false,
); );
} }
// This function must be called in the main window thread. // This function must be called in the main window thread.
// Because the _remoteDesktopWindows is managed in that thread. // Because the _remoteDesktopWindows is managed in that thread.
openMonitorSession( openMonitorSession(int windowId, String peerId, int display, int displayCount,
int windowId, String peerId, int display, int displayCount) async { dynamic screenRect) async {
if (_remoteDesktopWindows.length > 1) { if (_remoteDesktopWindows.length > 1) {
for (final windowId in _remoteDesktopWindows) { for (final windowId in _remoteDesktopWindows) {
if (await DesktopMultiWindow.invokeMethod( if (await DesktopMultiWindow.invokeMethod(
@ -94,6 +95,7 @@ class RustDeskMultiWindowManager {
'tab_window_id': windowId, 'tab_window_id': windowId,
'display': display, 'display': display,
'displays': displays, 'displays': displays,
'screen_rect': screenRect,
}; };
await _newSession( await _newSession(
false, false,
@ -102,21 +104,34 @@ class RustDeskMultiWindowManager {
peerId, peerId,
_remoteDesktopWindows, _remoteDesktopWindows,
jsonEncode(params), jsonEncode(params),
screenRect != null,
); );
} }
Future<int> newSessionWindow( Future<int> newSessionWindow(
WindowType type, String remoteId, String msg, List<int> windows) async { WindowType type,
String remoteId,
String msg,
List<int> windows,
bool withScreenRect,
) async {
final windowController = await DesktopMultiWindow.createWindow(msg); final windowController = await DesktopMultiWindow.createWindow(msg);
final windowId = windowController.windowId; final windowId = windowController.windowId;
if (!withScreenRect) {
windowController windowController
..setFrame( ..setFrame(const Offset(0, 0) &
const Offset(0, 0) & Size(1280 + windowId * 20, 720 + windowId * 20)) Size(1280 + windowId * 20, 720 + windowId * 20))
..center() ..center()
..setTitle(getWindowNameWithId( ..setTitle(getWindowNameWithId(
remoteId, remoteId,
overrideType: type, overrideType: type,
)); ));
} else {
windowController.setTitle(getWindowNameWithId(
remoteId,
overrideType: type,
));
}
if (Platform.isMacOS) { if (Platform.isMacOS) {
Future.microtask(() => windowController.show()); Future.microtask(() => windowController.show());
} }
@ -132,10 +147,12 @@ class RustDeskMultiWindowManager {
String remoteId, String remoteId,
List<int> windows, List<int> windows,
String msg, String msg,
bool withScreenRect,
) async { ) async {
if (openInTabs) { if (openInTabs) {
if (windows.isEmpty) { if (windows.isEmpty) {
final windowId = await newSessionWindow(type, remoteId, msg, windows); final windowId = await newSessionWindow(
type, remoteId, msg, windows, withScreenRect);
return MultiWindowCallResult(windowId, null); return MultiWindowCallResult(windowId, null);
} else { } else {
return call(type, methodName, msg); return call(type, methodName, msg);
@ -153,7 +170,8 @@ class RustDeskMultiWindowManager {
} }
} }
} }
final windowId = await newSessionWindow(type, remoteId, msg, windows); final windowId =
await newSessionWindow(type, remoteId, msg, windows, withScreenRect);
return MultiWindowCallResult(windowId, null); return MultiWindowCallResult(windowId, null);
} }
} }
@ -195,7 +213,8 @@ class RustDeskMultiWindowManager {
} }
} }
return _newSession(openInTabs, type, methodName, remoteId, windows, msg); return _newSession(
openInTabs, type, methodName, remoteId, windows, msg, false);
} }
Future<MultiWindowCallResult> newRemoteDesktop( Future<MultiWindowCallResult> newRemoteDesktop(

View File

@ -290,6 +290,12 @@ pub struct PeerConfig {
skip_serializing_if = "String::is_empty" skip_serializing_if = "String::is_empty"
)] )]
pub displays_as_individual_windows: String, pub displays_as_individual_windows: String,
#[serde(
default = "PeerConfig::default_use_all_my_monitors_when_connecting",
deserialize_with = "PeerConfig::deserialize_use_all_my_monitors_when_connecting",
skip_serializing_if = "String::is_empty"
)]
pub use_all_my_monitors_when_connecting: String,
#[serde( #[serde(
default, default,
@ -335,6 +341,8 @@ impl Default for PeerConfig {
view_only: Default::default(), view_only: Default::default(),
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(), reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
displays_as_individual_windows: Self::default_displays_as_individual_windows(), displays_as_individual_windows: Self::default_displays_as_individual_windows(),
use_all_my_monitors_when_connecting: Self::default_use_all_my_monitors_when_connecting(
),
custom_resolutions: Default::default(), custom_resolutions: Default::default(),
options: Self::default_options(), options: Self::default_options(),
ui_flutter: Default::default(), ui_flutter: Default::default(),
@ -1156,6 +1164,11 @@ impl PeerConfig {
deserialize_displays_as_individual_windows, deserialize_displays_as_individual_windows,
UserDefaultConfig::read().get("displays_as_individual_windows") UserDefaultConfig::read().get("displays_as_individual_windows")
); );
serde_field_string!(
default_use_all_my_monitors_when_connecting,
deserialize_use_all_my_monitors_when_connecting,
UserDefaultConfig::read().get("use_all_my_monitors_when_connecting")
);
fn default_custom_image_quality() -> Vec<i32> { fn default_custom_image_quality() -> Vec<i32> {
let f: f64 = UserDefaultConfig::read() let f: f64 = UserDefaultConfig::read()

View File

@ -1207,7 +1207,7 @@ impl LoginConfigHandler {
self.save_config(config); self.save_config(config);
} }
/// Save reverse mouse wheel ("", "Y") to the current config. /// Save "displays_as_individual_windows" ("", "Y") to the current config.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -1218,6 +1218,17 @@ impl LoginConfigHandler {
self.save_config(config); self.save_config(config);
} }
/// Save "use_all_my_monitors_when_connecting" ("", "Y") to the current config.
///
/// # Arguments
///
/// * `value` - The "use_all_my_monitors_when_connecting" value ("", "Y").
pub fn save_use_all_my_monitors_when_connecting(&mut self, value: String) {
let mut config = self.load_config();
config.use_all_my_monitors_when_connecting = value;
self.save_config(config);
}
/// Save scroll style to the current config. /// Save scroll style to the current config.
/// ///
/// # Arguments /// # Arguments

View File

@ -339,7 +339,9 @@ pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
} }
} }
pub fn session_get_displays_as_individual_windows(session_id: SessionID) -> SyncReturn<Option<String>> { pub fn session_get_displays_as_individual_windows(
session_id: SessionID,
) -> SyncReturn<Option<String>> {
if let Some(session) = sessions::get_session_by_session_id(&session_id) { if let Some(session) = sessions::get_session_by_session_id(&session_id) {
SyncReturn(Some(session.get_displays_as_individual_windows())) SyncReturn(Some(session.get_displays_as_individual_windows()))
} else { } else {
@ -353,6 +355,22 @@ pub fn session_set_displays_as_individual_windows(session_id: SessionID, value:
} }
} }
pub fn session_get_use_all_my_monitors_when_connecting(
session_id: SessionID,
) -> SyncReturn<Option<String>> {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
SyncReturn(Some(session.get_use_all_my_monitors_when_connecting()))
} else {
SyncReturn(None)
}
}
pub fn session_set_use_all_my_monitors_when_connecting(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_use_all_my_monitors_when_connecting(value);
}
}
pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32>> { pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32>> {
if let Some(session) = sessions::get_session_by_session_id(&session_id) { if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_custom_image_quality()) Some(session.get_custom_image_quality())
@ -1069,6 +1087,29 @@ pub fn main_get_main_display() -> SyncReturn<String> {
SyncReturn(display_info) SyncReturn(display_info)
} }
pub fn main_get_displays() -> SyncReturn<String> {
#[cfg(target_os = "ios")]
let display_info = "".to_owned();
#[cfg(not(target_os = "ios"))]
let mut display_info = "".to_owned();
#[cfg(not(target_os = "ios"))]
if let Ok(displays) = crate::display_service::try_get_displays() {
let displays = displays
.iter()
.map(|d| {
HashMap::from([
("x", d.origin().0),
("y", d.origin().1),
("w", d.width() as i32),
("h", d.height() as i32),
])
})
.collect::<Vec<_>>();
display_info = serde_json::to_string(&displays).unwrap_or_default();
}
SyncReturn(display_info)
}
pub fn session_add_port_forward( pub fn session_add_port_forward(
session_id: SessionID, session_id: SessionID,
local_port: i32, local_port: i32,

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"), ("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"),
("Open in new window", "在新的窗口中打开"), ("Open in new window", "在新的窗口中打开"),
("Show displays as individual windows", "在单个窗口中打开显示器"), ("Show displays as individual windows", "在单个窗口中打开显示器"),
("Use all my displays when connecting", "建立连接时使用我的所有显示器"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", "Pindah ke tampilan utama, pada mode elevasi, pengggunaan lebih dari satu layar tidak diizinkan"), ("elevated_switch_display_msg", "Pindah ke tampilan utama, pada mode elevasi, pengggunaan lebih dari satu layar tidak diizinkan"),
("Open in new window", "Buka di jendela baru"), ("Open in new window", "Buka di jendela baru"),
("Show displays as individual windows", "Tampilkan dengan jendela terpisah"), ("Show displays as individual windows", "Tampilkan dengan jendela terpisah"),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -565,5 +565,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", "Passo allo schermo principale perché in modalità elevata non sono supportati più schermi."), ("elevated_switch_display_msg", "Passo allo schermo principale perché in modalità elevata non sono supportati più schermi."),
("Open in new window", "Apri in una nuova finestra"), ("Open in new window", "Apri in una nuova finestra"),
("Show displays as individual windows", "Visualizza schermi come finestre individuali"), ("Show displays as individual windows", "Visualizza schermi come finestre individuali"),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", "Pārslēdzieties uz primāro displeju, jo paaugstinātajā režīmā netiek atbalstīti vairāki displeji."), ("elevated_switch_display_msg", "Pārslēdzieties uz primāro displeju, jo paaugstinātajā režīmā netiek atbalstīti vairāki displeji."),
("Open in new window", "Atvērt jaunā logā"), ("Open in new window", "Atvērt jaunā logā"),
("Show displays as individual windows", "Rādīt displejus kā atsevišķus logus"), ("Show displays as individual windows", "Rādīt displejus kā atsevišķus logus"),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", "Przełącz się na ekran główny, ponieważ wyświetlanie kilku ekranów nie jest obsługiwane przy podniesionych uprawnieniach."), ("elevated_switch_display_msg", "Przełącz się na ekran główny, ponieważ wyświetlanie kilku ekranów nie jest obsługiwane przy podniesionych uprawnieniach."),
("Open in new window", "Otwórz w nowym oknie"), ("Open in new window", "Otwórz w nowym oknie"),
("Show displays as individual windows", "Pokaż ekrany w osobnych oknach"), ("Show displays as individual windows", "Pokaż ekrany w osobnych oknach"),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -564,5 +564,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_switch_display_msg", ""), ("elevated_switch_display_msg", ""),
("Open in new window", ""), ("Open in new window", ""),
("Show displays as individual windows", ""), ("Show displays as individual windows", ""),
("Use all my displays when connecting", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -240,6 +240,10 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.read().unwrap().displays_as_individual_windows.clone() self.lc.read().unwrap().displays_as_individual_windows.clone()
} }
pub fn get_use_all_my_monitors_when_connecting(&self) -> String {
self.lc.read().unwrap().use_all_my_monitors_when_connecting.clone()
}
pub fn save_reverse_mouse_wheel(&self, value: String) { pub fn save_reverse_mouse_wheel(&self, value: String) {
self.lc.write().unwrap().save_reverse_mouse_wheel(value); self.lc.write().unwrap().save_reverse_mouse_wheel(value);
} }
@ -248,6 +252,10 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.write().unwrap().save_displays_as_individual_windows(value); self.lc.write().unwrap().save_displays_as_individual_windows(value);
} }
pub fn save_use_all_my_monitors_when_connecting(&self, value: String) {
self.lc.write().unwrap().save_use_all_my_monitors_when_connecting(value);
}
pub fn save_view_style(&self, value: String) { pub fn save_view_style(&self, value: String) {
self.lc.write().unwrap().save_view_style(value); self.lc.write().unwrap().save_view_style(value);
} }