feat, multi_flutter_ui_sessions

Signed-off-by: dignow <linlong1265@gmail.com>
This commit is contained in:
dignow
2023-10-08 21:44:54 +08:00
parent 5e616dd502
commit 013d307bcd
83 changed files with 2954 additions and 1319 deletions

View File

@@ -1054,7 +1054,7 @@ Widget msgboxIcon(String type) {
if (type == 'on-uac' || type == 'on-foreground-elevated') {
iconData = Icons.admin_panel_settings;
}
if (type == "info") {
if (type.contains('info')) {
iconData = Icons.info;
}
if (iconData != null) {
@@ -2317,7 +2317,7 @@ Widget dialogButton(String text,
}
}
int version_cmp(String v1, String v2) {
int versionCmp(String v1, String v2) {
return bind.versionToNumber(v: v1) - bind.versionToNumber(v: v2);
}
@@ -2589,3 +2589,25 @@ String getDesktopTabLabel(String peerId, String alias) {
}
return label;
}
String getChooseDisplayBehavior() {
var current = bind.mainGetOptionSync(key: kKeyChooseDisplayBehavior);
if (![kChooseDisplayBehaviorSwitch, kChooseDisplayBehaviorOpen]
.contains(current)) {
current = kChooseDisplayBehaviorOpen;
}
return current;
}
sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async {
if (pi.currentDisplay == kAllDisplayValue) {
for (int i = 0; i < pi.displays.length; i++) {
await bind.sessionRefresh(sessionId: sessionId, display: i);
}
} else {
await bind.sessionRefresh(sessionId: sessionId, display: pi.currentDisplay);
}
}
bool get isChooseDisplayToOpen =>
getChooseDisplayBehavior() != kChooseDisplayBehaviorSwitch;

View File

@@ -1295,7 +1295,7 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
} catch (_) {}
bool notShowFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
final content = customImageQualityWidget(
initQuality: qualityInitValue,

View File

@@ -90,7 +90,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
v.add(
TTextMenu(
child: Row(children: [
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')),
Offstage(
offstage: isDesktop,
child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12),
@@ -99,13 +99,13 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
trailingIcon: Transform.scale(
scale: 0.8,
child: InkWell(
onTap: () => pi.is_headless
onTap: () => pi.isHeadless
? showSetOSAccount(sessionId, ffi.dialogManager)
: handleOsPasswordEditIcon(sessionId, ffi.dialogManager),
child: Icon(Icons.edit),
),
),
onPressed: () => pi.is_headless
onPressed: () => pi.isHeadless
? showSetOSAccount(sessionId, ffi.dialogManager)
: handleOsPasswordAction(sessionId, ffi.dialogManager),
),
@@ -208,7 +208,8 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
ffiModel.keyboard &&
pi.platform != kPeerPlatformAndroid &&
pi.platform != kPeerPlatformMacOS &&
version_cmp(pi.version, '1.2.0') >= 0) {
versionCmp(pi.version, '1.2.0') >= 0 &&
bind.peerGetDefaultSessionsCount(id: id) == 1) {
v.add(TTextMenu(
child: Text(translate('Switch Sides')),
onPressed: () =>
@@ -217,8 +218,9 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
// refresh
if (pi.version.isNotEmpty) {
v.add(TTextMenu(
child: Text(translate('Refresh')),
onPressed: () => bind.sessionRefresh(sessionId: sessionId)));
child: Text(translate('Refresh')),
onPressed: () => sessionRefreshVideo(sessionId, pi),
));
}
// record
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
@@ -377,7 +379,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
// show remote cursor
if (pi.platform != kPeerPlatformAndroid &&
!ffi.canvasModel.cursorEmbedded &&
!pi.is_wayland) {
!pi.isWayland) {
final state = ShowRemoteCursorState.find(id);
final enabled = !ffiModel.viewOnly;
final option = 'show-remote-cursor';
@@ -488,7 +490,8 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
value: rxValue.value,
onChanged: (value) {
if (value == null) return;
if (ffiModel.pi.currentDisplay != 0) {
if (ffiModel.pi.currentDisplay != 0 &&
ffiModel.pi.currentDisplay != kAllDisplayValue) {
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
'Please switch to Display 1 first', '', ffi.dialogManager);
return;

View File

@@ -4,10 +4,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/state_model.dart';
const bool kOpenSamePeerInNewWindow = true;
const double kDesktopRemoteTabBarHeight = 28.0;
const int kInvalidWindowId = -1;
const int kMainWindowId = 0;
const kAllDisplayValue = -1;
const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS";
@@ -37,11 +41,13 @@ 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 kWindowEventActiveDisplaySession = "active_display_session";
const String kWindowEventGetRemoteList = "get_remote_list";
const String kWindowEventGetSessionIdList = "get_session_id_list";
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
const String kWindowEventOpenMonitorSession = "open_monitor_session";
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
const String kOptionOpenInTabs = "allow-open-in-tabs";
@@ -60,6 +66,10 @@ const int kWindowMainId = 0;
const String kPointerEventKindTouch = "touch";
const String kPointerEventKindMouse = "mouse";
const String kKeyChooseDisplayBehavior = 'choose-display-behavior';
const String kChooseDisplayBehaviorSwitch = 'switch';
const String kChooseDisplayBehaviorOpen = 'open';
// the executable name of the portable version
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";

View File

@@ -187,12 +187,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).colorScheme.background,
child: Tooltip(
message: translate('Settings'),
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
)),
message: translate('Settings'),
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
)),
),
),
onHover: (value) => hover.value = value,
@@ -256,27 +256,27 @@ class _DesktopHomePageState extends State<DesktopHomePage>
child: Obx(() => RotatedBox(
quarterTurns: 2,
child: Tooltip(
message: translate('Refresh Password'),
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
))
)),
message: translate('Refresh Password'),
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
)))),
onHover: (value) => refreshHover.value = value,
).marginOnly(right: 8, top: 4),
InkWell(
child: Obx(
() => Tooltip(
message: translate('Change Password'),
child: Icon(
Icons.edit,
color:
editHover.value ? textColor : Color(0xFFDDDDDD),
size: 22,
)).marginOnly(right: 8, top: 4),
message: translate('Change Password'),
child: Icon(
Icons.edit,
color: editHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
)).marginOnly(right: 8, top: 4),
),
onTap: () => DesktopSettingPage.switch2page(1),
onHover: (value) => editHover.value = value,
@@ -604,8 +604,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
debugPrint("Failed to parse window id '${call.arguments}': $e");
}
if (windowId != null) {
await rustDeskWinManager.moveTabToNewWindow(windowId, args[1], args[2]);
await rustDeskWinManager.moveTabToNewWindow(
windowId, args[1], args[2]);
}
} else if (call.method == kWindowEventOpenMonitorSession) {
final args = jsonDecode(call.arguments);
final windowId = args['window_id'] as int;
final peerId = args['peer_id'] as String;
final display = args['display'] as int;
final displayCount = args['display_count'] as int;
await rustDeskWinManager.openMonitorSession(
windowId, peerId, display, displayCount);
}
});
_uniLinksSubscription = listenUniLinks();

View File

