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

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