Merge branch 'rustdesk:master' into chat

This commit is contained in:
NicKoehler
2023-05-20 18:35:03 +02:00
committed by GitHub
59 changed files with 1087 additions and 295 deletions

View File

@@ -19,8 +19,6 @@ import '../../common/widgets/peer_tab_page.dart';
import '../../models/platform_model.dart';
import '../widgets/button.dart';
import 'package:flutter_hbb/common/widgets/dialog.dart';
/// Connection page for connecting to a remote peer.
class ConnectionPage extends StatefulWidget {
const ConnectionPage({Key? key}) : super(key: key);

View File

@@ -660,69 +660,24 @@ class _ControlMenu extends StatelessWidget {
}
}
class _DisplayMenu extends StatefulWidget {
class ScreenAdjustor {
final String id;
final FFI ffi;
final MenubarState state;
final Function(bool) setFullscreen;
final Widget pluginItem;
_DisplayMenu(
{Key? key,
required this.id,
required this.ffi,
required this.state,
required this.setFullscreen})
: pluginItem = LocationItem.createLocationItem(
id,
ffi,
kLocationClientRemoteToolbarDisplay,
true,
),
super(key: key);
@override
State<_DisplayMenu> createState() => _DisplayMenuState();
}
class _DisplayMenuState extends State<_DisplayMenu> {
final VoidCallback cbExitFullscreen;
window_size.Screen? _screen;
ScreenAdjustor({
required this.id,
required this.ffi,
required this.cbExitFullscreen,
});
bool get isFullscreen => stateGlobal.fullscreen;
int get windowId => stateGlobal.windowId;
Map<String, bool> get perms => widget.ffi.ffiModel.permissions;
PeerInfo get pi => widget.ffi.ffiModel.pi;
FfiModel get ffiModel => widget.ffi.ffiModel;
FFI get ffi => widget.ffi;
String get id => widget.id;
@override
Widget build(BuildContext context) {
_updateScreen();
return _IconSubmenuButton(
tooltip: 'Display Settings',
svg: "assets/display.svg",
ffi: widget.ffi,
color: _MenubarTheme.blueColor,
hoverColor: _MenubarTheme.hoverBlueColor,
menuChildren: [
adjustWindow(),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
resolutions(),
Divider(),
toggles(),
widget.pluginItem,
]);
}
adjustWindow() {
return futureBuilder(
future: _isWindowCanBeAdjusted(),
future: isWindowCanBeAdjusted(),
hasData: (data) {
final visible = data as bool;
if (!visible) return Offstage();
@@ -730,18 +685,18 @@ class _DisplayMenuState extends State<_DisplayMenu> {
children: [
MenuButton(
child: Text(translate('Adjust Window')),
onPressed: _doAdjustWindow,
ffi: widget.ffi),
onPressed: doAdjustWindow,
ffi: ffi),
Divider(),
],
);
});
}
_doAdjustWindow() async {
await _updateScreen();
doAdjustWindow() async {
await updateScreen();
if (_screen != null) {
widget.setFullscreen(false);
cbExitFullscreen();
double scale = _screen!.scaleFactor;
final wndRect = await WindowController.fromWindowId(windowId).getFrame();
final mediaSize = MediaQueryData.fromWindow(ui.window).size;
@@ -752,7 +707,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
double magicHeight =
wndRect.bottom - wndRect.top - mediaSize.height * scale;
final canvasModel = widget.ffi.canvasModel;
final canvasModel = ffi.canvasModel;
final width = (canvasModel.getDisplayWidth() * canvasModel.scale +
CanvasModel.leftToEdge +
CanvasModel.rightToEdge) *
@@ -787,7 +742,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
}
_updateScreen() async {
updateScreen() async {
final v = await rustDeskWinManager.call(
WindowType.Main, kWindowGetWindowInfo, '');
final String valueStr = v;
@@ -807,8 +762,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
}
Future<bool> _isWindowCanBeAdjusted() async {
final viewStyle = await bind.sessionGetViewStyle(id: widget.id) ?? '';
Future<bool> isWindowCanBeAdjusted() async {
final viewStyle = await bind.sessionGetViewStyle(id: id) ?? '';
if (viewStyle != kRemoteViewStyleOriginal) {
return false;
}
@@ -827,7 +782,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
selfHeight = _screen!.frame.height;
}
final canvasModel = widget.ffi.canvasModel;
final canvasModel = ffi.canvasModel;
final displayWidth = canvasModel.getDisplayWidth();
final displayHeight = canvasModel.getDisplayHeight();
final requiredWidth =
@@ -837,6 +792,77 @@ class _DisplayMenuState extends State<_DisplayMenu> {
return selfWidth > (requiredWidth * scale) &&
selfHeight > (requiredHeight * scale);
}
}
class _DisplayMenu extends StatefulWidget {
final String id;
final FFI ffi;
final MenubarState state;
final Function(bool) setFullscreen;
final Widget pluginItem;
_DisplayMenu(
{Key? key,
required this.id,
required this.ffi,
required this.state,
required this.setFullscreen})
: pluginItem = LocationItem.createLocationItem(
id,
ffi,
kLocationClientRemoteToolbarDisplay,
true,
),
super(key: key);
@override
State<_DisplayMenu> createState() => _DisplayMenuState();
}
class _DisplayMenuState extends State<_DisplayMenu> {
late final ScreenAdjustor _screenAdjustor = ScreenAdjustor(
id: widget.id,
ffi: widget.ffi,
cbExitFullscreen: () => widget.setFullscreen(false),
);
bool get isFullscreen => stateGlobal.fullscreen;
int get windowId => stateGlobal.windowId;
Map<String, bool> get perms => widget.ffi.ffiModel.permissions;
PeerInfo get pi => widget.ffi.ffiModel.pi;
FfiModel get ffiModel => widget.ffi.ffiModel;
FFI get ffi => widget.ffi;
String get id => widget.id;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
_screenAdjustor.updateScreen();
return _IconSubmenuButton(
tooltip: 'Display Settings',
svg: "assets/display.svg",
ffi: widget.ffi,
color: _MenubarTheme.blueColor,
hoverColor: _MenubarTheme.hoverBlueColor,
menuChildren: [
_screenAdjustor.adjustWindow(),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
_ResolutionsMenu(
id: widget.id,
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
Divider(),
toggles(),
widget.pluginItem,
]);
}
viewStyle() {
return futureBuilder(
@@ -935,46 +961,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
});
}
resolutions() {
final resolutions = pi.resolutions;
final visible = ffiModel.keyboard && resolutions.length > 1;
if (!visible) return Offstage();
final display = ffiModel.display;
final groupValue = "${display.width}x${display.height}";
onChanged(String? value) async {
if (value == null) return;
final list = value.split('x');
if (list.length == 2) {
final w = int.tryParse(list[0]);
final h = int.tryParse(list[1]);
if (w != null && h != null) {
await bind.sessionChangeResolution(
id: widget.id, width: w, height: h);
Future.delayed(Duration(seconds: 3), () async {
final display = ffiModel.display;
if (w == display.width && h == display.height) {
if (await _isWindowCanBeAdjusted()) {
_doAdjustWindow();
}
}
});
}
}
}
return _SubmenuButton(
ffi: widget.ffi,
menuChildren: resolutions
.map((e) => RdoMenuButton(
value: '${e.width}x${e.height}',
groupValue: groupValue,
onChanged: onChanged,
ffi: widget.ffi,
child: Text('${e.width}x${e.height}')))
.toList(),
child: Text(translate("Resolution")));
}
toggles() {
return futureBuilder(
future: toolbarDisplayToggle(context, id, ffi),
@@ -993,6 +979,263 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
}
class _ResolutionsMenu extends StatefulWidget {
final String id;
final FFI ffi;
final ScreenAdjustor screenAdjustor;
_ResolutionsMenu({
Key? key,
required this.id,
required this.ffi,
required this.screenAdjustor,
}) : super(key: key);
@override
State<_ResolutionsMenu> createState() => _ResolutionsMenuState();
}
const double _kCustonResolutionEditingWidth = 42;
const _kCustomResolutionValue = 'custom';
class _ResolutionsMenuState extends State<_ResolutionsMenu> {
String _groupValue = '';
Resolution? _localResolution;
late final TextEditingController _customWidth =
TextEditingController(text: display.width.toString());
late final TextEditingController _customHeight =
TextEditingController(text: display.height.toString());
PeerInfo get pi => widget.ffi.ffiModel.pi;
FfiModel get ffiModel => widget.ffi.ffiModel;
Display get display => ffiModel.display;
List<Resolution> get resolutions => pi.resolutions;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final isVirtualDisplay = display.isVirtualDisplayResolution;
final visible =
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
if (!visible) return Offstage();
_getLocalResolution();
final showOriginalBtn =
display.isOriginalResolutionSet && !display.isOriginalResolution;
final showFitLocalBtn = !_isRemoteResolutionFitLocal();
_setGroupValue();
return _SubmenuButton(
ffi: widget.ffi,
menuChildren: <Widget>[
_OriginalResolutionMenuButton(showOriginalBtn),
_FitLocalResolutionMenuButton(showFitLocalBtn),
_customResolutionMenuButton(isVirtualDisplay),
_menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay),
] +
_supportedResolutionMenuButtons(),
child: Text(translate("Resolution")),
);
}
_setGroupValue() {
final lastGroupValue =
stateGlobal.getLastResolutionGroupValue(widget.id, pi.currentDisplay);
if (lastGroupValue == _kCustomResolutionValue) {
_groupValue = _kCustomResolutionValue;
} else {
_groupValue = '${display.width}x${display.height}';
}
}
_menuDivider(
bool showOriginalBtn, bool showFitLocalBtn, bool isVirtualDisplay) {
return Offstage(
offstage: !(showOriginalBtn || showFitLocalBtn || isVirtualDisplay),
child: Divider(),
);
}
_getLocalResolution() {
_localResolution = null;
final String currentDisplay = bind.mainGetCurrentDisplay();
if (currentDisplay.isNotEmpty) {
try {
final display = json.decode(currentDisplay);
if (display['w'] != null && display['h'] != null) {
_localResolution = Resolution(display['w'], display['h']);
}
} catch (e) {
debugPrint('Failed to decode $currentDisplay, $e');
}
}
}
_onChanged(String? value) async {
stateGlobal.setLastResolutionGroupValue(
widget.id, pi.currentDisplay, value);
if (value == null) return;
int? w;
int? h;
if (value == _kCustomResolutionValue) {
w = int.tryParse(_customWidth.text);
h = int.tryParse(_customHeight.text);
} else {
final list = value.split('x');
if (list.length == 2) {
w = int.tryParse(list[0]);
h = int.tryParse(list[1]);
}
}
if (w != null && h != null) {
if (w != display.width || h != display.height) {
await _changeResolution(w, h);
}
}
}
_changeResolution(int w, int h) async {
await bind.sessionChangeResolution(
id: widget.id,
width: w,
height: h,
);
Future.delayed(Duration(seconds: 3), () async {
final display = ffiModel.display;
if (w == display.width && h == display.height) {
if (await widget.screenAdjustor.isWindowCanBeAdjusted()) {
widget.screenAdjustor.doAdjustWindow();
}
}
});
}
Widget _OriginalResolutionMenuButton(bool showOriginalBtn) {
return Offstage(
offstage: !showOriginalBtn,
child: MenuButton(
onPressed: () =>
_changeResolution(display.originalWidth, display.originalHeight),
ffi: widget.ffi,
child: Text(
'${translate('resolution_original_tip')} ${display.originalWidth}x${display.originalHeight}'),
),
);
}
Widget _FitLocalResolutionMenuButton(bool showFitLocalBtn) {
return Offstage(
offstage: !showFitLocalBtn,
child: MenuButton(
onPressed: () {
final resolution = _getBestFitResolution();
if (resolution != null) {
_changeResolution(resolution.width, resolution.height);
}
},
ffi: widget.ffi,
child: Text(
'${translate('resolution_fit_local_tip')} ${_localResolution?.width ?? 0}x${_localResolution?.height ?? 0}'),
),
);
}
Widget _customResolutionMenuButton(isVirtualDisplay) {
return Offstage(
offstage: !isVirtualDisplay,
child: RdoMenuButton(
value: _kCustomResolutionValue,
groupValue: _groupValue,
onChanged: _onChanged,
ffi: widget.ffi,
child: Row(
children: [
Text('${translate('resolution_custom_tip')} '),
SizedBox(
width: _kCustonResolutionEditingWidth,
child: _resolutionInput(_customWidth),
),
Text(' x '),
SizedBox(
width: _kCustonResolutionEditingWidth,
child: _resolutionInput(_customHeight),
),
],
),
),
);
}
TextField _resolutionInput(TextEditingController controller) {
return TextField(
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.fromLTRB(3, 3, 3, 3),
),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
controller: controller,
);
}
List<Widget> _supportedResolutionMenuButtons() => resolutions
.map((e) => RdoMenuButton(
value: '${e.width}x${e.height}',
groupValue: _groupValue,
onChanged: _onChanged,
ffi: widget.ffi,
child: Text('${e.width}x${e.height}')))
.toList();
Resolution? _getBestFitResolution() {
if (_localResolution == null) {
return null;
}
if (display.isVirtualDisplayResolution) {
return _localResolution!;
}
squareDistance(Resolution lhs, Resolution rhs) =>
(lhs.width - rhs.width) * (lhs.width - rhs.width) +
(lhs.height - rhs.height) * (lhs.height - rhs.height);
Resolution res = Resolution(display.width, display.height);
for (final r in resolutions) {
if (r.width <= _localResolution!.width &&
r.height <= _localResolution!.height) {
if (squareDistance(r, _localResolution!) <
squareDistance(res, _localResolution!)) {
res = r;
}
}
}
return res;
}
bool _isRemoteResolutionFitLocal() {
if (_localResolution == null) {
return true;
}
final bestFitResolution = _getBestFitResolution();
if (bestFitResolution == null) {
return true;
}
return bestFitResolution.width == display.width &&
bestFitResolution.height == display.height;
}
}
class _KeyboardMenu extends StatelessWidget {
final String id;
final FFI ffi;
@@ -1483,14 +1726,14 @@ class RdoMenuButton<T> extends StatelessWidget {
final ValueChanged<T?>? onChanged;
final Widget? child;
final FFI ffi;
const RdoMenuButton(
{Key? key,
required this.value,
required this.groupValue,
required this.onChanged,
required this.child,
required this.ffi})
: super(key: key);
const RdoMenuButton({
Key? key,
required this.value,
required this.groupValue,
required this.child,
required this.ffi,
this.onChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {

View File

@@ -295,11 +295,15 @@ class FfiModel with ChangeNotifier {
handleSwitchDisplay(Map<String, dynamic> evt, String peerId) {
_pi.currentDisplay = int.parse(evt['display']);
var newDisplay = Display();
newDisplay.x = double.parse(evt['x']);
newDisplay.y = double.parse(evt['y']);
newDisplay.width = int.parse(evt['width']);
newDisplay.height = int.parse(evt['height']);
newDisplay.cursorEmbedded = int.parse(evt['cursor_embedded']) == 1;
newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x;
newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y;
newDisplay.width = int.tryParse(evt['width']) ?? newDisplay.width;
newDisplay.height = int.tryParse(evt['height']) ?? newDisplay.height;
newDisplay.cursorEmbedded = int.tryParse(evt['cursor_embedded']) == 1;
newDisplay.originalWidth =
int.tryParse(evt['original_width']) ?? kInvalidResolutionValue;
newDisplay.originalHeight =
int.tryParse(evt['original_height']) ?? kInvalidResolutionValue;
_updateCurDisplay(peerId, newDisplay);
@@ -466,14 +470,7 @@ class FfiModel with ChangeNotifier {
_pi.displays = [];
List<dynamic> displays = json.decode(evt['displays']);
for (int i = 0; i < displays.length; ++i) {
Map<String, dynamic> d0 = displays[i];
var d = Display();
d.x = d0['x'].toDouble();
d.y = d0['y'].toDouble();
d.width = d0['width'];
d.height = d0['height'];
d.cursorEmbedded = d0['cursor_embedded'] == 1;
_pi.displays.add(d);
_pi.displays.add(evtToDisplay(displays[i]));
}
stateGlobal.displaysCount.value = _pi.displays.length;
if (_pi.currentDisplay < _pi.displays.length) {
@@ -506,6 +503,9 @@ class FfiModel with ChangeNotifier {
}
}
}
stateGlobal.resetLastResolutionGroupValues(peerId);
notifyListeners();
}
@@ -533,20 +533,25 @@ class FfiModel with ChangeNotifier {
}
}
Display evtToDisplay(Map<String, dynamic> evt) {
var d = Display();
d.x = evt['x']?.toDouble() ?? d.x;
d.y = evt['y']?.toDouble() ?? d.y;
d.width = evt['width'] ?? d.width;
d.height = evt['height'] ?? d.height;
d.cursorEmbedded = evt['cursor_embedded'] == 1;
d.originalWidth = evt['original_width'] ?? kInvalidResolutionValue;
d.originalHeight = evt['original_height'] ?? kInvalidResolutionValue;
return d;
}
/// Handle the peer info synchronization event based on [evt].
handleSyncPeerInfo(Map<String, dynamic> evt, String peerId) async {
if (evt['displays'] != null) {
List<dynamic> displays = json.decode(evt['displays']);
List<Display> newDisplays = [];
for (int i = 0; i < displays.length; ++i) {
Map<String, dynamic> d0 = displays[i];
var d = Display();
d.x = d0['x'].toDouble();
d.y = d0['y'].toDouble();
d.width = d0['width'];
d.height = d0['height'];
d.cursorEmbedded = d0['cursor_embedded'] == 1;
newDisplays.add(d);
newDisplays.add(evtToDisplay(displays[i]));
}
_pi.displays = newDisplays;
stateGlobal.displaysCount.value = _pi.displays.length;
@@ -1712,12 +1717,17 @@ class FFI {
}
}
const kInvalidResolutionValue = -1;
const kVirtualDisplayResolutionValue = 0;
class Display {
double x = 0;
double y = 0;
int width = 0;
int height = 0;
bool cursorEmbedded = false;
int originalWidth = kInvalidResolutionValue;
int originalHeight = kInvalidResolutionValue;
Display() {
width = (isDesktop || isWebDesktop)
@@ -1740,6 +1750,15 @@ class Display {
other.width == width &&
other.height == height &&
other.cursorEmbedded == cursorEmbedded;
bool get isOriginalResolutionSet =>
originalWidth != kInvalidResolutionValue &&
originalHeight != kInvalidResolutionValue;
bool get isVirtualDisplayResolution =>
originalWidth == kVirtualDisplayResolutionValue &&
originalHeight == kVirtualDisplayResolutionValue;
bool get isOriginalResolution =>
width == originalWidth && height == originalHeight;
}
class Resolution {

View File

@@ -12,12 +12,14 @@ class StateGlobal {
bool _maximize = false;
bool grabKeyboard = false;
final RxBool _showTabBar = true.obs;
final RxBool _showResizeEdge = true.obs;
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
final RxBool showRemoteMenuBar = false.obs;
final RxInt displaysCount = 0.obs;
// Use for desktop -> remote toolbar -> resolution
final Map<String, Map<int, String?>> _lastResolutionGroupValues = {};
int get windowId => _windowId;
bool get fullscreen => _fullscreen;
bool get maximize => _maximize;
@@ -26,6 +28,22 @@ class StateGlobal {
RxDouble get resizeEdgeSize => _resizeEdgeSize;
RxDouble get windowBorderWidth => _windowBorderWidth;
resetLastResolutionGroupValues(String peerId) {
_lastResolutionGroupValues[peerId] = {};
}
setLastResolutionGroupValue(
String peerId, int currentDisplay, String? value) {
if (!_lastResolutionGroupValues.containsKey(peerId)) {
_lastResolutionGroupValues[peerId] = {};
}
_lastResolutionGroupValues[peerId]![currentDisplay] = value;
}
String? getLastResolutionGroupValue(String peerId, int currentDisplay) {
return _lastResolutionGroupValues[peerId]?[currentDisplay];
}
setWindowId(int id) => _windowId = id;
setMaximize(bool v) {
if (_maximize != v && !_fullscreen) {
@@ -33,12 +51,12 @@ class StateGlobal {
_resizeEdgeSize.value = _maximize ? kMaximizeEdgeSize : kWindowEdgeSize;
}
}
setFullscreen(bool v) {
if (_fullscreen != v) {
_fullscreen = v;
_showTabBar.value = !_fullscreen;
_resizeEdgeSize.value =
fullscreen
_resizeEdgeSize.value = fullscreen
? kFullScreenEdgeSize
: _maximize
? kMaximizeEdgeSize