@@ -11,6 +11,7 @@ import 'package:flutter_hbb/consts.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/models/platform_model.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/plugin/manager.dart';
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
@@ -268,6 +269,7 @@ class _GeneralState extends State<_General> {
service(),
theme(),
hwcodec(),
chooseDisplay(),
audio(context),
record(context),
_Card(title: 'Language', children: [language()]),
@@ -376,6 +378,29 @@ class _GeneralState extends State<_General> {
);
}
Widget chooseDisplay() {
if (!useTextureRender) return const Offstage();
var current = getChooseDisplayBehavior();
onChanged(String value) {
bind.mainSetOption(key: kKeyChooseDisplayBehavior, value: value);
setState(() {});
}
return _Card(title: 'Choose Display Behavior', children: [
_Radio<String>(context,
value: kChooseDisplayBehaviorSwitch,
groupValue: current,
label: 'Switch Display',
onChanged: onChanged),
_Radio<String>(context,
value: kChooseDisplayBehaviorOpen,
groupValue: current,
label: 'Open in New Window',
onChanged: onChanged),
]);
}
Widget audio(BuildContext context) {
String getDefault() {
if (Platform.isWindows) return translate('System Sound');

View File

@@ -28,6 +28,7 @@ import '../widgets/tabbar_widget.dart';
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
// Used to skip session close if "move to new window" is clicked.
final Map<String, bool> closeSessionOnDispose = {};
class RemotePage extends StatefulWidget {
@@ -36,6 +37,8 @@ class RemotePage extends StatefulWidget {
required this.id,
required this.sessionId,
required this.tabWindowId,
required this.display,
required this.displays,
required this.password,
required this.toolbarState,
required this.tabController,
@@ -46,6 +49,8 @@ class RemotePage extends StatefulWidget {
final String id;
final SessionID? sessionId;
final int? tabWindowId;
final int? display;
final List<int>? displays;
final String? password;
final ToolbarState toolbarState;
final String? switchUuid;
@@ -73,7 +78,7 @@ class _RemotePageState extends State<RemotePage>
late RxBool _zoomCursor;
late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled;
late RenderTexture _renderTexture;
final Map<int, RenderTexture> _renderTextures = {};
final _blockableOverlayState = BlockableOverlayState();
@@ -109,6 +114,8 @@ class _RemotePageState extends State<RemotePage>
switchUuid: widget.switchUuid,
forceRelay: widget.forceRelay,
tabWindowId: widget.tabWindowId,
display: widget.display,
displays: widget.displays,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
@@ -118,9 +125,6 @@ class _RemotePageState extends State<RemotePage>
if (!Platform.isLinux) {
Wakelock.enable();
}
// Register texture.
_renderTexture = RenderTexture();
_renderTexture.create(sessionId);
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
@@ -207,7 +211,9 @@ class _RemotePageState extends State<RemotePage>
// https://github.com/flutter/flutter/issues/64935
super.dispose();
debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}");
await _renderTexture.destroy(closeSession);
for (final texture in _renderTextures.values) {
await texture.destroy(closeSession);
}
// ensure we leave this session, this is a double check
_ffi.inputModel.enterOrLeave(false);
DesktopMultiWindow.removeListener(this);
@@ -245,6 +251,7 @@ class _RemotePageState extends State<RemotePage>
onEnterOrLeaveImageSetter: (func) =>
_onEnterOrLeaveImage4Toolbar = func,
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Toolbar = null,
setRemoteState: setState,
);
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
@@ -392,6 +399,38 @@ class _RemotePageState extends State<RemotePage>
);
}
Map<int, RenderTexture> _updateGetRenderTextures(int curDisplay) {
tryCreateTexture(int idx) {
if (!_renderTextures.containsKey(idx)) {
final renderTexture = RenderTexture();
_renderTextures[idx] = renderTexture;
renderTexture.create(idx, sessionId);
}
}
tryRemoveTexture(int idx) {
if (_renderTextures.containsKey(idx)) {
_renderTextures[idx]!.destroy(true);
_renderTextures.remove(idx);
}
}
if (curDisplay == kAllDisplayValue) {
final displays = _ffi.ffiModel.pi.getCurDisplays();
for (var i = 0; i < displays.length; i++) {
tryCreateTexture(i);
}
} else {
tryCreateTexture(curDisplay);
for (var i = 0; i < _ffi.ffiModel.pi.displays.length; i++) {
if (i != curDisplay) {
tryRemoveTexture(i);
}
}
}
return _renderTextures;
}
Widget getBodyForDesktop(BuildContext context) {
var paints = <Widget>[
MouseRegion(onEnter: (evt) {
@@ -402,16 +441,20 @@ class _RemotePageState extends State<RemotePage>
Future.delayed(Duration.zero, () {
Provider.of<CanvasModel>(context, listen: false).updateViewStyle();
});
return ImagePaint(
id: widget.id,
zoomCursor: _zoomCursor,
cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved,
textureId: _renderTexture.textureId,
useTextureRender: RenderTexture.useTextureRender,
listenerBuilder: (child) =>
_buildRawTouchAndPointerRegion(child, enterView, leaveView),
final peerDisplay = CurrentDisplayState.find(widget.id);
return Obx(
() => _ffi.ffiModel.pi.isSet.isFalse
? Container(color: Colors.transparent)
: Obx(() => ImagePaint(
id: widget.id,
zoomCursor: _zoomCursor,
cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved,
renderTextures: _updateGetRenderTextures(peerDisplay.value),
listenerBuilder: (child) => _buildRawTouchAndPointerRegion(
child, enterView, leaveView),
)),
);
}))
];
@@ -447,8 +490,7 @@ class ImagePaint extends StatefulWidget {
final RxBool cursorOverImage;
final RxBool keyboardEnabled;
final RxBool remoteCursorMoved;
final RxInt textureId;
final bool useTextureRender;
final Map<int, RenderTexture> renderTextures;
final Widget Function(Widget)? listenerBuilder;
ImagePaint(
@@ -458,8 +500,7 @@ class ImagePaint extends StatefulWidget {
required this.cursorOverImage,
required this.keyboardEnabled,
required this.remoteCursorMoved,
required this.textureId,
required this.useTextureRender,
required this.renderTextures,
this.listenerBuilder})
: super(key: key);
@@ -530,27 +571,13 @@ class _ImagePaintState extends State<ImagePaint> {
});
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
final imageWidth = c.getDisplayWidth() * s;
final imageHeight = c.getDisplayHeight() * s;
final imageSize = Size(imageWidth, imageHeight);
late final Widget imageWidget;
if (widget.useTextureRender) {
imageWidget = SizedBox(
width: imageWidth,
height: imageHeight,
child: Obx(() => Texture(
textureId: widget.textureId.value,
filterQuality:
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
)),
);
} else {
imageWidget = CustomPaint(
size: imageSize,
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
);
}
final paintWidth = c.getDisplayWidth() * s;
final paintHeight = c.getDisplayHeight() * s;
final paintSize = Size(paintWidth, paintHeight);
final paintWidget = useTextureRender
? _BuildPaintTextureRender(
c, s, Offset.zero, paintSize, isViewOriginal())
: _buildScrollbarNonTextureRender(m, paintSize, s);
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
final percentX = _horizontal.hasClients
@@ -570,43 +597,79 @@ class _ImagePaintState extends State<ImagePaint> {
},
child: mouseRegion(
child: Obx(() => _buildCrossScrollbarFromLayout(
context, _buildListener(imageWidget), c.size, imageSize)),
context, _buildListener(paintWidget), c.size, paintSize)),
));
} else {
late final Widget imageWidget;
if (c.size.width > 0 && c.size.height > 0) {
if (widget.useTextureRender) {
final x = Platform.isLinux ? c.x.toInt().toDouble() : c.x;
final y = Platform.isLinux ? c.y.toInt().toDouble() : c.y;
imageWidget = Stack(
children: [
Positioned(
left: x,
top: y,
width: c.getDisplayWidth() * s,
height: c.getDisplayHeight() * s,
child: Texture(
textureId: widget.textureId.value,
filterQuality:
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
final paintWidget = useTextureRender
? _BuildPaintTextureRender(
c,
s,
Offset(
Platform.isLinux ? c.x.toInt().toDouble() : c.x,
Platform.isLinux ? c.y.toInt().toDouble() : c.y,
),
)
],
);
} else {
imageWidget = CustomPaint(
size: Size(c.size.width, c.size.height),
painter:
ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
);
}
return mouseRegion(child: _buildListener(imageWidget));
c.size,
isViewOriginal())
: _buildScrollAuthNonTextureRender(m, c, s);
return mouseRegion(child: _buildListener(paintWidget));
} else {
return Container();
}
}
}
Widget _buildScrollbarNonTextureRender(
ImageModel m, Size imageSize, double s) {
return CustomPaint(
size: imageSize,
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
);
}
Widget _buildScrollAuthNonTextureRender(
ImageModel m, CanvasModel c, double s) {
return CustomPaint(
size: Size(c.size.width, c.size.height),
painter: ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
);
}
Widget _BuildPaintTextureRender(
CanvasModel c, double s, Offset offset, Size size, bool isViewOriginal) {
final ffiModel = c.parent.target!.ffiModel;
final displays = ffiModel.pi.getCurDisplays();
final children = <Widget>[];
final rect = ffiModel.rect;
if (rect == null) {
return Container();
}
final curDisplay = ffiModel.pi.currentDisplay;
for (var i = 0; i < displays.length; i++) {
final textureId = widget
.renderTextures[curDisplay == kAllDisplayValue ? i : curDisplay]
?.textureId;
if (textureId != null) {
children.add(Positioned(
left: (displays[i].x - rect.left) * s + offset.dx,
top: (displays[i].y - rect.top) * s + offset.dy,
width: displays[i].width * s,
height: displays[i].height * s,
child: Obx(() => Texture(
textureId: textureId.value,
filterQuality:
isViewOriginal ? FilterQuality.none : FilterQuality.low,
)),
));
}
}
return SizedBox(
width: size.width,
height: size.height,
child: Stack(children: children),
);
}
MouseCursor _buildCursorOfCache(
CursorModel cursor, double scale, CursorData? cache) {
if (cache == null) {
@@ -731,7 +794,11 @@ class _ImagePaintState extends State<ImagePaint> {
);
}
return widget;
return Container(
child: widget,
width: layoutSize.width,
height: layoutSize.height,
);
}
Widget _buildListener(Widget child) {
@@ -770,9 +837,14 @@ class CursorPaint extends StatelessWidget {
double cy = c.y;
if (c.viewStyle.style == kRemoteViewStyleOriginal &&
c.scrollStyle == ScrollStyle.scrollbar) {
final d = c.parent.target!.ffiModel.display;
final imageWidth = d.width * c.scale;
final imageHeight = d.height * c.scale;
final rect = c.parent.target!.ffiModel.rect;
if (rect == null) {
// unreachable!
debugPrint('unreachable! The displays rect is null.');
return Container();
}
final imageWidth = rect.width * c.scale;
final imageHeight = rect.height * c.scale;
cx = -imageWidth * c.scrollX;
cy = -imageHeight * c.scrollY;
}

View File

@@ -57,6 +57,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
peerId = params['id'];
final sessionId = params['session_id'];
final tabWindowId = params['tab_window_id'];
final display = params['display'];
final displays = params['displays'];
if (peerId != null) {
ConnectionTypeState.init(peerId!);
tabController.onSelected = (id) {
@@ -80,6 +82,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
id: peerId!,
sessionId: sessionId == null ? null : SessionID(sessionId),
tabWindowId: tabWindowId,
display: display,
displays: displays?.cast<int>(),
password: params['password'],
toolbarState: _toolbarState,
tabController: tabController,
@@ -109,6 +113,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
final switchUuid = args['switch_uuid'];
final sessionId = args['session_id'];
final tabWindowId = args['tab_window_id'];
final display = args['display'];
final displays = args['displays'];
windowOnTop(windowId());
if (tabController.length == 0) {
if (Platform.isMacOS && stateGlobal.closeOnFullscreen) {
@@ -129,6 +135,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
id: id,
sessionId: sessionId == null ? null : SessionID(sessionId),
tabWindowId: tabWindowId,
display: display,
displays: displays?.cast<int>(),
password: args['password'],
toolbarState: _toolbarState,
tabController: tabController,
@@ -148,6 +156,15 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
windowOnTop(windowId());
}
return jumpOk;
} else if (call.method == kWindowEventActiveDisplaySession) {
final args = jsonDecode(call.arguments);
final id = args['id'];
final display = args['display'];
final jumpOk = tabController.jumpToByKeyAndDisplay(id, display);
if (jumpOk) {
windowOnTop(windowId());
}
return jumpOk;
} else if (call.method == kWindowEventGetRemoteList) {
return tabController.state.value.tabs
.map((e) => e.key)
@@ -160,18 +177,20 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
.join(';');
} else if (call.method == kWindowEventGetCachedSessionData) {
// Ready to show new window and close old tab.
final peerId = call.arguments;
final args = jsonDecode(call.arguments);
final id = args['id'];
final close = args['close'];
try {
final remotePage = tabController.state.value.tabs
.firstWhere((tab) => tab.key == peerId)
.firstWhere((tab) => tab.key == id)
.page as RemotePage;
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
} catch (e) {
debugPrint('Failed to get cached session data: $e');
}
if (returnValue != null) {
closeSessionOnDispose[peerId] = false;
tabController.closeBy(peerId);
if (close && returnValue != null) {
closeSessionOnDispose[id] = false;
tabController.closeBy(id);
}
}
_update_remote_count();

View File

@@ -1,10 +1,12 @@
import 'dart:convert';
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/state_model.dart';
import 'package:flutter_hbb/consts.dart';
@@ -325,7 +327,8 @@ class RemoteToolbar extends StatefulWidget {
final FFI ffi;
final ToolbarState state;
final Function(Function(bool)) onEnterOrLeaveImageSetter;
final Function() onEnterOrLeaveImageCleaner;
final VoidCallback onEnterOrLeaveImageCleaner;
final Function(VoidCallback) setRemoteState;
RemoteToolbar({
Key? key,
@@ -334,6 +337,7 @@ class RemoteToolbar extends StatefulWidget {
required this.state,
required this.onEnterOrLeaveImageSetter,
required this.onEnterOrLeaveImageCleaner,
required this.setRemoteState,
}) : super(key: key);
@override
@@ -450,13 +454,17 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
}
if (PrivacyModeState.find(widget.id).isFalse && pi.displays.length > 1) {
toolbarItems.add(
bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y'
? _MultiMonitorMenu(id: widget.id, ffi: widget.ffi)
: _MonitorMenu(id: widget.id, ffi: widget.ffi),
);
}
toolbarItems.add(Obx(() {
if (PrivacyModeState.find(widget.id).isFalse &&
pi.displaysCount.value > 1) {
return _MonitorMenu(
id: widget.id,
ffi: widget.ffi,
setRemoteState: widget.setRemoteState);
} else {
return Offstage();
}
}));
toolbarItems
.add(_ControlMenu(id: widget.id, ffi: widget.ffi, state: widget.state));
@@ -581,11 +589,22 @@ class _MobileActionMenu extends StatelessWidget {
class _MonitorMenu extends StatelessWidget {
final String id;
final FFI ffi;
const _MonitorMenu({Key? key, required this.id, required this.ffi})
: super(key: key);
final Function(VoidCallback) setRemoteState;
const _MonitorMenu({
Key? key,
required this.id,
required this.ffi,
required this.setRemoteState,
}) : super(key: key);
bool get showMonitorsToolbar =>
bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y';
@override
Widget build(BuildContext context) {
Widget build(BuildContext context) =>
showMonitorsToolbar ? buildMultiMonitorMenu() : buildMonitorMenu();
Widget buildMonitorMenu() {
return _IconSubmenuButton(
tooltip: 'Select Monitor',
icon: icon(),
@@ -595,7 +614,80 @@ class _MonitorMenu extends StatelessWidget {
menuStyle: MenuStyle(
padding:
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
menuChildren: [Row(children: displays(context))]);
menuChildren: [Row(children: buildMonitorList(false))]);
}
Widget buildMultiMonitorMenu() {
return Row(children: buildMonitorList(true));
}
List<Widget> buildMonitorList(bool isMulti) {
final List<Widget> monitorList = [];
final pi = ffi.ffiModel.pi;
getMonitorText(int i) {
if (i == kAllDisplayValue) {
if (pi.displays.length == 2) {
return '1|2';
} else {
return 'ALL';
}
} else {
return (i + 1).toString();
}
}
buildMonitorButton(int i) => Obx(() {
RxInt display = CurrentDisplayState.find(id);
return _IconMenuButton(
tooltip: isMulti ? '' : '#${i + 1} monitor',
hMargin: isMulti ? null : 6,
vMargin: isMulti ? null : 12,
topLevel: false,
color: i == display.value
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,
hoverColor: i == display.value
? _ToolbarTheme.hoverBlueColor
: _ToolbarTheme.hoverInactiveColor,
icon: Container(
alignment: AlignmentDirectional.center,
constraints:
const BoxConstraints(minHeight: _ToolbarTheme.height),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter:
ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(
() => Text(
getMonitorText(i),
style: TextStyle(
color: i == display.value
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
onPressed: () => onPressed(i, pi),
);
});
for (int i = 0; i < pi.displays.length; i++) {
monitorList.add(buildMonitorButton(i));
}
if (pi.isSupportMultiUiSession && pi.displays.length > 1) {
monitorList.add(buildMonitorButton(kAllDisplayValue));
}
return monitorList;
}
icon() {
@@ -610,7 +702,7 @@ class _MonitorMenu extends StatelessWidget {
Obx(() {
RxInt display = CurrentDisplayState.find(id);
return Text(
'${display.value + 1}/${pi.displays.length}',
'${display.value == kAllDisplayValue ? 'A' : '${display.value + 1}'}/${pi.displays.length}',
style: const TextStyle(
color: _ToolbarTheme.blueColor,
fontSize: 8,
@@ -622,48 +714,44 @@ class _MonitorMenu extends StatelessWidget {
);
}
List<Widget> displays(BuildContext context) {
final List<Widget> rowChildren = [];
final pi = ffi.ffiModel.pi;
for (int i = 0; i < pi.displays.length; i++) {
rowChildren.add(_IconMenuButton(
topLevel: false,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
tooltip: "#${i + 1} monitor",
hMargin: 6,
vMargin: 12,
icon: Container(
alignment: AlignmentDirectional.center,
constraints: const BoxConstraints(minHeight: _ToolbarTheme.height),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Text(
(i + 1).toString(),
style: TextStyle(
color: _ToolbarTheme.blueColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
onPressed: () {
_menuDismissCallback(ffi);
RxInt display = CurrentDisplayState.find(id);
if (display.value != i) {
bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i);
}
},
));
// 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) {
_menuDismissCallback(ffi);
RxInt display = CurrentDisplayState.find(id);
if (display.value != i) {
if (pi.isSupportMultiDisplay) {
openMonitorInNewTabOrWindow(i, pi);
} else {
openMonitorInTheSameTab(i, pi);
}
}
return rowChildren;
}
}
@@ -1044,14 +1132,14 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
Resolution? _localResolution;
late final TextEditingController _customWidth =
TextEditingController(text: display.width.toString());
TextEditingController(text: rect?.width.toInt().toString() ?? '');
late final TextEditingController _customHeight =
TextEditingController(text: display.height.toString());
TextEditingController(text: rect?.height.toInt().toString() ?? '');
FFI get ffi => widget.ffi;
PeerInfo get pi => widget.ffi.ffiModel.pi;
FfiModel get ffiModel => widget.ffi.ffiModel;
Display get display => ffiModel.display;
Rect? get rect => ffiModel.rect;
List<Resolution> get resolutions => pi.resolutions;
bool get isWayland => bind.mainCurrentIsWayland();
@@ -1063,12 +1151,12 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
@override
Widget build(BuildContext context) {
final isVirtualDisplay = display.isVirtualDisplayResolution;
final isVirtualDisplay = ffiModel.isVirtualDisplayResolution;
final visible =
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
if (!visible) return Offstage();
final showOriginalBtn =
display.isOriginalResolutionSet && !display.isOriginalResolution;
ffiModel.isOriginalResolutionSet && !ffiModel.isOriginalResolution;
final showFitLocalBtn = !_isRemoteResolutionFitLocal();
_setGroupValue();
return _SubmenuButton(
@@ -1085,12 +1173,15 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
}
_setGroupValue() {
if (pi.currentDisplay == kAllDisplayValue) {
return;
}
final lastGroupValue =
stateGlobal.getLastResolutionGroupValue(widget.id, pi.currentDisplay);
if (lastGroupValue == _kCustomResolutionValue) {
_groupValue = _kCustomResolutionValue;
} else {
_groupValue = '${display.width}x${display.height}';
_groupValue = '${rect?.width.toInt()}x${rect?.height.toInt()}';
}
}
@@ -1118,20 +1209,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
_getLocalResolution() {
_localResolution = null;
final String currentDisplay = bind.mainGetCurrentDisplay();
if (currentDisplay.isNotEmpty) {
final String mainDisplay = bind.mainGetMainDisplay();
if (mainDisplay.isNotEmpty) {
try {
final display = json.decode(currentDisplay);
final display = json.decode(mainDisplay);
if (display['w'] != null && display['h'] != null) {
_localResolution = Resolution(display['w'], display['h']);
}
} catch (e) {
debugPrint('Failed to decode $currentDisplay, $e');
debugPrint('Failed to decode $mainDisplay, $e');
}
}
}
_onChanged(BuildContext context, String? value) async {
if (pi.currentDisplay == kAllDisplayValue) {
return;
}
stateGlobal.setLastResolutionGroupValue(
widget.id, pi.currentDisplay, value);
if (value == null) return;
@@ -1150,13 +1244,16 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
}
if (w != null && h != null) {
if (w != display.width || h != display.height) {
if (w != rect?.width.toInt() || h != rect?.height.toInt()) {
await _changeResolution(context, w, h);
}
}
}
_changeResolution(BuildContext context, int w, int h) async {
if (pi.currentDisplay == kAllDisplayValue) {
return;
}
await bind.sessionChangeResolution(
sessionId: ffi.sessionId,
display: pi.currentDisplay,
@@ -1164,8 +1261,11 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
height: h,
);
Future.delayed(Duration(seconds: 3), () async {
final display = ffiModel.display;
if (w == display.width && h == display.height) {
final rect = ffiModel.rect;
if (rect == null) {
return;
}
if (w == rect.width.toInt() && h == rect.height.toInt()) {
if (await widget.screenAdjustor.isWindowCanBeAdjusted()) {
widget.screenAdjustor.doAdjustWindow(context);
}
@@ -1175,6 +1275,10 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
Widget _OriginalResolutionMenuButton(
BuildContext context, bool showOriginalBtn) {
final display = pi.tryGetDisplayIfNotAllDisplay();
if (display == null) {
return Offstage();
}
return Offstage(
offstage: !showOriginalBtn,
child: MenuButton(
@@ -1262,7 +1366,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
return null;
}
if (display.isVirtualDisplayResolution) {
if (ffiModel.isVirtualDisplayResolution) {
return _localResolution!;
}
@@ -1284,8 +1388,8 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
if (bestFitResolution == null) {
return true;
}
return bestFitResolution.width == display.width &&
bestFitResolution.height == display.height;
return bestFitResolution.width == rect?.width.toInt() &&
bestFitResolution.height == rect?.height.toInt();
}
}
@@ -1361,7 +1465,7 @@ class _KeyboardMenu extends StatelessWidget {
continue;
}
if (pi.is_wayland && mode.key != _kKeyMapMode) {
if (pi.isWayland && mode.key != _kKeyMapMode) {
continue;
}
@@ -1404,7 +1508,7 @@ class _KeyboardMenu extends StatelessWidget {
viewMode() {
final ffiModel = ffi.ffiModel;
final enabled = version_cmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
final enabled = versionCmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
return CkbMenuButton(
value: ffiModel.viewOnly,
onChanged: enabled
@@ -2037,71 +2141,3 @@ Widget _buildPointerTrackWidget(Widget child, FFI ffi) {
),
);
}
class _MultiMonitorMenu extends StatelessWidget {
final String id;
final FFI ffi;
const _MultiMonitorMenu({
Key? key,
required this.id,
required this.ffi,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final List<Widget> rowChildren = [];
final pi = ffi.ffiModel.pi;
for (int i = 0; i < pi.displays.length; i++) {
rowChildren.add(
Obx(() {
RxInt display = CurrentDisplayState.find(id);
return _IconMenuButton(
tooltip: "",
topLevel: false,
color: i == display.value
? _ToolbarTheme.blueColor
: Colors.grey[800]!,
hoverColor: i == display.value
? _ToolbarTheme.hoverBlueColor
: Colors.grey[850]!,
icon: Container(
alignment: AlignmentDirectional.center,
constraints:
const BoxConstraints(minHeight: _ToolbarTheme.height),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter:
ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(
() => Text(
(i + 1).toString(),
style: TextStyle(
color: i == display.value
? _ToolbarTheme.blueColor
: Colors.grey[800]!,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
onPressed: () {
if (display.value != i) {
bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i);
}
},
);
}),
);
}
return Row(children: rowChildren);
}
}

View File

@@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide TabBarTheme;
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
@@ -176,6 +177,19 @@ class DesktopTabController {
jumpTo(state.value.tabs.indexWhere((tab) => tab.key == key),
callOnSelected: callOnSelected);
bool jumpToByKeyAndDisplay(String key, int display) {
for (int i = 0; i < state.value.tabs.length; i++) {
final tab = state.value.tabs[i];
if (tab.key == key) {
final ffi = (tab.page as RemotePage).ffi;
if (ffi.ffiModel.pi.currentDisplay == display) {
return jumpTo(i, callOnSelected: true);
}
}
}
return false;
}
void closeBy(String? key) {
if (!isDesktop) return;
assert(onRemoved != null);

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -755,14 +756,14 @@ void showOptions(
if (image != null) {
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
}
if (pi.displays.length > 1) {
if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) {
final cur = pi.currentDisplay;
final children = <Widget>[];
for (var i = 0; i < pi.displays.length; ++i) {
children.add(InkWell(
onTap: () {
if (i == cur) return;
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: i);
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: Int32List.fromList([i]));
gFFI.dialogManager.dismissAll();
},
child: Ink(

View File

@@ -4,25 +4,30 @@ import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
import '../../common.dart';
import './platform_model.dart';
final useTextureRender = bind.mainUseTextureRender();
class RenderTexture {
final RxInt textureId = RxInt(-1);
int _textureKey = -1;
int _display = 0;
SessionID? _sessionId;
static final useTextureRender = bind.mainUseTextureRender();
final textureRenderer = TextureRgbaRenderer();
RenderTexture();
create(SessionID sessionId) {
int get display => _display;
create(int d, SessionID sessionId) {
if (useTextureRender) {
_display = d;
_textureKey = bind.getNextTextureKey();
_sessionId = sessionId;
textureRenderer.createTexture(_textureKey).then((id) async {
if (id != -1) {
final ptr = await textureRenderer.getTexturePtr(_textureKey);
platformFFI.registerTexture(sessionId, ptr);
platformFFI.registerTexture(sessionId, display, ptr);
textureId.value = id;
}
});
@@ -32,7 +37,7 @@ class RenderTexture {
destroy(bool unregisterTexture) async {
if (useTextureRender && _textureKey != -1 && _sessionId != null) {
if (unregisterTexture) {
platformFFI.registerTexture(_sessionId!, 0);
platformFFI.registerTexture(_sessionId!, display, 0);
}
await textureRenderer.closeTexture(_textureKey);
_textureKey = -1;

View File

@@ -552,22 +552,22 @@ class InputModel {
return v;
}
Offset setNearestEdge(double x, double y, Display d) {
double left = x - d.x;
double right = d.x + d.width - 1 - x;
double top = y - d.y;
double bottom = d.y + d.height - 1 - y;
Offset setNearestEdge(double x, double y, Rect rect) {
double left = x - rect.left;
double right = rect.right - 1 - x;
double top = y - rect.top;
double bottom = rect.bottom - 1 - y;
if (left < right && left < top && left < bottom) {
x = d.x;
x = rect.left;
}
if (right < left && right < top && right < bottom) {
x = d.x + d.width - 1;
x = rect.right - 1;
}
if (top < left && top < right && top < bottom) {
y = d.y;
y = rect.top;
}
if (bottom < left && bottom < right && bottom < top) {
y = d.y + d.height - 1;
y = rect.bottom - 1;
}
return Offset(x, y);
}
@@ -711,9 +711,12 @@ class InputModel {
final nearThr = 3;
var nearRight = (canvasModel.size.width - x) < nearThr;
var nearBottom = (canvasModel.size.height - y) < nearThr;
final d = ffiModel.display;
final imageWidth = d.width * canvasModel.scale;
final imageHeight = d.height * canvasModel.scale;
final rect = ffiModel.rect;
if (rect == null) {
return null;
}
final imageWidth = rect.width * canvasModel.scale;
final imageHeight = rect.height * canvasModel.scale;
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
x += imageWidth * canvasModel.scrollX;
y += imageHeight * canvasModel.scrollY;
@@ -741,11 +744,11 @@ class InputModel {
y += step;
}
}
x += d.x;
y += d.y;
x += rect.left;
y += rect.top;
if (onExit) {
final pos = setNearestEdge(x, y, d);
final pos = setNearestEdge(x, y, rect);
x = pos.dx;
y = pos.dy;
}
@@ -761,10 +764,10 @@ class InputModel {
return null;
}
int minX = d.x.toInt();
int maxX = (d.x + d.width).toInt() - 1;
int minY = d.y.toInt();
int maxY = (d.y + d.height).toInt() - 1;
int minX = rect.left.toInt();
int maxX = (rect.left + rect.width).toInt() - 1;
int minY = rect.top.toInt();
int maxY = (rect.top + rect.height).toInt() - 1;
evtX = trySetNearestRange(evtX, minX, maxX, 5);
evtY = trySetNearestRange(evtY, minY, maxY, 5);
if (kind == kPointerEventKindMouse) {

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:desktop_multi_window/desktop_multi_window.dart';
@@ -86,7 +87,7 @@ class CachedPeerData {
class FfiModel with ChangeNotifier {
CachedPeerData cachedPeerData = CachedPeerData();
PeerInfo _pi = PeerInfo();
Display _display = Display();
Rect? _rect;
var _inputBlocked = false;
final _permissions = <String, bool>{};
@@ -103,9 +104,15 @@ class FfiModel with ChangeNotifier {
Timer? waitForImageTimer;
RxBool waitForFirstImage = true.obs;
Map<String, bool> get permissions => _permissions;
Rect? get rect => _rect;
bool get isOriginalResolutionSet =>
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolutionSet ?? false;
bool get isVirtualDisplayResolution =>
_pi.tryGetDisplayIfNotAllDisplay()?.isVirtualDisplayResolution ?? false;
bool get isOriginalResolution =>
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolution ?? false;
Display get display => _display;
Map<String, bool> get permissions => _permissions;
bool? get secure => _secure;
@@ -130,6 +137,24 @@ class FfiModel with ChangeNotifier {
sessionId = parent.target!.sessionId;
}
Rect? displaysRect() {
final displays = _pi.getCurDisplays();
if (displays.isEmpty) {
return null;
}
double l = displays[0].x;
double t = displays[0].y;
double r = displays[0].x + displays[0].width;
double b = displays[0].y + displays[0].height;
for (var display in displays.sublist(1)) {
l = min(l, display.x);
t = min(t, display.y);
r = max(r, display.x + display.width);
b = max(b, display.y + display.height);
}
return Rect.fromLTRB(l, t, r, b);
}
toggleTouchMode() {
if (!isPeerAndroid) {
_touchMode = !_touchMode;
@@ -154,7 +179,6 @@ class FfiModel with ChangeNotifier {
clear() {
_pi = PeerInfo();
_display = Display();
_secure = null;
_direct = null;
_inputBlocked = false;
@@ -207,8 +231,10 @@ class FfiModel with ChangeNotifier {
updateLastCursorId(element);
await handleCursorData(element);
}
updateLastCursorId(data.lastCursorId);
handleCursorId(data.lastCursorId);
if (data.lastCursorId.isNotEmpty) {
updateLastCursorId(data.lastCursorId);
handleCursorId(data.lastCursorId);
}
}
// todo: why called by two position
@@ -220,11 +246,12 @@ class FfiModel with ChangeNotifier {
} else if (name == 'peer_info') {
handlePeerInfo(evt, peerId);
} else if (name == 'sync_peer_info') {
handleSyncPeerInfo(evt, sessionId);
handleSyncPeerInfo(evt, sessionId, peerId);
} else if (name == 'connection_ready') {
setConnectionType(
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
} else if (name == 'switch_display') {
// switch display is kept for backward compatibility
handleSwitchDisplay(evt, sessionId, peerId);
} else if (name == 'cursor_data') {
updateLastCursorId(evt);
@@ -279,7 +306,7 @@ class FfiModel with ChangeNotifier {
await bind.sessionSwitchSides(sessionId: sessionId);
closeConnection(id: peer_id);
} else if (name == 'portable_service_running') {
parent.target?.elevationModel.onPortableServiceRunning(evt);
_handlePortableServiceRunning(peerId, evt);
} else if (name == 'on_url_scheme_received') {
// currently comes from "_url" ipc of mac and dbus of linux
onUrlSchemeReceived(evt);
@@ -354,20 +381,65 @@ class FfiModel with ChangeNotifier {
platformFFI.setEventCallback(startEventListener(sessionId, peerId));
}
_updateCurDisplay(SessionID sessionId, Display newDisplay) {
if (newDisplay != _display) {
if (newDisplay.x != _display.x || newDisplay.y != _display.y) {
parent.target?.cursorModel
.updateDisplayOrigin(newDisplay.x, newDisplay.y);
_handlePortableServiceRunning(String peerId, Map<String, dynamic> evt) {
final running = evt['running'] == 'true';
parent.target?.elevationModel.onPortableServiceRunning(running);
if (running) {
if (pi.primaryDisplay != kInvalidDisplayIndex) {
if (pi.currentDisplay != pi.primaryDisplay) {
// Notify to switch display
msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt',
'elevated_switch_display_msg', '', parent.target!.dialogManager);
bind.sessionSwitchDisplay(
sessionId: sessionId,
value: Int32List.fromList([pi.primaryDisplay]));
}
}
_display = newDisplay;
}
}
handleAliasChanged(Map<String, dynamic> evt) {
if (!isDesktop) return;
final String peerId = evt['id'];
final String alias = evt['alias'];
String label = getDesktopTabLabel(peerId, alias);
final rxTabLabel = PeerStringOption.find(evt['id'], 'tabLabel');
if (rxTabLabel.value != label) {
rxTabLabel.value = label;
}
}
updateCurDisplay(SessionID sessionId) {
final newRect = displaysRect();
if (newRect == null) {
return;
}
if (newRect != _rect) {
_rect = newRect;
if (newRect.left != _rect?.left || newRect.top != _rect?.top) {
parent.target?.cursorModel
.updateDisplayOrigin(newRect.left, newRect.top);
}
parent.target?.canvasModel.updateViewStyle();
_updateSessionWidthHeight(sessionId);
}
}
handleSwitchDisplay(
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
_pi.currentDisplay = int.parse(evt['display']);
final curDisplay = int.parse(evt['display']);
// The message should be handled by the another UI session.
if (_pi.isSupportMultiDisplay) {
if (curDisplay != _pi.currentDisplay) {
return;
}
}
if (_pi.currentDisplay != kAllDisplayValue) {
_pi.currentDisplay = curDisplay;
}
var newDisplay = Display();
newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x;
newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y;
@@ -378,11 +450,11 @@ class FfiModel with ChangeNotifier {
int.tryParse(evt['original_width']) ?? kInvalidResolutionValue;
newDisplay.originalHeight =
int.tryParse(evt['original_height']) ?? kInvalidResolutionValue;
_pi.displays[curDisplay] = newDisplay;
_updateCurDisplay(sessionId, newDisplay);
updateCurDisplay(sessionId);
try {
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
CurrentDisplayState.find(peerId).value = curDisplay;
} catch (e) {
//
}
@@ -522,13 +594,30 @@ class FfiModel with ChangeNotifier {
}
_updateSessionWidthHeight(SessionID sessionId) {
parent.target?.canvasModel.updateViewStyle();
if (display.width <= 0 || display.height <= 0) {
if (_rect == null) return;
if (_rect!.width <= 0 || _rect!.height <= 0) {
debugPrintStack(
label: 'invalid display size (${display.width},${display.height})');
label: 'invalid display size (${_rect!.width},${_rect!.height})');
} else {
bind.sessionSetSize(
sessionId: sessionId, width: display.width, height: display.height);
final displays = _pi.getCurDisplays();
if (displays.length == 1) {
bind.sessionSetSize(
sessionId: sessionId,
display:
pi.currentDisplay == kAllDisplayValue ? 0 : pi.currentDisplay,
width: _rect!.width.toInt(),
height: _rect!.height.toInt(),
);
} else {
for (int i = 0; i < displays.length; ++i) {
bind.sessionSetSize(
sessionId: sessionId,
display: i,
width: displays[i].width.toInt(),
height: displays[i].height.toInt(),
);
}
}
}
}
@@ -541,11 +630,20 @@ class FfiModel with ChangeNotifier {
parent.target?.dialogManager.dismissAll();
_pi.version = evt['version'];
_pi.isSupportMultiUiSession =
bind.isSupportMultiUiSession(version: _pi.version);
_pi.username = evt['username'];
_pi.hostname = evt['hostname'];
_pi.platform = evt['platform'];
_pi.sasEnabled = evt['sas_enabled'] == 'true';
_pi.currentDisplay = int.parse(evt['current_display']);
final currentDisplay = int.parse(evt['current_display']);
if (_pi.primaryDisplay == kInvalidDisplayIndex) {
_pi.primaryDisplay = currentDisplay;
}
if (bind.peerGetDefaultSessionsCount(id: peerId) <= 1) {
_pi.currentDisplay = currentDisplay;
}
try {
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
@@ -569,10 +667,10 @@ class FfiModel with ChangeNotifier {
for (int i = 0; i < displays.length; ++i) {
_pi.displays.add(evtToDisplay(displays[i]));
}
stateGlobal.displaysCount.value = _pi.displays.length;
_pi.displaysCount.value = _pi.displays.length;
if (_pi.currentDisplay < _pi.displays.length) {
_display = _pi.displays[_pi.currentDisplay];
_updateSessionWidthHeight(sessionId);
// now replaced to _updateCurDisplay
updateCurDisplay(sessionId);
}
if (displays.isNotEmpty) {
_reconnects = 1;
@@ -590,12 +688,12 @@ class FfiModel with ChangeNotifier {
sessionId: sessionId, arg: 'view-only'));
}
if (connType == ConnType.defaultConn) {
final platform_additions = evt['platform_additions'];
if (platform_additions != null && platform_additions != '') {
final platformDdditions = evt['platform_additions'];
if (platformDdditions != null && platformDdditions != '') {
try {
_pi.platform_additions = json.decode(platform_additions);
_pi.platformDdditions = json.decode(platformDdditions);
} catch (e) {
debugPrint('Failed to decode platform_additions $e');
debugPrint('Failed to decode platformDdditions $e');
}
}
}
@@ -670,7 +768,8 @@ class FfiModel with ChangeNotifier {
}
/// Handle the peer info synchronization event based on [evt].
handleSyncPeerInfo(Map<String, dynamic> evt, SessionID sessionId) async {
handleSyncPeerInfo(
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
if (evt['displays'] != null) {
cachedPeerData.peerInfo['displays'] = evt['displays'];
List<dynamic> displays = json.decode(evt['displays']);
@@ -679,14 +778,54 @@ class FfiModel with ChangeNotifier {
newDisplays.add(evtToDisplay(displays[i]));
}
_pi.displays = newDisplays;
stateGlobal.displaysCount.value = _pi.displays.length;
if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) {
_updateCurDisplay(sessionId, _pi.displays[_pi.currentDisplay]);
_pi.displaysCount.value = _pi.displays.length;
if (_pi.currentDisplay == kAllDisplayValue) {
updateCurDisplay(sessionId);
// to-do: What if the displays are changed?
} else {
if (_pi.currentDisplay >= 0 &&
_pi.currentDisplay < _pi.displays.length) {
updateCurDisplay(sessionId);
} else {
if (_pi.displays.isNotEmpty) {
// Notify to switch display
msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt',
'display_is_plugged_out_msg', '', parent.target!.dialogManager);
final newDisplay = pi.primaryDisplay == kInvalidDisplayIndex
? 0
: pi.primaryDisplay;
final displays = newDisplay;
bind.sessionSwitchDisplay(
sessionId: sessionId, value: Int32List.fromList([displays]));
if (_pi.isSupportMultiUiSession) {
// If the peer supports multi-ui-session, no switch display message will be send back.
// We need to update the display manually.
switchToNewDisplay(newDisplay, sessionId, peerId);
}
} else {
msgBox(sessionId, 'nocancel-error', 'Prompt', 'No Displays', '',
parent.target!.dialogManager);
}
}
}
}
notifyListeners();
}
// Directly switch to the new display without waiting for the response.
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
// no need to wait for the response
pi.currentDisplay = display;
updateCurDisplay(sessionId);
try {
CurrentDisplayState.find(peerId).value = display;
} catch (e) {
//
}
parent.target?.recordingModel.onSwitchDisplay();
}
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
_inputBlocked = evt['input_state'] == 'on';
notifyListeners();
@@ -709,7 +848,7 @@ class FfiModel with ChangeNotifier {
}
void setViewOnly(String id, bool value) {
if (version_cmp(_pi.version, '1.2.0') < 0) return;
if (versionCmp(_pi.version, '1.2.0') < 0) return;
// tmp fix for https://github.com/rustdesk/rustdesk/pull/3706#issuecomment-1481242389
// because below rx not used in mobile version, so not initialized, below code will cause crash
// current our flutter code quality is fucking shit now. !!!!!!!!!!!!!!!!
@@ -749,16 +888,16 @@ class ImageModel with ChangeNotifier {
addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb);
onRgba(Uint8List rgba) {
onRgba(int display, Uint8List rgba) {
final pid = parent.target?.id;
img.decodeImageFromPixels(
rgba,
parent.target?.ffiModel.display.width ?? 0,
parent.target?.ffiModel.display.height ?? 0,
parent.target?.ffiModel.rect?.width.toInt() ?? 0,
parent.target?.ffiModel.rect?.height.toInt() ?? 0,
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888,
onPixelsCopied: () {
// Unlock the rgba memory from rust codes.
platformFFI.nextRgba(sessionId);
platformFFI.nextRgba(sessionId, display);
}).then((image) {
if (parent.target?.id != pid) return;
try {
@@ -1017,20 +1156,20 @@ class CanvasModel with ChangeNotifier {
}
bool get cursorEmbedded =>
parent.target?.ffiModel.display.cursorEmbedded ?? false;
parent.target?.ffiModel._pi.cursorEmbedded ?? false;
int getDisplayWidth() {
final defaultWidth = (isDesktop || isWebDesktop)
? kDesktopDefaultDisplayWidth
: kMobileDefaultDisplayWidth;
return parent.target?.ffiModel.display.width ?? defaultWidth;
return parent.target?.ffiModel.rect?.width.toInt() ?? defaultWidth;
}
int getDisplayHeight() {
final defaultHeight = (isDesktop || isWebDesktop)
? kDesktopDefaultDisplayHeight
: kMobileDefaultDisplayHeight;
return parent.target?.ffiModel.display.height ?? defaultHeight;
return parent.target?.ffiModel.rect?.height.toInt() ?? defaultHeight;
}
static double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
@@ -1619,7 +1758,27 @@ class QualityMonitorModel with ChangeNotifier {
updateQualityStatus(Map<String, dynamic> evt) {
try {
if ((evt['speed'] as String).isNotEmpty) _data.speed = evt['speed'];
if ((evt['fps'] as String).isNotEmpty) _data.fps = evt['fps'];
if ((evt['fps'] as String).isNotEmpty) {
final fps = jsonDecode(evt['fps']) as Map<String, dynamic>;
final pi = parent.target?.ffiModel.pi;
if (pi != null) {
final currentDisplay = pi.currentDisplay;
if (currentDisplay != kAllDisplayValue) {
final fps2 = fps[currentDisplay.toString()];
if (fps2 != null) {
_data.fps = fps2.toString();
}
} else if (fps.isNotEmpty) {
final fpsList = [];
for (var i = 0; i < pi.displays.length; i++) {
fpsList.add((fps[i.toString()] ?? 0).toString());
}
_data.fps = fpsList.join(' ');
}
} else {
_data.fps = null;
}
}
if ((evt['delay'] as String).isNotEmpty) _data.delay = evt['delay'];
if ((evt['target_bitrate'] as String).isNotEmpty) {
_data.targetBitrate = evt['target_bitrate'];
@@ -1646,8 +1805,15 @@ class RecordingModel with ChangeNotifier {
int? width = parent.target?.canvasModel.getDisplayWidth();
int? height = parent.target?.canvasModel.getDisplayHeight();
if (sessionId == null || width == null || height == null) return;
bind.sessionRecordScreen(
sessionId: sessionId, start: true, width: width, height: height);
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
if (currentDisplay != kAllDisplayValue) {
bind.sessionRecordScreen(
sessionId: sessionId,
start: true,
display: currentDisplay!,
width: width,
height: height);
}
}
toggle() async {
@@ -1658,10 +1824,20 @@ class RecordingModel with ChangeNotifier {
notifyListeners();
await bind.sessionRecordStatus(sessionId: sessionId, status: _start);
if (_start) {
bind.sessionRefresh(sessionId: sessionId);
final pi = parent.target?.ffiModel.pi;
if (pi != null) {
sessionRefreshVideo(sessionId, pi);
}
} else {
bind.sessionRecordScreen(
sessionId: sessionId, start: false, width: 0, height: 0);
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
if (currentDisplay != kAllDisplayValue) {
bind.sessionRecordScreen(
sessionId: sessionId,
start: false,
display: currentDisplay!,
width: 0,
height: 0);
}
}
}
@@ -1670,8 +1846,15 @@ class RecordingModel with ChangeNotifier {
final sessionId = parent.target?.sessionId;
if (sessionId == null) return;
_start = false;
bind.sessionRecordScreen(
sessionId: sessionId, start: false, width: 0, height: 0);
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
if (currentDisplay != kAllDisplayValue) {
bind.sessionRecordScreen(
sessionId: sessionId,
start: false,
display: currentDisplay!,
width: 0,
height: 0);
}
}
}
@@ -1686,9 +1869,7 @@ class ElevationModel with ChangeNotifier {
_running = false;
}
onPortableServiceRunning(Map<String, dynamic> evt) {
_running = evt['running'] == 'true';
}
onPortableServiceRunning(bool running) => _running = running;
}
enum ConnType { defaultConn, fileTransfer, portForward, rdp }
@@ -1751,14 +1932,18 @@ class FFI {
}
/// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
void start(String id,
{bool isFileTransfer = false,
bool isPortForward = false,
bool isRdp = false,
String? switchUuid,
String? password,
bool? forceRelay,
int? tabWindowId}) {
void start(
String id, {
bool isFileTransfer = false,
bool isPortForward = false,
bool isRdp = false,
String? switchUuid,
String? password,
bool? forceRelay,
int? tabWindowId,
int? display,
List<int>? displays,
}) {
closed = false;
auditNote = '';
if (isMobile) mobileReset();
@@ -1788,11 +1973,34 @@ class FFI {
forceRelay: forceRelay ?? false,
password: password ?? '',
);
} else if (display != null) {
if (displays == null) {
debugPrint(
'Unreachable, failed to add existed session to $id, the displays is null while display is $display');
return;
}
final addRes = bind.sessionAddExistedSync(id: id, sessionId: sessionId);
if (addRes != '') {
debugPrint(
'Unreachable, failed to add existed session to $id, $addRes');
return;
}
bind.sessionTryAddDisplay(
sessionId: sessionId, displays: Int32List.fromList(displays));
ffiModel.pi.currentDisplay = display;
}
final stream = bind.sessionStart(sessionId: sessionId, id: id);
final cb = ffiModel.startEventListener(sessionId, id);
final useTextureRender = bind.mainUseTextureRender();
// Force refresh displays.
// The controlled side may not refresh the image when the (peer,display) is already subscribed.
if (displays != null) {
for (final display in displays) {
bind.sessionRefresh(sessionId: sessionId, display: display);
}
}
final SimpleWrapper<bool> isToNewWindowNotified = SimpleWrapper(false);
// Preserved for the rgba data.
stream.listen((message) {
@@ -1801,8 +2009,9 @@ class FFI {
// Session is read to be moved to a new window.
// Get the cached data and handle the cached data.
Future.delayed(Duration.zero, () async {
final args = jsonEncode({'id': id, 'close': display == null});
final cachedData = await DesktopMultiWindow.invokeMethod(
tabWindowId, kWindowEventGetCachedSessionData, id);
tabWindowId, kWindowEventGetCachedSessionData, args);
if (cachedData == null) {
// unreachable
debugPrint('Unreachable, the cached data is empty.');
@@ -1814,7 +2023,7 @@ class FFI {
return;
}
await ffiModel.handleCachedPeerData(data, id);
await bind.sessionRefresh(sessionId: sessionId);
await sessionRefreshVideo(sessionId, ffiModel.pi);
});
isToNewWindowNotified.value = true;
}
@@ -1836,18 +2045,19 @@ class FFI {
await cb(event);
}
} else if (message is EventToUI_Rgba) {
final display = message.field0;
if (useTextureRender) {
onEvent2UIRgba();
} else {
// Fetch the image buffer from rust codes.
final sz = platformFFI.getRgbaSize(sessionId);
final sz = platformFFI.getRgbaSize(sessionId, display);
if (sz == 0) {
return;
}
final rgba = platformFFI.getRgba(sessionId, sz);
final rgba = platformFFI.getRgba(sessionId, display, sz);
if (rgba != null) {
onEvent2UIRgba();
imageModel.onRgba(rgba);
imageModel.onRgba(display, rgba);
}
}
}
@@ -1979,22 +2189,73 @@ class Features {
bool privacyMode = false;
}
const kInvalidDisplayIndex = -1;
class PeerInfo with ChangeNotifier {
String version = '';
String username = '';
String hostname = '';
String platform = '';
bool sasEnabled = false;
bool isSupportMultiUiSession = false;
int currentDisplay = 0;
int primaryDisplay = kInvalidDisplayIndex;
List<Display> displays = [];
Features features = Features();
List<Resolution> resolutions = [];
Map<String, dynamic> platform_additions = {};
Map<String, dynamic> platformDdditions = {};
RxInt displaysCount = 0.obs;
RxBool isSet = false.obs;
bool get is_wayland => platform_additions['is_wayland'] == true;
bool get is_headless => platform_additions['headless'] == true;
bool get isWayland => platformDdditions['is_wayland'] == true;
bool get isHeadless => platformDdditions['headless'] == true;
bool get isSupportMultiDisplay =>
isDesktop && isSupportMultiUiSession && isChooseDisplayToOpen;
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
Display? tryGetDisplay() {
if (displays.isEmpty) {
return null;
}
if (currentDisplay == kAllDisplayValue) {
return displays[0];
} else {
if (currentDisplay > 0 && currentDisplay < displays.length) {
return displays[currentDisplay];
} else {
return displays[0];
}
}
}
Display? tryGetDisplayIfNotAllDisplay() {
if (displays.isEmpty) {
return null;
}
if (currentDisplay == kAllDisplayValue) {
return null;
}
if (currentDisplay > 0 && currentDisplay < displays.length) {
return displays[currentDisplay];
} else {
return null;
}
}
List<Display> getCurDisplays() {
if (currentDisplay == kAllDisplayValue) {
return displays;
} else {
if (currentDisplay >= 0 && currentDisplay < displays.length) {
return [displays[currentDisplay]];
} else {
return [];
}
}
}
}
const canvasKey = 'canvas';
@@ -2038,8 +2299,8 @@ Future<void> initializeCursorAndCanvas(FFI ffi) async {
currentDisplay = p['currentDisplay'];
}
if (p == null || currentDisplay != ffi.ffiModel.pi.currentDisplay) {
ffi.cursorModel
.updateDisplayOrigin(ffi.ffiModel.display.x, ffi.ffiModel.display.y);
ffi.cursorModel.updateDisplayOrigin(
ffi.ffiModel.rect?.left ?? 0, ffi.ffiModel.rect?.top ?? 0);
return;
}
double xCursor = p['xCursor'];
@@ -2047,8 +2308,8 @@ Future<void> initializeCursorAndCanvas(FFI ffi) async {
double xCanvas = p['xCanvas'];
double yCanvas = p['yCanvas'];
double scale = p['scale'];
ffi.cursorModel.updateDisplayOriginWithCursor(
ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor);
ffi.cursorModel.updateDisplayOriginWithCursor(ffi.ffiModel.rect?.left ?? 0,
ffi.ffiModel.rect?.top ?? 0, xCursor, yCursor);
ffi.canvasModel.update(xCanvas, yCanvas, scale);
}

View File

@@ -21,7 +21,8 @@ class RgbaFrame extends Struct {
external Pointer<Uint8> data;
}
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>);
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>, int);
typedef F3Dart = Pointer<Uint8> Function(Pointer<Utf8>, Int32);
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt);
/// FFI wrapper around the native Rust core.
@@ -80,12 +81,12 @@ class PlatformFFI {
String translate(String name, String locale) =>
_ffiBind.translate(name: name, locale: locale);
Uint8List? getRgba(SessionID sessionId, int bufSize) {
Uint8List? getRgba(SessionID sessionId, int display, int bufSize) {
if (_session_get_rgba == null) return null;
final sessionIdStr = sessionId.toString();
var a = sessionIdStr.toNativeUtf8();
try {
final buffer = _session_get_rgba!(a);
final buffer = _session_get_rgba!(a, display);
if (buffer == nullptr) {
return null;
}
@@ -96,12 +97,11 @@ class PlatformFFI {
}
}
int getRgbaSize(SessionID sessionId) =>
_ffiBind.sessionGetRgbaSize(sessionId: sessionId);
void nextRgba(SessionID sessionId) =>
_ffiBind.sessionNextRgba(sessionId: sessionId);
void registerTexture(SessionID sessionId, int ptr) =>
_ffiBind.sessionRegisterTexture(sessionId: sessionId, ptr: ptr);
int getRgbaSize(SessionID sessionId, int display) =>
_ffiBind.sessionGetRgbaSize(sessionId: sessionId, display: display);
void nextRgba(SessionID sessionId, int display) => _ffiBind.sessionNextRgba(sessionId: sessionId, display: display);
void registerTexture(SessionID sessionId, int display, int ptr) =>
_ffiBind.sessionRegisterTexture(sessionId: sessionId, display: display, ptr: ptr);
/// Init the FFI class, loads the native Rust core library.
Future<void> init(String appType) async {
@@ -117,7 +117,7 @@ class PlatformFFI {
: DynamicLibrary.process();
debugPrint('initializing FFI $_appType');
try {
_session_get_rgba = dylib.lookupFunction<F3, F3>("session_get_rgba");
_session_get_rgba = dylib.lookupFunction<F3Dart, F3>("session_get_rgba");
try {
// SYSTEM user failed
_dir = (await getApplicationDocumentsDirectory()).path;

View File

@@ -18,7 +18,6 @@ class StateGlobal {
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
final RxBool showRemoteToolBar = false.obs;
final RxInt displaysCount = 0.obs;
final svcStatus = SvcStatus.notReady.obs;
// Only used for macOS
bool closeOnFullscreen = false;

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -67,6 +68,44 @@ class RustDeskMultiWindowManager {
);
}
// This function must be called in the main window thread.
// Because the _remoteDesktopWindows is managed in that thread.
openMonitorSession(
int windowId, String peerId, int display, int displayCount) async {
if (_remoteDesktopWindows.length > 1) {
for (final windowId in _remoteDesktopWindows) {
if (await DesktopMultiWindow.invokeMethod(
windowId,
kWindowEventActiveDisplaySession,
jsonEncode({
'id': peerId,
'display': display,
}))) {
return;
}
}
}
final displays = display == kAllDisplayValue
? List.generate(displayCount, (index) => index)
: [display];
var params = {
'type': WindowType.RemoteDesktop.index,
'id': peerId,
'tab_window_id': windowId,
'display': display,
'displays': displays,
};
await _newSession(
false,
WindowType.RemoteDesktop,
kWindowEventNewRemoteDesktop,
peerId,
_remoteDesktopWindows,
jsonEncode(params),
);
}
Future<int> newSessionWindow(
WindowType type, String remoteId, String msg, List<int> windows) async {
final windowController = await DesktopMultiWindow.createWindow(msg);
@@ -148,11 +187,21 @@ class RustDeskMultiWindowManager {
bool openInTabs = type != WindowType.RemoteDesktop ||
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
if (windows.length > 1 || !openInTabs) {
for (final windowId in windows) {
if (await DesktopMultiWindow.invokeMethod(
windowId, kWindowEventActiveSession, remoteId)) {
return MultiWindowCallResult(windowId, null);
if (kOpenSamePeerInNewWindow) {
// Open in new window if the peer is already connected.
// No need to care about the previous session type.
if (type == WindowType.RemoteDesktop &&
await bind.sessionGetFlutterOptionByPeerId(id: remoteId, k: '') !=
null) {
openInTabs = false;
}
} else {
if (windows.length > 1 || !openInTabs) {
for (final windowId in windows) {
if (await DesktopMultiWindow.invokeMethod(
windowId, kWindowEventActiveSession, remoteId)) {
return MultiWindowCallResult(windowId, null);
}
}
}
}