Merge pull request #4411 from fufesou/feat/remember_custom_resolution
Feat/remember custom resolution
This commit is contained in:
commit
c63e757b76
@ -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);
|
||||
|
@ -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,192 @@ 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();
|
||||
}
|
||||
|
||||
class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
String _groupValue = '';
|
||||
Resolution? _localResolution;
|
||||
|
||||
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);
|
||||
final visible = ffiModel.keyboard && resolutions.length > 1;
|
||||
if (!visible) return Offstage();
|
||||
_groupValue = '${display.width}x${display.height}';
|
||||
_getLocalResolution();
|
||||
final showOriginalBtn =
|
||||
display.isOriginalResolutionSet && !display.isOriginalResolution;
|
||||
final showFitLocalBtn = !_isRemoteResolutionFitLocal();
|
||||
|
||||
return _SubmenuButton(
|
||||
ffi: widget.ffi,
|
||||
menuChildren: <Widget>[
|
||||
_OriginalResolutionMenuButton(showOriginalBtn),
|
||||
_FitLocalResolutionMenuButton(showFitLocalBtn),
|
||||
// _customResolutionMenuButton(isVirtualDisplay),
|
||||
_menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay),
|
||||
] +
|
||||
_supportedResolutionMenuButtons(),
|
||||
child: Text(translate("Resolution")),
|
||||
);
|
||||
}
|
||||
|
||||
_menuDivider(
|
||||
bool showOriginalBtn, bool showFitLocalBtn, bool isVirtualDisplay) {
|
||||
return Offstage(
|
||||
// offstage: !(showOriginalBtn || showFitLocalBtn || isVirtualDisplay),
|
||||
offstage: !(showOriginalBtn || showFitLocalBtn),
|
||||
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 {
|
||||
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 _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}'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -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) {
|
||||
@ -533,20 +530,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 +1714,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 +1747,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 {
|
||||
|
@ -41,6 +41,7 @@ message DisplayInfo {
|
||||
string name = 5;
|
||||
bool online = 6;
|
||||
bool cursor_embedded = 7;
|
||||
Resolution original_resolution = 8;
|
||||
}
|
||||
|
||||
message PortForward {
|
||||
@ -444,6 +445,8 @@ message SwitchDisplay {
|
||||
int32 height = 5;
|
||||
bool cursor_embedded = 6;
|
||||
SupportedResolutions resolutions = 7;
|
||||
// Do not care about the origin point for now.
|
||||
Resolution original_resolution = 8;
|
||||
}
|
||||
|
||||
message PermissionInfo {
|
||||
@ -501,6 +504,7 @@ message OptionMessage {
|
||||
SupportedDecoding supported_decoding = 10;
|
||||
int32 custom_fps = 11;
|
||||
BoolOption disable_keyboard = 12;
|
||||
Resolution custom_resolution = 13;
|
||||
}
|
||||
|
||||
message TestDelay {
|
||||
|
@ -105,12 +105,9 @@ macro_rules! serde_field_string {
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let s: &str = de::Deserialize::deserialize(deserializer).unwrap_or_default();
|
||||
Ok(if s.is_empty() {
|
||||
Self::$default_func()
|
||||
} else {
|
||||
s.to_owned()
|
||||
})
|
||||
let s: String =
|
||||
de::Deserialize::deserialize(deserializer).unwrap_or(Self::$default_func());
|
||||
Ok(s)
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -191,6 +188,12 @@ pub struct Config2 {
|
||||
pub options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Resolution {
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct PeerConfig {
|
||||
#[serde(default, deserialize_with = "deserialize_vec_u8")]
|
||||
@ -246,6 +249,13 @@ pub struct PeerConfig {
|
||||
#[serde(flatten)]
|
||||
pub view_only: ViewOnly,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "deserialize_option_resolution"
|
||||
)]
|
||||
pub custom_resolution: Option<Resolution>,
|
||||
|
||||
// The other scalar value must before this
|
||||
#[serde(default, deserialize_with = "PeerConfig::deserialize_options")]
|
||||
pub options: HashMap<String, String>, // not use delete to represent default values
|
||||
@ -1486,6 +1496,7 @@ deserialize_default!(deserialize_option_string, Option<String>);
|
||||
deserialize_default!(deserialize_hashmap_string_string, HashMap<String, String>);
|
||||
deserialize_default!(deserialize_hashmap_string_bool, HashMap<String, bool>);
|
||||
deserialize_default!(deserialize_hashmap_string_configoidcprovider, HashMap<String, ConfigOidcProvider>);
|
||||
deserialize_default!(deserialize_option_resolution, Option<Resolution>);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@ -1534,4 +1545,34 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_config_deserialize() {
|
||||
let default_peer_config = toml::from_str::<PeerConfig>("").unwrap();
|
||||
// test custom_resolution
|
||||
{
|
||||
let wrong_type_str = r#"
|
||||
view_style = "adaptive"
|
||||
scroll_style = "scrollbar"
|
||||
custom_resolution = true
|
||||
"#;
|
||||
let mut compare_config = default_peer_config.clone();
|
||||
compare_config.view_style = "adaptive".to_string();
|
||||
compare_config.scroll_style = "scrollbar".to_string();
|
||||
let cfg = toml::from_str::<PeerConfig>(wrong_type_str);
|
||||
assert_eq!(cfg, Ok(compare_config), "Failed to test wrong_type_str");
|
||||
|
||||
let wrong_field_str = r#"
|
||||
[custom_resolution]
|
||||
w = 1920
|
||||
h = 1080
|
||||
hello = "world"
|
||||
[ui_flutter]
|
||||
"#;
|
||||
let mut compare_config = default_peer_config.clone();
|
||||
compare_config.custom_resolution = Some(Resolution { w: 1920, h: 1080 });
|
||||
let cfg = toml::from_str::<PeerConfig>(wrong_field_str);
|
||||
assert_eq!(cfg, Ok(compare_config), "Failed to test wrong_field_str");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ def main():
|
||||
|
||||
|
||||
def expand():
|
||||
for fn in glob.glob('./src/lang/*'):
|
||||
for fn in glob.glob('./src/lang/*.rs'):
|
||||
lang = os.path.basename(fn)[:-3]
|
||||
if lang in ['en','template']: continue
|
||||
print(lang)
|
||||
|
@ -31,9 +31,11 @@ use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, Context},
|
||||
bail,
|
||||
config::{Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT},
|
||||
config::{
|
||||
Config, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT,
|
||||
},
|
||||
get_version_number, log,
|
||||
message_proto::{option_message::BoolOption, *},
|
||||
message_proto::{option_message::BoolOption, Resolution as ProtoResolution, *},
|
||||
protobuf::Message as _,
|
||||
rand,
|
||||
rendezvous_proto::*,
|
||||
@ -1400,6 +1402,16 @@ impl LoginConfigHandler {
|
||||
msg.disable_clipboard = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
if let Some(r) = self.get_custom_resolution() {
|
||||
if r.0 > 0 && r.1 > 0 {
|
||||
msg.custom_resolution = Some(ProtoResolution {
|
||||
width: r.0,
|
||||
height: r.1,
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
}
|
||||
}
|
||||
msg.supported_decoding =
|
||||
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(Some(&self.id)));
|
||||
n += 1;
|
||||
@ -1571,6 +1583,18 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_custom_resolution(&self) -> Option<(i32, i32)> {
|
||||
self.config.custom_resolution.as_ref().map(|r| (r.w, r.h))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_resolution(&mut self, wh: Option<(i32, i32)>) {
|
||||
let mut config = self.load_config();
|
||||
config.custom_resolution = wh.map(|r| Resolution { w: r.0, h: r.1 });
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Get user name.
|
||||
/// Return the name of the given peer. If the peer has no name, return the name in the config.
|
||||
///
|
||||
|
@ -1211,6 +1211,14 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
s.cursor_embedded,
|
||||
);
|
||||
}
|
||||
let custom_resolution = if s.width != s.original_resolution.width
|
||||
|| s.height != s.original_resolution.height
|
||||
{
|
||||
Some((s.width, s.height))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.handler.set_custom_resolution(custom_resolution);
|
||||
}
|
||||
Some(misc::Union::CloseReason(c)) => {
|
||||
self.handler.msgbox("error", "Connection Error", &c, "");
|
||||
|
@ -1,10 +1,10 @@
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::platform::breakdown_callback;
|
||||
use hbb_common::log;
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::platform::register_breakdown_handler;
|
||||
use hbb_common::{allow_err, log};
|
||||
|
||||
/// shared by flutter and sciter main function
|
||||
///
|
||||
@ -270,7 +270,7 @@ fn init_plugins(args: &Vec<String>) {
|
||||
crate::plugin::init();
|
||||
}
|
||||
} else if "--service" == (&args[0] as &str) {
|
||||
allow_err!(crate::plugin::remove_uninstalled());
|
||||
hbb_common::allow_err!(crate::plugin::remove_uninstalled());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,6 +286,8 @@ impl FlutterHandler {
|
||||
h.insert("width", d.width);
|
||||
h.insert("height", d.height);
|
||||
h.insert("cursor_embedded", if d.cursor_embedded { 1 } else { 0 });
|
||||
h.insert("original_width", d.original_resolution.width);
|
||||
h.insert("original_height", d.original_resolution.height);
|
||||
msg_vec.push(h);
|
||||
}
|
||||
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
|
||||
@ -618,6 +620,14 @@ impl InvokeUiSession for FlutterHandler {
|
||||
.to_string(),
|
||||
),
|
||||
("resolutions", &resolutions),
|
||||
(
|
||||
"original_width",
|
||||
&display.original_resolution.width.to_string(),
|
||||
),
|
||||
(
|
||||
"original_height",
|
||||
&display.original_resolution.height.to_string(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -880,6 +880,18 @@ pub fn main_handle_relay_id(id: String) -> String {
|
||||
handle_relay_id(id)
|
||||
}
|
||||
|
||||
pub fn main_get_current_display() -> SyncReturn<String> {
|
||||
let display_info = match crate::video_service::get_current_display() {
|
||||
Ok((_, _, display)) => serde_json::to_string(&HashMap::from([
|
||||
("w", display.width()),
|
||||
("h", display.height()),
|
||||
]))
|
||||
.unwrap_or_default(),
|
||||
Err(..) => "".to_string(),
|
||||
};
|
||||
SyncReturn(display_info)
|
||||
}
|
||||
|
||||
pub fn session_add_port_forward(
|
||||
id: String,
|
||||
local_port: i32,
|
||||
@ -1426,10 +1438,10 @@ pub fn plugin_event(_id: String, _peer: String, _event: Vec<u8>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_register_event_stream(id: String, event2ui: StreamSink<EventToUI>) {
|
||||
pub fn plugin_register_event_stream(_id: String, _event2ui: StreamSink<EventToUI>) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
{
|
||||
crate::plugin::native_handlers::session::session_register_event_stream(id, event2ui);
|
||||
crate::plugin::native_handlers::session::session_register_event_stream(_id, _event2ui);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1577,16 +1589,16 @@ pub fn plugin_list_reload() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_install(id: String, b: bool) {
|
||||
pub fn plugin_install(_id: String, _b: bool) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
if b {
|
||||
if _b {
|
||||
if let Err(e) = crate::plugin::install_plugin(&id) {
|
||||
log::error!("Failed to install plugin '{}': {}", id, e);
|
||||
}
|
||||
} else {
|
||||
crate::plugin::uninstall_plugin(&id, true);
|
||||
crate::plugin::uninstall_plugin(&_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "启用"),
|
||||
("Disable", "禁用"),
|
||||
("Options", "选项"),
|
||||
("resolution_original_tip", "原始分辨率"),
|
||||
("resolution_fit_local_tip", "适应本地分辨率"),
|
||||
("resolution_custom_tip", "自定义分辨率"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Aktivieren"),
|
||||
("Disable", "Deaktivieren"),
|
||||
("Options", "Einstellungen"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -66,5 +66,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("xorg_not_found_text_tip", "Please install Xorg"),
|
||||
("no_desktop_title_tip", "No desktop is available"),
|
||||
("no_desktop_text_tip", "Please install GNOME desktop"),
|
||||
("resolution_original_tip", "Original resolution"),
|
||||
("resolution_fit_local_tip", "Fit local resolution"),
|
||||
("resolution_custom_tip", "Custom resolution"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Habilitar"),
|
||||
("Disable", "Inhabilitar"),
|
||||
("Options", "Opciones"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "فعال کردن"),
|
||||
("Disable", "غیر فعال کردن"),
|
||||
("Options", "گزینه ها"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Activé"),
|
||||
("Disable", "Desactivé"),
|
||||
("Options", "Options"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Abilita"),
|
||||
("Disable", "Disabilita"),
|
||||
("Options", "Opzioni"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Activeer"),
|
||||
("Disable", "Deactiveer"),
|
||||
("Options", "Opties"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Włącz"),
|
||||
("Disable", "Wyłącz"),
|
||||
("Options", "Opcje"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Habilitar"),
|
||||
("Disable", "Desabilitar"),
|
||||
("Options", "Opções"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", "Включить"),
|
||||
("Disable", "Отключить"),
|
||||
("Options", "Настройки"),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ use windows_service::{
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
// This string is defined here.
|
||||
// https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40
|
||||
const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0";
|
||||
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
@ -1831,21 +1835,25 @@ pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn str_to_device_name(name: &str) -> [u16; 32] {
|
||||
let mut device_name: Vec<u16> = wide_string(name);
|
||||
if device_name.len() < 32 {
|
||||
device_name.resize(32, 0);
|
||||
}
|
||||
let mut result = [0; 32];
|
||||
result.copy_from_slice(&device_name[..32]);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn resolutions(name: &str) -> Vec<Resolution> {
|
||||
unsafe {
|
||||
let mut dm: DEVMODEW = std::mem::zeroed();
|
||||
let wname = wide_string(name);
|
||||
let len = if wname.len() <= dm.dmDeviceName.len() {
|
||||
wname.len()
|
||||
} else {
|
||||
dm.dmDeviceName.len()
|
||||
};
|
||||
std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len);
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
let mut v = vec![];
|
||||
let mut num = 0;
|
||||
let device_name = str_to_device_name(name);
|
||||
loop {
|
||||
if EnumDisplaySettingsW(NULL as _, num, &mut dm) == 0 {
|
||||
if EnumDisplaySettingsW(device_name.as_ptr(), num, &mut dm) == 0 {
|
||||
break;
|
||||
}
|
||||
let r = Resolution {
|
||||
@ -1866,8 +1874,8 @@ pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
||||
unsafe {
|
||||
let mut dm: DEVMODEW = std::mem::zeroed();
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
let wname = wide_string(name);
|
||||
if EnumDisplaySettingsW(wname.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) == 0 {
|
||||
let device_name = str_to_device_name(name);
|
||||
if EnumDisplaySettingsW(device_name.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) == 0 {
|
||||
bail!(
|
||||
"failed to get currrent resolution, errno={}",
|
||||
GetLastError()
|
||||
@ -1882,25 +1890,57 @@ pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_device_name(device_name: &str, name: &str) -> bool {
|
||||
if name.len() == device_name.len() {
|
||||
name == device_name
|
||||
} else if name.len() > device_name.len() {
|
||||
false
|
||||
} else {
|
||||
&device_name[..name.len()] == name && device_name.as_bytes()[name.len() as usize] == 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_virtual_display(name: &str) -> ResultType<bool> {
|
||||
let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() };
|
||||
dd.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as DWORD;
|
||||
let mut i_dev_num = 0;
|
||||
loop {
|
||||
let result = unsafe { EnumDisplayDevicesW(null_mut(), i_dev_num, &mut dd, 0) };
|
||||
if result == 0 {
|
||||
break;
|
||||
}
|
||||
if let Ok(device_name) = String::from_utf16(&dd.DeviceName) {
|
||||
if is_device_name(&device_name, name) {
|
||||
return match std::string::String::from_utf16(&dd.DeviceString) {
|
||||
Ok(s) => Ok(&s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING),
|
||||
Err(e) => bail!("convert the device string of '{}' to string: {}", name, e),
|
||||
};
|
||||
}
|
||||
}
|
||||
i_dev_num += 1;
|
||||
}
|
||||
bail!("No such display '{}'", name)
|
||||
}
|
||||
|
||||
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
|
||||
let device_name = str_to_device_name(name);
|
||||
unsafe {
|
||||
let mut dm: DEVMODEW = std::mem::zeroed();
|
||||
if FALSE == EnumDisplaySettingsW(NULL as _, ENUM_CURRENT_SETTINGS, &mut dm) {
|
||||
if FALSE == EnumDisplaySettingsW(device_name.as_ptr() as _, ENUM_CURRENT_SETTINGS, &mut dm)
|
||||
{
|
||||
bail!("EnumDisplaySettingsW failed, errno={}", GetLastError());
|
||||
}
|
||||
let wname = wide_string(name);
|
||||
let len = if wname.len() <= dm.dmDeviceName.len() {
|
||||
wname.len()
|
||||
} else {
|
||||
dm.dmDeviceName.len()
|
||||
};
|
||||
std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len);
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
// dmPelsWidth and dmPelsHeight is the same to width and height
|
||||
// Because this process is running in dpi awareness mode.
|
||||
if dm.dmPelsWidth == width as u32 && dm.dmPelsHeight == height as u32 {
|
||||
return Ok(());
|
||||
}
|
||||
dm.dmPelsWidth = width as _;
|
||||
dm.dmPelsHeight = height as _;
|
||||
dm.dmFields = DM_PELSHEIGHT | DM_PELSWIDTH;
|
||||
let res = ChangeDisplaySettingsExW(
|
||||
wname.as_ptr(),
|
||||
device_name.as_ptr(),
|
||||
&mut dm,
|
||||
NULL as _,
|
||||
CDS_UPDATEREGISTRY | CDS_GLOBAL | CDS_RESET,
|
||||
|
@ -8,12 +8,10 @@ use std::{
|
||||
use bytes::Bytes;
|
||||
|
||||
pub use connection::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::config::Config2;
|
||||
use hbb_common::tcp::{self, new_listener};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, Context},
|
||||
anyhow::Context,
|
||||
bail,
|
||||
config::{Config, CONNECT_TIMEOUT, RELAY_PORT},
|
||||
log,
|
||||
@ -25,6 +23,8 @@ use hbb_common::{
|
||||
timeout, tokio, ResultType, Stream,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::{anyhow::anyhow, config::Config2};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use service::ServiceTmpl;
|
||||
use service::{GenericService, Service, Subscriber};
|
||||
|
||||
|
@ -179,8 +179,6 @@ pub struct Connection {
|
||||
#[cfg(windows)]
|
||||
portable: PortableState,
|
||||
from_switch: bool,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
origin_resolution: HashMap<String, Resolution>,
|
||||
voice_call_request_timestamp: Option<NonZeroI64>,
|
||||
audio_input_device_before_voice_call: Option<String>,
|
||||
options_in_login: Option<OptionMessage>,
|
||||
@ -306,8 +304,6 @@ impl Connection {
|
||||
#[cfg(windows)]
|
||||
portable: Default::default(),
|
||||
from_switch: false,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
origin_resolution: Default::default(),
|
||||
audio_sender: None,
|
||||
voice_call_request_timestamp: None,
|
||||
audio_input_device_before_voice_call: None,
|
||||
@ -631,15 +627,18 @@ impl Connection {
|
||||
conn.post_conn_audit(json!({
|
||||
"action": "close",
|
||||
}));
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
conn.reset_resolution();
|
||||
ALIVE_CONNS.lock().unwrap().retain(|&c| c != id);
|
||||
let mut active_conns_lock = ALIVE_CONNS.lock().unwrap();
|
||||
active_conns_lock.retain(|&c| c != id);
|
||||
if let Some(s) = conn.server.upgrade() {
|
||||
let mut s = s.write().unwrap();
|
||||
s.remove_connection(&conn.inner);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
try_stop_record_cursor_pos();
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if active_conns_lock.is_empty() {
|
||||
video_service::reset_resolutions();
|
||||
}
|
||||
log::info!("#{} connection loop exited", id);
|
||||
}
|
||||
|
||||
@ -1044,6 +1043,8 @@ impl Connection {
|
||||
})
|
||||
.into();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
video_service::try_reset_current_display();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
pi.resolutions = Some(SupportedResolutions {
|
||||
resolutions: video_service::get_current_display_name()
|
||||
@ -1893,25 +1894,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::ChangeResolution(r)) => {
|
||||
if self.keyboard {
|
||||
if let Ok(name) = video_service::get_current_display_name() {
|
||||
if let Ok(current) = crate::platform::current_resolution(&name) {
|
||||
if let Err(e) = crate::platform::change_resolution(
|
||||
&name,
|
||||
r.width as _,
|
||||
r.height as _,
|
||||
) {
|
||||
log::error!("change resolution failed:{:?}", e);
|
||||
} else {
|
||||
if !self.origin_resolution.contains_key(&name) {
|
||||
self.origin_resolution.insert(name, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(misc::Union::ChangeResolution(r)) => self.change_resolution(&r),
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::PluginRequest(p)) => {
|
||||
@ -1953,6 +1936,25 @@ impl Connection {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn change_resolution(&mut self, r: &Resolution) {
|
||||
if self.keyboard {
|
||||
if let Ok(name) = video_service::get_current_display_name() {
|
||||
if let Err(e) =
|
||||
crate::platform::change_resolution(&name, r.width as _, r.height as _)
|
||||
{
|
||||
log::error!(
|
||||
"Failed to change resolution '{}' to ({},{}):{:?}",
|
||||
&name,
|
||||
r.width,
|
||||
r.height,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_voice_call(&mut self, accepted: bool) {
|
||||
if let Some(ts) = self.voice_call_request_timestamp.take() {
|
||||
let msg = new_voice_call_response(ts.get(), accepted);
|
||||
@ -2147,6 +2149,12 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(custom_resolution) = o.custom_resolution.as_ref() {
|
||||
if custom_resolution.width > 0 && custom_resolution.height > 0 {
|
||||
self.change_resolution(&custom_resolution);
|
||||
}
|
||||
}
|
||||
if self.keyboard {
|
||||
if let Ok(q) = o.block_input.enum_value() {
|
||||
match q {
|
||||
@ -2262,20 +2270,6 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn reset_resolution(&self) {
|
||||
self.origin_resolution
|
||||
.iter()
|
||||
.map(|(name, r)| {
|
||||
if let Err(e) =
|
||||
crate::platform::change_resolution(&name, r.width as _, r.height as _)
|
||||
{
|
||||
log::error!("change resolution failed:{:?}", e);
|
||||
}
|
||||
})
|
||||
.count();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn release_pressed_modifiers(&mut self) {
|
||||
for modifier in self.pressed_modifiers.iter() {
|
||||
|
@ -25,9 +25,12 @@ use crate::virtual_display_manager;
|
||||
use crate::{platform::windows::is_process_consent_running, privacy_win_mag};
|
||||
#[cfg(windows)]
|
||||
use hbb_common::get_version_number;
|
||||
use hbb_common::tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex as TokioMutex,
|
||||
use hbb_common::{
|
||||
protobuf::MessageField,
|
||||
tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex as TokioMutex,
|
||||
},
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
use scrap::Capturer;
|
||||
@ -62,8 +65,70 @@ lazy_static::lazy_static! {
|
||||
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
|
||||
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
|
||||
pub static ref LAST_SYNC_DISPLAYS: Arc<RwLock<Vec<DisplayInfo>>> = Default::default();
|
||||
static ref ORIGINAL_RESOLUTIONS: Arc<RwLock<HashMap<String, (i32, i32)>>> = Default::default();
|
||||
}
|
||||
|
||||
// Not virtual display
|
||||
#[inline]
|
||||
fn set_original_resolution_(display_name: &str, wh: (i32, i32)) -> (i32, i32) {
|
||||
let mut original_resolutions = ORIGINAL_RESOLUTIONS.write().unwrap();
|
||||
match original_resolutions.get(display_name) {
|
||||
Some(r) => r.clone(),
|
||||
None => {
|
||||
original_resolutions.insert(display_name.to_owned(), wh.clone());
|
||||
wh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not virtual display
|
||||
#[inline]
|
||||
fn get_original_resolution_(display_name: &str) -> Option<(i32, i32)> {
|
||||
ORIGINAL_RESOLUTIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(display_name)
|
||||
.map(|r| r.clone())
|
||||
}
|
||||
|
||||
// Not virtual display
|
||||
#[inline]
|
||||
fn get_or_set_original_resolution_(display_name: &str, wh: (i32, i32)) -> (i32, i32) {
|
||||
let r = get_original_resolution_(display_name);
|
||||
if let Some(r) = r {
|
||||
return r;
|
||||
}
|
||||
set_original_resolution_(display_name, wh)
|
||||
}
|
||||
|
||||
// Not virtual display
|
||||
#[inline]
|
||||
fn update_get_original_resolution_(display_name: &str, w: usize, h: usize) -> Resolution {
|
||||
let wh = get_or_set_original_resolution_(display_name, (w as _, h as _));
|
||||
Resolution {
|
||||
width: wh.0,
|
||||
height: wh.1,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn reset_resolutions() {
|
||||
for (name, (w, h)) in ORIGINAL_RESOLUTIONS.read().unwrap().iter() {
|
||||
if let Err(e) = crate::platform::change_resolution(name, *w as _, *h as _) {
|
||||
log::error!(
|
||||
"Failed to reset resolution of display '{}' to ({},{}): {}",
|
||||
name,
|
||||
w,
|
||||
h,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_capturer_mag_supported() -> bool {
|
||||
#[cfg(windows)]
|
||||
return scrap::CapturerMag::is_supported();
|
||||
@ -71,22 +136,27 @@ fn is_capturer_mag_supported() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn capture_cursor_embedded() -> bool {
|
||||
scrap::is_cursor_embedded()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option<Instant>) {
|
||||
FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_privacy_mode_conn_id(conn_id: i32) {
|
||||
*PRIVACY_MODE_CONN_ID.lock().unwrap() = conn_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_privacy_mode_conn_id() -> i32 {
|
||||
*PRIVACY_MODE_CONN_ID.lock().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_privacy_mode_supported() -> bool {
|
||||
#[cfg(windows)]
|
||||
return *IS_CAPTURER_MAGNIFIER_SUPPORTED
|
||||
@ -491,6 +561,8 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
if *SWITCH.lock().unwrap() {
|
||||
log::debug!("Broadcasting display switch");
|
||||
let mut misc = Misc::new();
|
||||
let display_name = get_current_display_name().unwrap_or_default();
|
||||
let original_resolution = get_original_resolution(&display_name, c.width, c.height);
|
||||
misc.set_switch_display(SwitchDisplay {
|
||||
display: c.current as _,
|
||||
x: c.origin.0 as _,
|
||||
@ -500,12 +572,15 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
cursor_embedded: capture_cursor_embedded(),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
resolutions: Some(SupportedResolutions {
|
||||
resolutions: get_current_display_name()
|
||||
.map(|name| crate::platform::resolutions(&name))
|
||||
.unwrap_or(vec![]),
|
||||
resolutions: if display_name.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
crate::platform::resolutions(&display_name)
|
||||
},
|
||||
..SupportedResolutions::default()
|
||||
})
|
||||
.into(),
|
||||
original_resolution,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
@ -820,6 +895,38 @@ pub fn handle_one_frame_encoded(
|
||||
Ok(send_conn_ids)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField<Resolution> {
|
||||
Some(if is_virtual_display(&display_name) {
|
||||
Resolution {
|
||||
width: 0,
|
||||
height: 0,
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
update_get_original_resolution_(&display_name, w, h)
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_virtual_display(name: &str) -> bool {
|
||||
match crate::platform::windows::is_virtual_display(&name) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
log::error!("Failed to check is virtual display for '{}': {}", &name, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn is_virtual_display(_name: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
|
||||
let mut displays = Vec::new();
|
||||
let mut primary = 0;
|
||||
@ -827,14 +934,17 @@ pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
|
||||
if d.is_primary() {
|
||||
primary = i;
|
||||
}
|
||||
let display_name = d.name();
|
||||
let original_resolution = get_original_resolution(&display_name, d.width(), d.height());
|
||||
displays.push(DisplayInfo {
|
||||
x: d.origin().0 as _,
|
||||
y: d.origin().1 as _,
|
||||
width: d.width() as _,
|
||||
height: d.height() as _,
|
||||
name: d.name(),
|
||||
name: display_name,
|
||||
online: d.is_online(),
|
||||
cursor_embedded: false,
|
||||
original_resolution,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
@ -853,6 +963,15 @@ pub fn is_inited_msg() -> Option<Message> {
|
||||
None
|
||||
}
|
||||
|
||||
// switch to primary display if long time (30 seconds) no users
|
||||
#[inline]
|
||||
pub fn try_reset_current_display() {
|
||||
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
|
||||
}
|
||||
*LAST_ACTIVE.lock().unwrap() = time::Instant::now();
|
||||
}
|
||||
|
||||
pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@ -860,10 +979,6 @@ pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
return super::wayland::get_displays().await;
|
||||
}
|
||||
}
|
||||
// switch to primary display if long time (30 seconds) no users
|
||||
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
|
||||
}
|
||||
Ok(get_displays_2(&try_get_displays()?))
|
||||
}
|
||||
|
||||
@ -957,6 +1072,8 @@ pub fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
get_current_display_2(try_get_displays()?)
|
||||
}
|
||||
|
||||
// `try_reset_current_display` is needed because `get_displays` may change the current display,
|
||||
// which may cause the mismatch of current display and the current display name.
|
||||
pub fn get_current_display_name() -> ResultType<String> {
|
||||
Ok(get_current_display_2(try_get_displays()?)?.2.name())
|
||||
}
|
||||
|
@ -88,10 +88,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
|
||||
pub fn is_port_forward(&self) -> bool {
|
||||
let conn_type = self.lc
|
||||
.read()
|
||||
.unwrap()
|
||||
.conn_type;
|
||||
let conn_type = self.lc.read().unwrap().conn_type;
|
||||
conn_type == ConnType::PORT_FORWARD || conn_type == ConnType::RDP
|
||||
}
|
||||
|
||||
@ -832,6 +829,11 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_resolution(&mut self, wh: Option<(i32, i32)>) {
|
||||
self.lc.write().unwrap().set_custom_resolution(wh);
|
||||
}
|
||||
|
||||
pub fn change_resolution(&self, width: i32, height: i32) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_change_resolution(Resolution {
|
||||
|
Loading…
x
Reference in New Issue
Block a user