Merge branch 'rustdesk:master' into chat
This commit is contained in:
commit
717a7e9e03
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -3558,9 +3558,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "magnum-opus"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/rustdesk/magnum-opus#79be072c939168e907fe851690759dcfd6a326af"
|
||||
source = "git+https://github.com/rustdesk/magnum-opus#5cd2bf989c148662fa3a2d9d539a71d71fd1d256"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"pkg-config",
|
||||
"target_build_utils",
|
||||
]
|
||||
|
||||
@ -5431,6 +5432,7 @@ dependencies = [
|
||||
"log",
|
||||
"ndk 0.7.0",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"quest",
|
||||
"repng",
|
||||
"serde 1.0.163",
|
||||
|
@ -32,6 +32,7 @@ mediacodec = ["scrap/mediacodec"]
|
||||
linux_headless = ["pam" ]
|
||||
virtual_display_driver = ["virtual_display"]
|
||||
plugin_framework = []
|
||||
linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -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,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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -1446,7 +1456,7 @@ impl ConfigOidc {
|
||||
|
||||
fn _load_env(mut self) -> Self {
|
||||
use std::env;
|
||||
for (k, mut v) in &mut self.providers {
|
||||
for (k, v) in &mut self.providers {
|
||||
if let Ok(client_id) = env::var(format!("OIDC-{}-CLIENT-ID", k.to_uppercase())) {
|
||||
v.client_id = client_id;
|
||||
}
|
||||
@ -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 cfg_to_compare = default_peer_config.clone();
|
||||
cfg_to_compare.view_style = "adaptive".to_string();
|
||||
cfg_to_compare.scroll_style = "scrollbar".to_string();
|
||||
let cfg = toml::from_str::<PeerConfig>(wrong_type_str);
|
||||
assert_eq!(cfg, Ok(cfg_to_compare), "Failed to test wrong_type_str");
|
||||
|
||||
let wrong_field_str = r#"
|
||||
[custom_resolution]
|
||||
w = 1920
|
||||
h = 1080
|
||||
hello = "world"
|
||||
[ui_flutter]
|
||||
"#;
|
||||
let mut cfg_to_compare = default_peer_config.clone();
|
||||
cfg_to_compare.custom_resolution = Some(Resolution { w: 1920, h: 1080 });
|
||||
let cfg = toml::from_str::<PeerConfig>(wrong_field_str);
|
||||
assert_eq!(cfg, Ok(cfg_to_compare), "Failed to test wrong_field_str");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ edition = "2018"
|
||||
[features]
|
||||
wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing"]
|
||||
mediacodec = ["ndk"]
|
||||
linux-pkg-config = ["dep:pkg-config"]
|
||||
|
||||
[dependencies]
|
||||
block = "0.1"
|
||||
@ -43,6 +44,7 @@ quest = "0.3"
|
||||
[build-dependencies]
|
||||
target_build_utils = "0.3"
|
||||
bindgen = "0.65"
|
||||
pkg-config = { version = "0.3.27", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
dbus = { version = "0.9", optional = true }
|
||||
|
@ -1,8 +1,28 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
println,
|
||||
};
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "linux-pkg-config"))]
|
||||
fn link_pkg_config(name: &str) -> Vec<PathBuf> {
|
||||
// sometimes an override is needed
|
||||
let pc_name = match name {
|
||||
"libvpx" => "vpx",
|
||||
_ => name,
|
||||
};
|
||||
let lib = pkg_config::probe_library(pc_name)
|
||||
.expect(format!(
|
||||
"unable to find '{pc_name}' development headers with pkg-config (feature linux-pkg-config is enabled).
|
||||
try installing '{pc_name}-dev' from your system package manager.").as_str());
|
||||
|
||||
lib.include_paths
|
||||
}
|
||||
#[cfg(not(all(target_os = "linux", feature = "linux-pkg-config")))]
|
||||
fn link_pkg_config(_name: &str) -> Vec<PathBuf> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Link vcppkg package.
|
||||
fn link_vcpkg(mut path: PathBuf, name: &str) -> PathBuf {
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
@ -102,8 +122,16 @@ fn link_homebrew_m1(name: &str) -> PathBuf {
|
||||
}
|
||||
|
||||
/// Find package. By default, it will try to find vcpkg first, then homebrew(currently only for Mac M1).
|
||||
/// If building for linux and feature "linux-pkg-config" is enabled, will try to use pkg-config
|
||||
/// unless check fails (e.g. NO_PKG_CONFIG_libyuv=1)
|
||||
fn find_package(name: &str) -> Vec<PathBuf> {
|
||||
if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") {
|
||||
let no_pkg_config_var_name = format!("NO_PKG_CONFIG_{name}");
|
||||
println!("cargo:rerun-if-env-changed={no_pkg_config_var_name}");
|
||||
if cfg!(all(target_os = "linux", feature = "linux-pkg-config"))
|
||||
&& std::env::var(no_pkg_config_var_name).as_deref() != Ok("1") {
|
||||
|
||||
link_pkg_config(name)
|
||||
} else if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") {
|
||||
vec![link_vcpkg(vcpkg_root.into(), name)]
|
||||
} else {
|
||||
// Try using homebrew
|
||||
|
@ -23,7 +23,7 @@ use hwcodec::{
|
||||
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
||||
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
||||
|
||||
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P;
|
||||
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_NV12;
|
||||
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
||||
const DEFAULT_GOP: i32 = i32::MAX;
|
||||
const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
||||
@ -332,6 +332,8 @@ pub fn check_config_process() {
|
||||
use hbb_common::sysinfo::{ProcessExt, System, SystemExt};
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Remove to avoid checking process errors
|
||||
// But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration
|
||||
HwCodecConfig::remove();
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(file_name) = exe.file_name().to_owned() {
|
||||
@ -344,9 +346,16 @@ pub fn check_config_process() {
|
||||
}
|
||||
}
|
||||
if let Ok(mut child) = std::process::Command::new(exe).arg(arg).spawn() {
|
||||
let second = 3;
|
||||
std::thread::sleep(std::time::Duration::from_secs(second));
|
||||
// kill: Different platforms have different results
|
||||
// wait up to 10 seconds
|
||||
for _ in 0..10 {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(Some(status)) = child.try_wait() {
|
||||
if status.success() {
|
||||
HwCodecConfig::refresh();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
allow_err!(child.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
match child.try_wait() {
|
||||
|
@ -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::*,
|
||||
@ -1351,7 +1353,7 @@ impl LoginConfigHandler {
|
||||
///
|
||||
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
||||
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
|
||||
if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD)
|
||||
if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@ -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;
|
||||
@ -1412,7 +1424,7 @@ impl LoginConfigHandler {
|
||||
}
|
||||
|
||||
pub fn get_option_message_after_login(&self) -> Option<OptionMessage> {
|
||||
if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD)
|
||||
if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@ -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.
|
||||
///
|
||||
@ -1689,7 +1713,7 @@ impl LoginConfigHandler {
|
||||
show_hidden: !self.get_option("remote_show_hidden").is_empty(),
|
||||
..Default::default()
|
||||
}),
|
||||
ConnType::PORT_FORWARD => lr.set_port_forward(PortForward {
|
||||
ConnType::PORT_FORWARD | ConnType::RDP => lr.set_port_forward(PortForward {
|
||||
host: self.port_forward.0.clone(),
|
||||
port: self.port_forward.1,
|
||||
..Default::default()
|
||||
|
@ -18,6 +18,7 @@ use hbb_common::protobuf::Message as _;
|
||||
use hbb_common::rendezvous_proto::ConnType;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::sleep;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
||||
#[cfg(windows)]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
@ -1211,6 +1212,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,21 @@ pub fn main_handle_relay_id(id: String) -> String {
|
||||
handle_relay_id(id)
|
||||
}
|
||||
|
||||
pub fn main_get_current_display() -> SyncReturn<String> {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
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(),
|
||||
};
|
||||
#[cfg(target_os = "ios")]
|
||||
let display_info = "".to_owned();
|
||||
SyncReturn(display_info)
|
||||
}
|
||||
|
||||
pub fn session_add_port_forward(
|
||||
id: String,
|
||||
local_port: i32,
|
||||
@ -1426,10 +1441,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 +1592,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", "Originalauflösung"),
|
||||
("resolution_fit_local_tip", "Lokale Auflösung anpassen"),
|
||||
("resolution_custom_tip", "Benutzerdefinierte Auflösung"),
|
||||
].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();
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."),
|
||||
("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."),
|
||||
("identical_file_tip", "This file is identical with the peer's one."),
|
||||
("show_monitors_tip", "Show monitors in toolbar."),
|
||||
("show_monitors_tip", "Show monitors in toolbar"),
|
||||
("enter_rustdesk_passwd_tip", "Enter RustDesk password"),
|
||||
("remember_rustdesk_passwd_tip", "Remember RustDesk password"),
|
||||
("login_linux_tip", "You need to login to remote Linux account to enable a X desktop session"),
|
||||
@ -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", "Resolución original"),
|
||||
("resolution_fit_local_tip", "Ajustar resolución local"),
|
||||
("resolution_custom_tip", "Resolución personalizada"),
|
||||
].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();
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you want to enter again?", "Voulez-vous participer à nouveau ?"),
|
||||
("Connection Error", "Erreur de connexion"),
|
||||
("Error", "Erreur"),
|
||||
("Reset by the peer", "La connexion a été fermée par le pair"),
|
||||
("Reset by the peer", "La connexion a été fermée par la machine distante"),
|
||||
("Connecting...", "Connexion..."),
|
||||
("Connection in progress. Please wait.", "Connexion en cours. Veuillez patienter."),
|
||||
("Please try 1 minute later", "Réessayez dans une minute"),
|
||||
@ -210,7 +210,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Settings", "Paramètres"),
|
||||
("Username", " Nom d'utilisateur"),
|
||||
("Invalid port", "Port invalide"),
|
||||
("Closed manually by the peer", "Fermé manuellement par le pair"),
|
||||
("Closed manually by the peer", "Fermé manuellement par la machine distante"),
|
||||
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
|
||||
("Run without install", "Exécuter sans installer"),
|
||||
("Connect via relay", "Connexion via relais"),
|
||||
@ -246,7 +246,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("install_daemon_tip", "Pour une exécution au démarrage du système, vous devez installer le service système."),
|
||||
("Remote ID", "ID de l'appareil à distance"),
|
||||
("Paste", "Coller"),
|
||||
("Paste here?", "Coller ici ?"),
|
||||
("Paste here?", "Coller ici?"),
|
||||
("Are you sure to close the connection?", "Êtes-vous sûr de fermer la connexion?"),
|
||||
("Download new version", "Télécharger la nouvelle version"),
|
||||
("Touch mode", "Mode tactile"),
|
||||
@ -300,11 +300,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Succeeded", "Succès"),
|
||||
("Someone turns on privacy mode, exit", "Quelqu'un active le mode de confidentialité, quittez"),
|
||||
("Unsupported", "Non pris en charge"),
|
||||
("Peer denied", "Pair refusé"),
|
||||
("Peer denied", "Machine distante refusée"),
|
||||
("Please install plugins", "Veuillez installer les plugins"),
|
||||
("Peer exit", "Sortie des pairs"),
|
||||
("Peer exit", ""),
|
||||
("Failed to turn off", "Échec de la désactivation"),
|
||||
("Turned off", "Éteindre"),
|
||||
("Turned off", "Désactivé"),
|
||||
("In privacy mode", "en mode privé"),
|
||||
("Out privacy mode", "hors mode de confidentialité"),
|
||||
("Language", "Langue"),
|
||||
@ -366,7 +366,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Deny remote access", "Interdir l'accès distant"),
|
||||
("Use IP Whitelisting", "Utiliser une liste blanche d'IP"),
|
||||
("Network", "Réseau"),
|
||||
("Enable RDP", "Activer RDP"),
|
||||
("Enable RDP", "Activer connection RDP"),
|
||||
("Pin menubar", "Épingler la barre de menus"),
|
||||
("Unpin menubar", "Détacher la barre de menu"),
|
||||
("Recording", "Enregistrement"),
|
||||
@ -377,8 +377,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", "Stopper l'enregistrement"),
|
||||
("Enable Recording Session", "Activer l'enregistrement de session"),
|
||||
("Allow recording session", "Autoriser l'enregistrement de session"),
|
||||
("Enable LAN Discovery", "Activer la découverte réseau local"),
|
||||
("Deny LAN Discovery", "Interdir la découverte réseau local"),
|
||||
("Enable LAN Discovery", "Activer la découverte sur réseau local"),
|
||||
("Deny LAN Discovery", "Interdir la découverte sur réseau local"),
|
||||
("Write a message", "Ecrire un message"),
|
||||
("Prompt", ""),
|
||||
("Please wait for confirmation of UAC...", "Veuillez attendre la confirmation de l'UAC..."),
|
||||
@ -392,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland nécessite Ubuntu 21.04 ou une version supérieure."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland nécessite une version supérieure de la distribution Linux. Veuillez essayer le bureau X11 ou changer votre système d'exploitation."),
|
||||
("JumpLink", "Afficher"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (côté machine distante)."),
|
||||
("Show RustDesk", "Afficher RustDesk"),
|
||||
("This PC", "Ce PC"),
|
||||
("or", "ou"),
|
||||
@ -478,33 +478,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Password", "Mot de passe non spécifié"),
|
||||
("Me", "Moi"),
|
||||
("identical_file_tip", "Ce fichier est identique à celui du pair."),
|
||||
("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils."),
|
||||
("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils"),
|
||||
("View Mode", "Mode vue"),
|
||||
("login_linux_tip", "Se connecter au compte Linux distant"),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("verify_rustdesk_password_tip", "Vérifier le mot de passe RustDesk"),
|
||||
("remember_account_tip", "Se souvenir de ce compte"),
|
||||
("os_account_desk_tip", "Ce compte est utilisé pour se connecter au système d'exploitation distant et activer la session de bureau en mode sans affichage"),
|
||||
("OS Account", "Compte système d'exploitation"),
|
||||
("another_user_login_title_tip", "Un autre utilisateur est déjà connecté"),
|
||||
("another_user_login_text_tip", "Déconnexion"),
|
||||
("xorg_not_found_title_tip", "Xorg introuvable"),
|
||||
("xorg_not_found_text_tip", "Veuillez installer Xorg"),
|
||||
("no_desktop_title_tip", "Aucun gestionaire de bureau n'est disponible"),
|
||||
("no_desktop_text_tip", "Veuillez installer le gestionaire de bureau GNOME"),
|
||||
("No need to elevate", "Pas besoin de permissions administrateur"),
|
||||
("System Sound", "Son système"),
|
||||
("Default", "Défaut"),
|
||||
("New RDP", "Nouvel RDP"),
|
||||
("Fingerprint", "Empreinte digitale"),
|
||||
("Copy Fingerprint", "Copier empreinte digitale"),
|
||||
("no fingerprints", "Pas d'empreintes digitales"),
|
||||
("Select a peer", "Sélectionnez la machine distante"),
|
||||
("Select peers", "Sélectionnez des machines distantes"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Désinstaller"),
|
||||
("Update", "Mise à jour"),
|
||||
("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", "Risoluzione originale"),
|
||||
("resolution_fit_local_tip", "Adatta risoluzione locale"),
|
||||
("resolution_custom_tip", "Risoluzione personalizzata"),
|
||||
].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();
|
||||
}
|
||||
|
@ -478,7 +478,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Password", "Senha Vazia"),
|
||||
("Me", "Eu"),
|
||||
("identical_file_tip", "Este arquivo é idêntico ao do parceiro."),
|
||||
("show_monitors_tip", "Mostrar monitores na barra de ferramentas."),
|
||||
("show_monitors_tip", "Mostrar monitores na barra de ferramentas"),
|
||||
("View Mode", "Modo de Visualização"),
|
||||
("login_linux_tip", "Você precisa fazer login na conta Linux remota para habilitar uma sessão de desktop X"),
|
||||
("verify_rustdesk_password_tip", "Verifique a senha do RustDesk"),
|
||||
@ -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();
|
||||
}
|
||||
|
@ -774,6 +774,12 @@ pub fn resolutions(name: &str) -> Vec<Resolution> {
|
||||
Virtual2 disconnected (normal left inverted right x axis y axis)
|
||||
Virtual3 disconnected (normal left inverted right x axis y axis)
|
||||
|
||||
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384
|
||||
eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 344mm x 193mm
|
||||
1920x1080 60.01*+ 60.01 59.97 59.96 59.93
|
||||
1680x1050 59.95 59.88
|
||||
1600x1024 60.17
|
||||
|
||||
XWAYLAND0 connected primary 1920x984+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
|
||||
Virtual1 connected primary 1920x984+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
|
||||
HDMI-0 connected (normal left inverted right x axis y axis)
|
||||
@ -783,7 +789,7 @@ pub fn resolutions(name: &str) -> Vec<Resolution> {
|
||||
if let Some(caps) = re.captures(&xrandr_output) {
|
||||
if let Some(resolutions) = caps.name("resolutions") {
|
||||
let resolution_pat =
|
||||
r"\s*(?P<width>\d+)x(?P<height>\d+)\s+(?P<rates>(\d+\.\d+[* ]*)+)\s*\n";
|
||||
r"\s*(?P<width>\d+)x(?P<height>\d+)\s+(?P<rates>(\d+\.\d+\D*)+)\s*\n";
|
||||
let resolution_re = Regex::new(&format!(r"{}", resolution_pat)).unwrap();
|
||||
for resolution_caps in resolution_re.captures_iter(resolutions.as_str()) {
|
||||
if let Some((width, height)) =
|
||||
|
@ -1831,21 +1831,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 +1870,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 +1886,26 @@ pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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,35 @@ 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() {
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
if let Some(_ok) =
|
||||
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
|
||||
&name,
|
||||
r.width as _,
|
||||
r.height as _,
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
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 +2159,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 +2280,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,24 @@ pub fn handle_one_frame_encoded(
|
||||
Ok(send_conn_ids)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField<Resolution> {
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name);
|
||||
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
|
||||
let is_virtual_display = false;
|
||||
Some(if is_virtual_display {
|
||||
Resolution {
|
||||
width: 0,
|
||||
height: 0,
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
update_get_original_resolution_(&display_name, w, h)
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
|
||||
let mut displays = Vec::new();
|
||||
let mut primary = 0;
|
||||
@ -827,14 +920,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 +949,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 +965,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 +1058,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 {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use hbb_common::{allow_err, bail, lazy_static, log, ResultType};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
@ -16,8 +16,8 @@ lazy_static::lazy_static! {
|
||||
|
||||
#[derive(Default)]
|
||||
struct VirtualDisplayManager {
|
||||
headless_index: Option<u32>,
|
||||
peer_required_indices: HashSet<u32>,
|
||||
headless_index_name: Option<(u32, String)>,
|
||||
peer_index_name: HashMap<u32, String>,
|
||||
}
|
||||
|
||||
impl VirtualDisplayManager {
|
||||
@ -54,14 +54,16 @@ pub fn plug_in_headless() -> ResultType<()> {
|
||||
height: 1080,
|
||||
sync: 60,
|
||||
}];
|
||||
let device_names = windows::get_device_names();
|
||||
VirtualDisplayManager::plug_in_monitor(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, &modes)?;
|
||||
manager.headless_index = Some(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS);
|
||||
let device_name = get_new_device_name(&device_names);
|
||||
manager.headless_index_name = Some((VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, device_name));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn plug_out_headless() -> bool {
|
||||
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
if let Some(index) = manager.headless_index.take() {
|
||||
if let Some((index, _)) = manager.headless_index_name.take() {
|
||||
if let Err(e) = virtual_display::plug_out_monitor(index) {
|
||||
log::error!("Plug out monitor failed {}", e);
|
||||
}
|
||||
@ -71,19 +73,39 @@ pub fn plug_out_headless() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plug_in_peer_required(
|
||||
modes: Vec<Vec<virtual_display::MonitorMode>>,
|
||||
) -> ResultType<Vec<u32>> {
|
||||
fn get_new_device_name(device_names: &HashSet<String>) -> String {
|
||||
for _ in 0..3 {
|
||||
let device_names_af = windows::get_device_names();
|
||||
let diff_names: Vec<_> = device_names_af.difference(&device_names).collect();
|
||||
if diff_names.len() == 1 {
|
||||
return diff_names[0].clone();
|
||||
} else if diff_names.len() > 1 {
|
||||
log::error!(
|
||||
"Failed to get diff device names after plugin virtual display, more than one diff names: {:?}",
|
||||
&diff_names
|
||||
);
|
||||
return "".to_string();
|
||||
}
|
||||
// Sleep is needed here to wait for the virtual display to be ready.
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
log::error!("Failed to get diff device names after plugin virtual display",);
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> ResultType<Vec<u32>> {
|
||||
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
VirtualDisplayManager::prepare_driver()?;
|
||||
|
||||
let mut indices: Vec<u32> = Vec::new();
|
||||
for m in modes.iter() {
|
||||
for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT {
|
||||
if !manager.peer_required_indices.contains(&idx) {
|
||||
if !manager.peer_index_name.contains_key(&idx) {
|
||||
let device_names = windows::get_device_names();
|
||||
match VirtualDisplayManager::plug_in_monitor(idx, m) {
|
||||
Ok(_) => {
|
||||
manager.peer_required_indices.insert(idx);
|
||||
let device_name = get_new_device_name(&device_names);
|
||||
manager.peer_index_name.insert(idx, device_name);
|
||||
indices.push(idx);
|
||||
}
|
||||
Err(e) => {
|
||||
@ -97,13 +119,135 @@ pub fn plug_in_peer_required(
|
||||
Ok(indices)
|
||||
}
|
||||
|
||||
pub fn plug_out_peer_required(modes: &[u32]) -> ResultType<()> {
|
||||
pub fn plug_out_peer_request(modes: &[u32]) -> ResultType<()> {
|
||||
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
for idx in modes.iter() {
|
||||
if manager.peer_required_indices.contains(idx) {
|
||||
if manager.peer_index_name.contains_key(idx) {
|
||||
allow_err!(virtual_display::plug_out_monitor(*idx));
|
||||
manager.peer_required_indices.remove(idx);
|
||||
manager.peer_index_name.remove(idx);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_virtual_display(name: &str) -> bool {
|
||||
let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
if let Some((_, device_name)) = &lock.headless_index_name {
|
||||
if windows::is_device_name(device_name, name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (k, v) in lock.peer_index_name.iter() {
|
||||
if windows::is_device_name(v, name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn change_resolution(index: u32, w: u32, h: u32) -> bool {
|
||||
let modes = [virtual_display::MonitorMode {
|
||||
width: w,
|
||||
height: h,
|
||||
sync: 60,
|
||||
}];
|
||||
match virtual_display::update_monitor_modes(index, &modes) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
log::error!("Update monitor {} modes {:?} failed: {}", index, &modes, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_resolution_if_is_virtual_display(name: &str, w: u32, h: u32) -> Option<bool> {
|
||||
let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
if let Some((index, device_name)) = &lock.headless_index_name {
|
||||
if windows::is_device_name(device_name, name) {
|
||||
return Some(change_resolution(*index, w, h));
|
||||
}
|
||||
}
|
||||
|
||||
for (k, v) in lock.peer_index_name.iter() {
|
||||
if windows::is_device_name(v, name) {
|
||||
return Some(change_resolution(*k, w, h));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
mod windows {
|
||||
use std::{collections::HashSet, ptr::null_mut};
|
||||
use winapi::{
|
||||
shared::minwindef::{DWORD, FALSE},
|
||||
um::{
|
||||
wingdi::{
|
||||
DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_MIRRORING_DRIVER,
|
||||
},
|
||||
winuser::{EnumDisplayDevicesW, EnumDisplaySettingsExW, ENUM_CURRENT_SETTINGS},
|
||||
},
|
||||
};
|
||||
|
||||
// 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";
|
||||
|
||||
#[inline]
|
||||
pub(super) 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(super) fn get_device_names() -> HashSet<String> {
|
||||
let mut device_names = HashSet::new();
|
||||
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;
|
||||
}
|
||||
i_dev_num += 1;
|
||||
|
||||
if 0 == (dd.StateFlags & DISPLAY_DEVICE_ACTIVE)
|
||||
|| (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut dm: DEVMODEW = unsafe { std::mem::zeroed() };
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
dm.dmDriverExtra = 0;
|
||||
let ok = unsafe {
|
||||
EnumDisplaySettingsExW(
|
||||
dd.DeviceName.as_ptr(),
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
&mut dm as _,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ok == FALSE {
|
||||
continue;
|
||||
}
|
||||
if dm.dmPelsHeight == 0 || dm.dmPelsWidth == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let (Ok(device_name), Ok(device_string)) = (
|
||||
String::from_utf16(&dd.DeviceName),
|
||||
String::from_utf16(&dd.DeviceString),
|
||||
) {
|
||||
if &device_string[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING {
|
||||
device_names.insert(device_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
device_names
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user