Merge pull request #4411 from fufesou/feat/remember_custom_resolution

Feat/remember custom resolution
This commit is contained in:
RustDesk 2023-05-19 11:14:54 +08:00 committed by GitHub
commit c63e757b76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 765 additions and 222 deletions

View File

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

View File

@ -660,69 +660,24 @@ class _ControlMenu extends StatelessWidget {
}
}
class _DisplayMenu extends StatefulWidget {
class ScreenAdjustor {
final String id;
final FFI ffi;
final MenubarState state;
final Function(bool) setFullscreen;
final Widget pluginItem;
_DisplayMenu(
{Key? key,
required this.id,
required this.ffi,
required this.state,
required this.setFullscreen})
: pluginItem = LocationItem.createLocationItem(
id,
ffi,
kLocationClientRemoteToolbarDisplay,
true,
),
super(key: key);
@override
State<_DisplayMenu> createState() => _DisplayMenuState();
}
class _DisplayMenuState extends State<_DisplayMenu> {
final VoidCallback cbExitFullscreen;
window_size.Screen? _screen;
ScreenAdjustor({
required this.id,
required this.ffi,
required this.cbExitFullscreen,
});
bool get isFullscreen => stateGlobal.fullscreen;
int get windowId => stateGlobal.windowId;
Map<String, bool> get perms => widget.ffi.ffiModel.permissions;
PeerInfo get pi => widget.ffi.ffiModel.pi;
FfiModel get ffiModel => widget.ffi.ffiModel;
FFI get ffi => widget.ffi;
String get id => widget.id;
@override
Widget build(BuildContext context) {
_updateScreen();
return _IconSubmenuButton(
tooltip: 'Display Settings',
svg: "assets/display.svg",
ffi: widget.ffi,
color: _MenubarTheme.blueColor,
hoverColor: _MenubarTheme.hoverBlueColor,
menuChildren: [
adjustWindow(),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
resolutions(),
Divider(),
toggles(),
widget.pluginItem,
]);
}
adjustWindow() {
return futureBuilder(
future: _isWindowCanBeAdjusted(),
future: isWindowCanBeAdjusted(),
hasData: (data) {
final visible = data as bool;
if (!visible) return Offstage();
@ -730,18 +685,18 @@ class _DisplayMenuState extends State<_DisplayMenu> {
children: [
MenuButton(
child: Text(translate('Adjust Window')),
onPressed: _doAdjustWindow,
ffi: widget.ffi),
onPressed: doAdjustWindow,
ffi: ffi),
Divider(),
],
);
});
}
_doAdjustWindow() async {
await _updateScreen();
doAdjustWindow() async {
await updateScreen();
if (_screen != null) {
widget.setFullscreen(false);
cbExitFullscreen();
double scale = _screen!.scaleFactor;
final wndRect = await WindowController.fromWindowId(windowId).getFrame();
final mediaSize = MediaQueryData.fromWindow(ui.window).size;
@ -752,7 +707,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
double magicHeight =
wndRect.bottom - wndRect.top - mediaSize.height * scale;
final canvasModel = widget.ffi.canvasModel;
final canvasModel = ffi.canvasModel;
final width = (canvasModel.getDisplayWidth() * canvasModel.scale +
CanvasModel.leftToEdge +
CanvasModel.rightToEdge) *
@ -787,7 +742,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
}
_updateScreen() async {
updateScreen() async {
final v = await rustDeskWinManager.call(
WindowType.Main, kWindowGetWindowInfo, '');
final String valueStr = v;
@ -807,8 +762,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
}
Future<bool> _isWindowCanBeAdjusted() async {
final viewStyle = await bind.sessionGetViewStyle(id: widget.id) ?? '';
Future<bool> isWindowCanBeAdjusted() async {
final viewStyle = await bind.sessionGetViewStyle(id: id) ?? '';
if (viewStyle != kRemoteViewStyleOriginal) {
return false;
}
@ -827,7 +782,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
selfHeight = _screen!.frame.height;
}
final canvasModel = widget.ffi.canvasModel;
final canvasModel = ffi.canvasModel;
final displayWidth = canvasModel.getDisplayWidth();
final displayHeight = canvasModel.getDisplayHeight();
final requiredWidth =
@ -837,6 +792,77 @@ class _DisplayMenuState extends State<_DisplayMenu> {
return selfWidth > (requiredWidth * scale) &&
selfHeight > (requiredHeight * scale);
}
}
class _DisplayMenu extends StatefulWidget {
final String id;
final FFI ffi;
final MenubarState state;
final Function(bool) setFullscreen;
final Widget pluginItem;
_DisplayMenu(
{Key? key,
required this.id,
required this.ffi,
required this.state,
required this.setFullscreen})
: pluginItem = LocationItem.createLocationItem(
id,
ffi,
kLocationClientRemoteToolbarDisplay,
true,
),
super(key: key);
@override
State<_DisplayMenu> createState() => _DisplayMenuState();
}
class _DisplayMenuState extends State<_DisplayMenu> {
late final ScreenAdjustor _screenAdjustor = ScreenAdjustor(
id: widget.id,
ffi: widget.ffi,
cbExitFullscreen: () => widget.setFullscreen(false),
);
bool get isFullscreen => stateGlobal.fullscreen;
int get windowId => stateGlobal.windowId;
Map<String, bool> get perms => widget.ffi.ffiModel.permissions;
PeerInfo get pi => widget.ffi.ffiModel.pi;
FfiModel get ffiModel => widget.ffi.ffiModel;
FFI get ffi => widget.ffi;
String get id => widget.id;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
_screenAdjustor.updateScreen();
return _IconSubmenuButton(
tooltip: 'Display Settings',
svg: "assets/display.svg",
ffi: widget.ffi,
color: _MenubarTheme.blueColor,
hoverColor: _MenubarTheme.hoverBlueColor,
menuChildren: [
_screenAdjustor.adjustWindow(),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
_ResolutionsMenu(
id: widget.id,
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
Divider(),
toggles(),
widget.pluginItem,
]);
}
viewStyle() {
return futureBuilder(
@ -935,46 +961,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
});
}
resolutions() {
final resolutions = pi.resolutions;
final visible = ffiModel.keyboard && resolutions.length > 1;
if (!visible) return Offstage();
final display = ffiModel.display;
final groupValue = "${display.width}x${display.height}";
onChanged(String? value) async {
if (value == null) return;
final list = value.split('x');
if (list.length == 2) {
final w = int.tryParse(list[0]);
final h = int.tryParse(list[1]);
if (w != null && h != null) {
await bind.sessionChangeResolution(
id: widget.id, width: w, height: h);
Future.delayed(Duration(seconds: 3), () async {
final display = ffiModel.display;
if (w == display.width && h == display.height) {
if (await _isWindowCanBeAdjusted()) {
_doAdjustWindow();
}
}
});
}
}
}
return _SubmenuButton(
ffi: widget.ffi,
menuChildren: resolutions
.map((e) => RdoMenuButton(
value: '${e.width}x${e.height}',
groupValue: groupValue,
onChanged: onChanged,
ffi: widget.ffi,
child: Text('${e.width}x${e.height}')))
.toList(),
child: Text(translate("Resolution")));
}
toggles() {
return futureBuilder(
future: toolbarDisplayToggle(context, id, ffi),
@ -993,6 +979,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;

View File

@ -295,11 +295,15 @@ class FfiModel with ChangeNotifier {
handleSwitchDisplay(Map<String, dynamic> evt, String peerId) {
_pi.currentDisplay = int.parse(evt['display']);
var newDisplay = Display();
newDisplay.x = double.parse(evt['x']);
newDisplay.y = double.parse(evt['y']);
newDisplay.width = int.parse(evt['width']);
newDisplay.height = int.parse(evt['height']);
newDisplay.cursorEmbedded = int.parse(evt['cursor_embedded']) == 1;
newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x;
newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y;
newDisplay.width = int.tryParse(evt['width']) ?? newDisplay.width;
newDisplay.height = int.tryParse(evt['height']) ?? newDisplay.height;
newDisplay.cursorEmbedded = int.tryParse(evt['cursor_embedded']) == 1;
newDisplay.originalWidth =
int.tryParse(evt['original_width']) ?? kInvalidResolutionValue;
newDisplay.originalHeight =
int.tryParse(evt['original_height']) ?? kInvalidResolutionValue;
_updateCurDisplay(peerId, newDisplay);
@ -466,14 +470,7 @@ class FfiModel with ChangeNotifier {
_pi.displays = [];
List<dynamic> displays = json.decode(evt['displays']);
for (int i = 0; i < displays.length; ++i) {
Map<String, dynamic> d0 = displays[i];
var d = Display();
d.x = d0['x'].toDouble();
d.y = d0['y'].toDouble();
d.width = d0['width'];
d.height = d0['height'];
d.cursorEmbedded = d0['cursor_embedded'] == 1;
_pi.displays.add(d);
_pi.displays.add(evtToDisplay(displays[i]));
}
stateGlobal.displaysCount.value = _pi.displays.length;
if (_pi.currentDisplay < _pi.displays.length) {
@ -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 {

View File

@ -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 {

View File

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

View File

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

View File

@ -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.
///

View File

@ -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, "");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

@ -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() {

View File

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

View File

@ -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 {