diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 4d23614ca..bc5b19c97 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -959,6 +959,7 @@ class CustomAlertDialog extends StatelessWidget { void msgBox(SessionID sessionId, String type, String title, String text, String link, OverlayDialogManager dialogManager, {bool? hasCancel, ReconnectHandle? reconnect, int? reconnectTimeout}) { + dialogManager.dismissAll(); List buttons = []; bool hasOk = false; @@ -1983,8 +1984,8 @@ List? urlLinkToCmdArgs(Uri uri) { id = uri.authority; } - if (isMobile){ - if (id != null){ + if (isMobile) { + if (id != null) { connect(Get.context!, id); return null; } @@ -2040,7 +2041,7 @@ connect( final idController = Get.find(); idController.text = formatID(id); } - if (Get.isRegistered()){ + if (Get.isRegistered()) { final fieldTextEditingController = Get.find(); fieldTextEditingController.text = formatID(id); } diff --git a/flutter/lib/common/shared_state.dart b/flutter/lib/common/shared_state.dart index 21e926643..2aa9353f9 100644 --- a/flutter/lib/common/shared_state.dart +++ b/flutter/lib/common/shared_state.dart @@ -11,7 +11,7 @@ class PrivacyModeState { static void init(String id) { final key = tag(id); if (!Get.isRegistered(tag: key)) { - final RxBool state = false.obs; + final RxString state = ''.obs; Get.put(state, tag: key); } } @@ -21,11 +21,11 @@ class PrivacyModeState { if (Get.isRegistered(tag: key)) { Get.delete(tag: key); } else { - Get.find(tag: key).value = false; + Get.find(tag: key).value = ''; } } - static RxBool find(String id) => Get.find(tag: tag(id)); + static RxString find(String id) => Get.find(tag: tag(id)); } class BlockInputState { diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 468091200..a3b67b752 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -481,24 +481,6 @@ Future> toolbarDisplayToggle( }, child: Text(translate('Lock after session end')))); } - // privacy mode - if (ffiModel.keyboard && pi.features.privacyMode) { - final option = 'privacy-mode'; - final rxValue = PrivacyModeState.find(id); - v.add(TToggleMenu( - value: rxValue.value, - onChanged: (value) { - if (value == null) return; - if (ffiModel.pi.currentDisplay != 0 && - ffiModel.pi.currentDisplay != kAllDisplayValue) { - msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info', - 'Please switch to Display 1 first', '', ffi.dialogManager); - return; - } - bind.sessionToggleOption(sessionId: sessionId, value: option); - }, - child: Text(translate('Privacy mode')))); - } // swap key if (ffiModel.keyboard && ((Platform.isMacOS && pi.platform != kPeerPlatformMacOS) || @@ -517,7 +499,7 @@ Future> toolbarDisplayToggle( if (useTextureRender && pi.isSupportMultiDisplay && - PrivacyModeState.find(id).isFalse && + PrivacyModeState.find(id).isEmpty && pi.displaysCount.value > 1 && bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') { final value = @@ -567,3 +549,69 @@ Future> toolbarDisplayToggle( return v; } + +var togglePrivacyModeTime = DateTime.now().subtract(const Duration(hours: 1)); + +List toolbarPrivacyMode( + RxString privacyModeState, BuildContext context, String id, FFI ffi) { + final ffiModel = ffi.ffiModel; + final pi = ffiModel.pi; + final sessionId = ffi.sessionId; + + getDefaultMenu(Future Function(SessionID sid, String opt) toggleFunc) { + return TToggleMenu( + value: privacyModeState.isNotEmpty, + onChanged: (value) { + if (value == null) return; + if (ffiModel.pi.currentDisplay != 0 && + ffiModel.pi.currentDisplay != kAllDisplayValue) { + msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info', + 'Please switch to Display 1 first', '', ffi.dialogManager); + return; + } + final option = 'privacy-mode'; + toggleFunc(sessionId, option); + }, + child: Text(translate('Privacy mode'))); + } + + final privacyModeImpls = + pi.platformAdditions[kPlatformAdditionsSupportedPrivacyModeImpl] + as List?; + if (privacyModeImpls == null) { + return [ + getDefaultMenu((sid, opt) async { + bind.sessionToggleOption(sessionId: sid, value: opt); + togglePrivacyModeTime = DateTime.now(); + }) + ]; + } + if (privacyModeImpls.isEmpty) { + return []; + } + + if (privacyModeImpls.length == 1) { + final implKey = (privacyModeImpls[0] as List)[0] as String; + return [ + getDefaultMenu((sid, opt) async { + bind.sessionTogglePrivacyMode( + sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty); + togglePrivacyModeTime = DateTime.now(); + }) + ]; + } else { + return privacyModeImpls.map((e) { + final implKey = (e as List)[0] as String; + final implName = (e)[1] as String; + return TToggleMenu( + child: Text(translate(implName)), + value: privacyModeState.value == implKey, + onChanged: (value) { + if (value == null) return; + togglePrivacyModeTime = DateTime.now(); + bind.sessionTogglePrivacyMode( + sessionId: sessionId, implKey: implKey, on: value); + }); + }).toList(); + } +} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 3ea9774bf..34475a62d 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -23,6 +23,7 @@ const String kPlatformAdditionsHeadless = "headless"; const String kPlatformAdditionsIsInstalled = "is_installed"; const String kPlatformAdditionsVirtualDisplays = "virtual_displays"; const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard"; +const String kPlatformAdditionsSupportedPrivacyModeImpl = "supported_privacy_mode_impl"; const String kPeerPlatformWindows = "Windows"; const String kPeerPlatformLinux = "Linux"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 3322291eb..c4261b4c9 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1060,7 +1060,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { tmpWrapper() { // Setting page is not modal, oldOptions should only be used when getting options, never when setting. Map oldOptions = - jsonDecode(bind.mainGetOptionsSync() as String); + jsonDecode(bind.mainGetOptionsSync()); old(String key) { return (oldOptions[key] ?? '').trim(); } @@ -1151,6 +1151,7 @@ class _DisplayState extends State<_Display> { scrollStyle(context), imageQuality(context), codec(context), + privacyModeImpl(context), other(context), ]).marginOnly(bottom: _kListViewBottomMargin)); } @@ -1290,6 +1291,42 @@ class _DisplayState extends State<_Display> { ]); } + Widget privacyModeImpl(BuildContext context) { + final supportedPrivacyModeImpls = bind.mainSupportedPrivacyModeImpls(); + late final List privacyModeImpls; + try { + privacyModeImpls = jsonDecode(supportedPrivacyModeImpls); + } catch (e) { + debugPrint('failed to parse supported privacy mode impls, err=$e'); + return Offstage(); + } + if (privacyModeImpls.length < 2) { + return Offstage(); + } + + final key = 'privacy-mode-impl-key'; + onChanged(String value) async { + await bind.mainSetOption(key: key, value: value); + setState(() {}); + } + + String groupValue = bind.mainGetOptionSync(key: key); + if (groupValue.isEmpty) { + groupValue = bind.mainDefaultPrivacyModeImpl(); + } + return _Card( + title: 'Privacy mode', + children: privacyModeImpls.map((impl) { + final d = impl as List; + return _Radio(context, + value: d[0] as String, + groupValue: groupValue, + label: d[1] as String, + onChanged: onChanged); + }).toList(), + ); + } + Widget otherRow(String label, String key) { final value = bind.mainGetUserDefaultOption(key: key) == 'Y'; onChanged(bool b) async { diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 5b67ed77b..3e5fc441a 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -17,6 +17,7 @@ import '../../common/widgets/overlay.dart'; import '../../common/widgets/remote_input.dart'; import '../../common.dart'; import '../../common/widgets/dialog.dart'; +import '../../common/widgets/toolbar.dart'; import '../../models/model.dart'; import '../../models/desktop_render_texture.dart'; import '../../models/platform_model.dart'; @@ -281,24 +282,23 @@ class _RemotePageState extends State }, inputModel: _ffi.inputModel, child: getBodyForDesktop(context))), - Stack( - children: [ - _ffi.ffiModel.pi.isSet.isTrue && - _ffi.ffiModel.waitForFirstImage.isTrue - ? emptyOverlay() - : () { - _ffi.ffiModel.tryShowAndroidActionsOverlay(); - return Offstage(); - }(), - // Use Overlay to enable rebuild every time on menu button click. - _ffi.ffiModel.pi.isSet.isTrue - ? Overlay(initialEntries: [ - OverlayEntry(builder: remoteToolbar) - ]) - : remoteToolbar(context), - _ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(), - ], - ), + Stack( + children: [ + _ffi.ffiModel.pi.isSet.isTrue && + _ffi.ffiModel.waitForFirstImage.isTrue + ? emptyOverlay() + : () { + _ffi.ffiModel.tryShowAndroidActionsOverlay(); + return Offstage(); + }(), + // Use Overlay to enable rebuild every time on menu button click. + _ffi.ffiModel.pi.isSet.isTrue + ? Overlay( + initialEntries: [OverlayEntry(builder: remoteToolbar)]) + : remoteToolbar(context), + _ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(), + ], + ), ], ); } @@ -309,12 +309,17 @@ class _RemotePageState extends State final imageReady = _ffi.ffiModel.pi.isSet.isTrue && _ffi.ffiModel.waitForFirstImage.isFalse; if (imageReady) { - // `dismissAll()` is to ensure that the state is clean. - // It's ok to call dismissAll() here. - _ffi.dialogManager.dismissAll(); - // Recreate the block state to refresh the state. - _blockableOverlayState = BlockableOverlayState(); - _blockableOverlayState.applyFfi(_ffi); + // If the privacy mode(disable physical displays) is switched, + // we should not dismiss the dialog immediately. + if (DateTime.now().difference(togglePrivacyModeTime) > + const Duration(milliseconds: 3000)) { + // `dismissAll()` is to ensure that the state is clean. + // It's ok to call dismissAll() here. + _ffi.dialogManager.dismissAll(); + // Recreate the block state to refresh the state. + _blockableOverlayState = BlockableOverlayState(); + _blockableOverlayState.applyFfi(_ffi); + } // Block the whole `bodyWidget()` when dialog shows. return BlockableOverlay( underlying: bodyWidget(), diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index dac8ae528..271879428 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -468,7 +468,7 @@ class _RemoteToolbarState extends State { } toolbarItems.add(Obx(() { - if (PrivacyModeState.find(widget.id).isFalse && + if (PrivacyModeState.find(widget.id).isEmpty && pi.displaysCount.value > 1) { return _MonitorMenu( id: widget.id, @@ -1034,31 +1034,62 @@ class _DisplayMenuState extends State<_DisplayMenu> { @override Widget build(BuildContext context) { _screenAdjustor.updateScreen(); - return _IconSubmenuButton( - tooltip: 'Display Settings', - svg: "assets/display.svg", + + final menuChildren = [ + _screenAdjustor.adjustWindow(context), + viewStyle(), + scrollStyle(), + imageQuality(), + codec(), + _ResolutionsMenu( + id: widget.id, ffi: widget.ffi, - color: _ToolbarTheme.blueColor, - hoverColor: _ToolbarTheme.hoverBlueColor, - menuChildren: [ - _screenAdjustor.adjustWindow(context), - viewStyle(), - scrollStyle(), - imageQuality(), - codec(), - _ResolutionsMenu( - id: widget.id, - ffi: widget.ffi, - screenAdjustor: _screenAdjustor, - ), - _VirtualDisplayMenu( - id: widget.id, - ffi: widget.ffi, - ), + screenAdjustor: _screenAdjustor, + ), + _VirtualDisplayMenu( + id: widget.id, + ffi: widget.ffi, + ), + Divider(), + toggles(), + ]; + // privacy mode + if (ffiModel.keyboard && pi.features.privacyMode) { + final privacyModeState = PrivacyModeState.find(id); + final privacyModeList = + toolbarPrivacyMode(privacyModeState, context, id, ffi); + if (privacyModeList.length == 1) { + menuChildren.add(CkbMenuButton( + value: privacyModeList[0].value, + onChanged: privacyModeList[0].onChanged, + child: privacyModeList[0].child, + ffi: ffi)); + } else if (privacyModeList.length > 1) { + menuChildren.addAll([ Divider(), - toggles(), - widget.pluginItem, + _SubmenuButton( + ffi: widget.ffi, + child: Text(translate('Privacy Mode')), + menuChildren: privacyModeList + .map((e) => CkbMenuButton( + value: e.value, + onChanged: e.onChanged, + child: e.child, + ffi: ffi)) + .toList()), ]); + } + } + menuChildren.add(widget.pluginItem); + + return _IconSubmenuButton( + tooltip: 'Display Settings', + svg: "assets/display.svg", + ffi: widget.ffi, + color: _ToolbarTheme.blueColor, + hoverColor: _ToolbarTheme.hoverBlueColor, + menuChildren: menuChildren, + ); } viewStyle() { @@ -1495,32 +1526,39 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> { } final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays; + final privacyModeState = PrivacyModeState.find(widget.id); final children = []; for (var i = 0; i < kMaxVirtualDisplayCount; i++) { - children.add(CkbMenuButton( - value: virtualDisplays.contains(i + 1), - onChanged: (bool? value) async { - if (value != null) { - bind.sessionToggleVirtualDisplay( - sessionId: widget.ffi.sessionId, index: i + 1, on: value); - } - }, - child: Text('${translate('Virtual display')} ${i + 1}'), - ffi: widget.ffi, - )); + children.add(Obx(() => CkbMenuButton( + value: virtualDisplays.contains(i + 1), + onChanged: privacyModeState.isNotEmpty + ? null + : (bool? value) async { + if (value != null) { + bind.sessionToggleVirtualDisplay( + sessionId: widget.ffi.sessionId, + index: i + 1, + on: value); + } + }, + child: Text('${translate('Virtual display')} ${i + 1}'), + ffi: widget.ffi, + ))); } children.add(Divider()); - children.add(MenuButton( - onPressed: () { - bind.sessionToggleVirtualDisplay( - sessionId: widget.ffi.sessionId, - index: kAllVirtualDisplay, - on: false); - }, - ffi: widget.ffi, - child: Text(translate('Plug out all')), - )); + children.add(Obx(() => MenuButton( + onPressed: privacyModeState.isNotEmpty + ? null + : () { + bind.sessionToggleVirtualDisplay( + sessionId: widget.ffi.sessionId, + index: kAllVirtualDisplay, + on: false); + }, + ffi: widget.ffi, + child: Text(translate('Plug out all')), + ))); return _SubmenuButton( ffi: widget.ffi, menuChildren: children, diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 6afb58eac..05bf433fe 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -21,6 +21,7 @@ import '../../models/input_model.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; import '../../utils/image.dart'; +import '../widgets/dialog.dart'; final initText = '1' * 1024; @@ -807,6 +808,16 @@ void showOptions( List displayToggles = await toolbarDisplayToggle(context, id, gFFI); + List privacyModeList = []; + // privacy mode + final privacyModeState = PrivacyModeState.find(id); + if (gFFI.ffiModel.keyboard && gFFI.ffiModel.pi.features.privacyMode) { + privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI); + if (privacyModeList.length == 1) { + displayToggles.add(privacyModeList[0]); + } + } + dialogManager.show((setState, close, context) { var viewStyle = (viewStyleRadios.isNotEmpty ? viewStyleRadios[0].groupValue : '').obs; @@ -849,10 +860,21 @@ void showOptions( title: e.value.child))) .toList(); + Widget privacyModeWidget = Offstage(); + if (privacyModeList.length > 1) { + privacyModeWidget = ListTile( + contentPadding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + title: Text(translate('Privacy mode')), + onTap: () => setPrivacyModeDialog( + dialogManager, privacyModeList, privacyModeState), + ); + } + return CustomAlertDialog( content: Column( mainAxisSize: MainAxisSize.min, - children: displays + radios + toggles), + children: displays + radios + toggles + [privacyModeWidget]), ); }, clickMaskDismiss: true, backDismiss: true); } diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 3e745ecce..6b480c46e 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; +import 'package:flutter_hbb/common/widgets/toolbar.dart'; import 'package:get/get.dart'; import '../../common.dart'; @@ -259,6 +260,30 @@ void showServerSettingsWithValue( }); } +void setPrivacyModeDialog( + OverlayDialogManager dialogManager, + List privacyModeList, + RxString privacyModeState, +) async { + dialogManager.dismissAll(); + dialogManager.show((setState, close, context) { + return CustomAlertDialog( + title: Text(translate('Privacy mode')), + content: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: privacyModeList + .map((value) => CheckboxListTile( + contentPadding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + title: value.child, + value: value.value, + onChanged: value.onChanged, + )) + .toList()), + ); + }, backDismiss: true, clickMaskDismiss: true); +} + Future validateAsync(String value) async { value = value.trim(); if (value.isEmpty) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 089b69522..2de85aa11 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -967,11 +967,21 @@ class FfiModel with ChangeNotifier { } updatePrivacyMode( - Map evt, SessionID sessionId, String peerId) { + Map evt, SessionID sessionId, String peerId) async { notifyListeners(); try { - PrivacyModeState.find(peerId).value = bind.sessionGetToggleOptionSync( + final isOn = bind.sessionGetToggleOptionSync( sessionId: sessionId, arg: 'privacy-mode'); + if (isOn) { + var privacyModeImpl = await bind.sessionGetOption( + sessionId: sessionId, arg: 'privacy-mode-impl-key'); + // For compatibility, version < 1.2.4, the default value is 'privacy_mode_impl_mag'. + final initDefaultPrivacyMode = 'privacy_mode_impl_mag'; + PrivacyModeState.find(peerId).value = + privacyModeImpl ?? initDefaultPrivacyMode; + } else { + PrivacyModeState.find(peerId).value = ''; + } } catch (e) { // } diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 1433cd1eb..a31655f69 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -518,6 +518,11 @@ message ToggleVirtualDisplay { bool on = 2; } +message TogglePrivacyMode { + string impl_key = 1; + bool on = 2; +} + message PermissionInfo { enum Permission { Keyboard = 0; @@ -656,6 +661,8 @@ message BackNotification { } // Supplementary message, for "PrvOnFailed" and "PrvOffFailed" string details = 3; + // The key of the implementation + string impl_key = 4; } message ElevationRequestWithLogon { @@ -721,6 +728,7 @@ message Misc { CaptureDisplays capture_displays = 30; int32 refresh_video_display = 31; ToggleVirtualDisplay toggle_virtual_display = 32; + TogglePrivacyMode toggle_privacy_mode = 33; } } diff --git a/src/client.rs b/src/client.rs index 58112abaf..e9f80eeac 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1552,9 +1552,11 @@ impl LoginConfigHandler { } let mut n = 0; let mut msg = OptionMessage::new(); - if self.get_toggle_option("privacy-mode") { - msg.privacy_mode = BoolOption::Yes.into(); - n += 1; + if self.version < hbb_common::get_version_number("1.2.4") { + if self.get_toggle_option("privacy-mode") { + msg.privacy_mode = BoolOption::Yes.into(); + n += 1; + } } if n > 0 { Some(msg) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 721fe6d81..ac98d8da8 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -14,8 +14,6 @@ use crossbeam_queue::ArrayQueue; use hbb_common::sleep; #[cfg(not(target_os = "ios"))] use hbb_common::tokio::sync::mpsc::error::TryRecvError; -#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] -use hbb_common::tokio::sync::Mutex as TokioMutex; use hbb_common::{ allow_err, config::{PeerConfig, TransferSerde}, @@ -34,8 +32,10 @@ use hbb_common::{ sync::mpsc, time::{self, Duration, Instant, Interval}, }, - ResultType, Stream, + Stream, }; +#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] +use hbb_common::{tokio::sync::Mutex as TokioMutex, ResultType}; use scrap::CodecFormat; use crate::client::{ @@ -926,6 +926,24 @@ impl Remote { } } + async fn send_toggle_privacy_mode_msg(&self, peer: &mut Stream) { + let lc = self.handler.lc.read().unwrap(); + if lc.version >= hbb_common::get_version_number("1.2.4") + && lc.get_toggle_option("privacy-mode") + { + let impl_key = lc.get_option("privacy-mode-impl-key"); + let mut misc = Misc::new(); + misc.set_toggle_privacy_mode(TogglePrivacyMode { + impl_key, + on: true, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + allow_err!(peer.send(&msg_out).await); + } + } + fn contains_key_frame(vf: &VideoFrame) -> bool { use video_frame::Union::*; match &vf.union { @@ -1026,6 +1044,7 @@ impl Remote { self.handler.close_success(); self.handler.adapt_size(); self.send_opts_after_login(peer).await; + self.send_toggle_privacy_mode_msg(peer).await; } let incoming_format = CodecFormat::from(&vf); if self.video_format != incoming_format { @@ -1557,6 +1576,7 @@ impl Remote { .handle_back_msg_privacy_mode( state.enum_value_or(back_notification::PrivacyModeState::PrvStateUnknown), notification.details, + notification.impl_key, ) .await { @@ -1615,9 +1635,20 @@ impl Remote { } #[inline(always)] - fn update_privacy_mode(&mut self, on: bool) { + fn update_privacy_mode(&mut self, impl_key: String, on: bool) { let mut config = self.handler.load_config(); config.privacy_mode.v = on; + if on { + // For compatibility, version < 1.2.4, the default value is 'privacy_mode_impl_mag'. + let impl_key = if impl_key.is_empty() { + "privacy_mode_impl_mag".to_string() + } else { + impl_key + }; + config + .options + .insert("privacy-mode-impl-key".to_string(), impl_key); + } self.handler.save_config(config); self.handler.update_privacy_mode(); @@ -1627,6 +1658,7 @@ impl Remote { &mut self, state: back_notification::PrivacyModeState, details: String, + impl_key: String, ) -> bool { match state { back_notification::PrivacyModeState::PrvOnByOther => { @@ -1641,22 +1673,22 @@ impl Remote { back_notification::PrivacyModeState::PrvNotSupported => { self.handler .msgbox("custom-error", "Privacy mode", "Unsupported", ""); - self.update_privacy_mode(false); + self.update_privacy_mode(impl_key, false); } back_notification::PrivacyModeState::PrvOnSucceeded => { self.handler - .msgbox("custom-nocancel", "Privacy mode", "In privacy mode", ""); - self.update_privacy_mode(true); + .msgbox("custom-nocancel", "Privacy mode", "Enter privacy mode", ""); + self.update_privacy_mode(impl_key, true); } back_notification::PrivacyModeState::PrvOnFailedDenied => { self.handler .msgbox("custom-error", "Privacy mode", "Peer denied", ""); - self.update_privacy_mode(false); + self.update_privacy_mode(impl_key, false); } back_notification::PrivacyModeState::PrvOnFailedPlugin => { self.handler .msgbox("custom-error", "Privacy mode", "Please install plugins", ""); - self.update_privacy_mode(false); + self.update_privacy_mode(impl_key, false); } back_notification::PrivacyModeState::PrvOnFailed => { self.handler.msgbox( @@ -1669,17 +1701,17 @@ impl Remote { }, "", ); - self.update_privacy_mode(false); + self.update_privacy_mode(impl_key, false); } back_notification::PrivacyModeState::PrvOffSucceeded => { self.handler - .msgbox("custom-nocancel", "Privacy mode", "Out privacy mode", ""); - self.update_privacy_mode(false); + .msgbox("custom-nocancel", "Privacy mode", "Exit privacy mode", ""); + self.update_privacy_mode(impl_key, false); } back_notification::PrivacyModeState::PrvOffByPeer => { self.handler .msgbox("custom-error", "Privacy mode", "Peer exit", ""); - self.update_privacy_mode(false); + self.update_privacy_mode(impl_key, false); } back_notification::PrivacyModeState::PrvOffFailed => { self.handler.msgbox( @@ -1697,7 +1729,7 @@ impl Remote { self.handler .msgbox("custom-error", "Privacy mode", "Turned off", ""); // log::error!("Privacy mode is turned off with unknown reason"); - self.update_privacy_mode(false); + self.update_privacy_mode(impl_key, false); } _ => {} } diff --git a/src/common.rs b/src/common.rs index be5e223e0..23612488c 100644 --- a/src/common.rs +++ b/src/common.rs @@ -378,19 +378,6 @@ pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc>>) } } -pub async fn send_opts_after_login( - config: &crate::client::LoginConfigHandler, - peer: &mut FramedStream, -) { - if let Some(opts) = config.get_option_message_after_login() { - let mut misc = Misc::new(); - misc.set_option(opts); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - allow_err!(peer.send(&msg_out).await); - } -} - #[cfg(feature = "use_rubato")] pub fn resample_channels( data: &[f32], @@ -1070,10 +1057,12 @@ pub async fn post_request_sync(url: String, body: String, header: &str) -> Resul pub fn make_privacy_mode_msg_with_details( state: back_notification::PrivacyModeState, details: String, + impl_key: String, ) -> Message { let mut misc = Misc::new(); let mut back_notification = BackNotification { details, + impl_key, ..Default::default() }; back_notification.set_privacy_mode_state(state); @@ -1084,8 +1073,8 @@ pub fn make_privacy_mode_msg_with_details( } #[inline] -pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Message { - make_privacy_mode_msg_with_details(state, "".to_owned()) +pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState, impl_key: String) -> Message { + make_privacy_mode_msg_with_details(state, "".to_owned(), impl_key) } pub fn is_keyboard_mode_supported( diff --git a/src/core_main.rs b/src/core_main.rs index f1669d36b..5f45cd773 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -244,6 +244,8 @@ pub fn core_main() -> Option> { return None; } else if args[0] == "--server" { log::info!("start --server with user {}", crate::username()); + #[cfg(all(windows, feature = "virtual_display_driver"))] + crate::privacy_mode::restore_reg_connectivity(); #[cfg(any(target_os = "linux", target_os = "windows"))] { crate::start_server(true); diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index f4ef1434e..761d0d438 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -224,6 +224,12 @@ pub fn session_toggle_option(session_id: SessionID, value: String) { } } +pub fn session_toggle_privacy_mode(session_id: SessionID, impl_key: String, on: bool) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.toggle_privacy_mode(impl_key, on); + } +} + pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option { if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_flutter_option(k)) @@ -1974,6 +1980,17 @@ pub fn is_selinux_enforcing() -> SyncReturn { } } +pub fn main_default_privacy_mode_impl() -> SyncReturn { + SyncReturn(crate::privacy_mode::DEFAULT_PRIVACY_MODE_IMPL.to_owned()) +} + +pub fn main_supported_privacy_mode_impls() -> SyncReturn { + SyncReturn( + serde_json::to_string(&crate::privacy_mode::get_supported_privacy_mode_impl()) + .unwrap_or_default(), + ) +} + #[cfg(target_os = "android")] pub mod server_side { use hbb_common::{config, log}; diff --git a/src/ipc.rs b/src/ipc.rs index 153b900f9..02f0e2dc5 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -28,7 +28,7 @@ use hbb_common::{ ResultType, }; -use crate::rendezvous_mediator::RendezvousMediator; +use crate::{common::is_server, privacy_mode, rendezvous_mediator::RendezvousMediator}; // State with timestamp, because std::time::Instant cannot be serialized #[derive(Debug, Serialize, Deserialize, Clone)] @@ -207,7 +207,7 @@ pub enum Data { #[cfg(not(any(target_os = "android", target_os = "ios")))] ClipboardFile(ClipboardFile), ClipboardFileEnabled(bool), - PrivacyModeState((i32, PrivacyModeState)), + PrivacyModeState((i32, PrivacyModeState, String)), TestRendezvousServer, #[cfg(not(any(target_os = "android", target_os = "ios")))] Keyboard(DataKeyboard), @@ -352,6 +352,9 @@ async fn handle(data: Data, stream: &mut Connection) { if EXIT_RECV_CLOSE.load(Ordering::SeqCst) { #[cfg(not(target_os = "android"))] crate::server::input_service::fix_key_down_timeout_at_exit(); + if is_server() { + let _ = privacy_mode::turn_off_privacy(0, Some(PrivacyModeState::OffByPeer)); + } std::process::exit(0); } } @@ -442,6 +445,9 @@ async fn handle(data: Data, stream: &mut Connection) { } Some(value) => { let _chk = CheckIfRestart::new(); + if let Some(v) = value.get("privacy-mode-impl-key") { + crate::privacy_mode::switch(v); + } Config::set_options(value); allow_err!(stream.send(&Data::Options(None)).await); } diff --git a/src/keyboard.rs b/src/keyboard.rs index 3baeef2ec..a693e8b58 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -177,7 +177,6 @@ pub mod client { Key::MetaRight => Some(ControlKey::RWin), _ => None, } - } pub fn event_lock_screen() -> KeyEvent { diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 8346144d3..95f0a8a04 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "خروج القرين"), ("Failed to turn off", "فشل ايقاف التشغيل"), ("Turned off", "مطفئ"), - ("In privacy mode", "في وضع الخصوصية"), - ("Out privacy mode", "الخروج من وضع الخصوصية"), ("Language", "اللغة"), ("Keep RustDesk background service", "ابق خدمة RustDesk تعمل في الخلفية"), ("Ignore Battery Optimizations", "تجاهل تحسينات البطارية"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 33cf3cd92..37ac295aa 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "El peer ha sortit"), ("Failed to turn off", "Error en apagar"), ("Turned off", "Apagat"), - ("In privacy mode", "En mode de privacitat"), - ("Out privacy mode", "Fora del mode de privacitat"), ("Language", "Idioma"), ("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"), ("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 35f6d58db..a838701d5 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "被控端退出"), ("Failed to turn off", "退出失败"), ("Turned off", "退出"), - ("In privacy mode", "进入隐私模式"), - ("Out privacy mode", "退出隐私模式"), ("Language", "语言"), ("Keep RustDesk background service", "保持 RustDesk 后台服务"), ("Ignore Battery Optimizations", "忽略电池优化"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "真彩模式(4:4:4)"), ("Enable blocking user input", "允许阻止用户输入"), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", "模式 1 (不推荐)"), + ("privacy_mode_impl_virtual_display_tip", "模式 2 (推荐)"), + ("Enter privacy mode", "进入隐私模式"), + ("Exit privacy mode", "退出隐私模式"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index d8fd2f7ec..993f136f4 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Ukončení protistrany"), ("Failed to turn off", "Nepodařilo se vypnout"), ("Turned off", "Vypnutý"), - ("In privacy mode", "v režimu ochrany soukromí"), - ("Out privacy mode", "mimo režim ochrany soukromí"), ("Language", "Jazyk"), ("Keep RustDesk background service", "Zachovat službu RustDesk na pozadí"), ("Ignore Battery Optimizations", "Ignorovat optimalizaci baterie"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Skutečné barvy (4:4:4)"), ("Enable blocking user input", "Povolit blokování uživatelského vstupu"), ("id_input_tip", "Můžete zadat ID, přímou IP adresu nebo doménu s portem (:).\nPokud chcete přistupovat k zařízení na jiném serveru, připojte adresu serveru (@?key=), například,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nPokud chcete přistupovat k zařízení na veřejném serveru, zadejte \"@public\", klíč není pro veřejný server potřeba."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 173fc7cef..7cbe8d99d 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Modpart-Afslut"), ("Failed to turn off", "Mislykkedes i at lukke ned"), ("Turned off", "Slukket"), - ("In privacy mode", "I privatlivstilstand"), - ("Out privacy mode", "Privatlivstilstand fra"), ("Language", "Sprog"), ("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"), ("Ignore Battery Optimizations", "Ignorér betteri optimeringer"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 2fdef036b..9017a744a 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Die Gegenstelle hat die Verbindung getrennt."), ("Failed to turn off", "Ausschalten fehlgeschlagen"), ("Turned off", "Ausgeschaltet"), - ("In privacy mode", "Datenschutzmodus aktivieren"), - ("Out privacy mode", "Datenschutzmodus deaktivieren"), ("Language", "Sprache"), ("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"), ("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "True Color (4:4:4)"), ("Enable blocking user input", "Blockieren von Benutzereingaben aktivieren"), ("id_input_tip", "Sie können eine ID, eine direkte IP oder eine Domäne mit einem Port (:) eingeben.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen möchten, fügen Sie bitte die Serveradresse (@?key=) hinzu, zum Beispiel\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nWenn Sie auf ein Gerät auf einem öffentlichen Server zugreifen wollen, geben Sie bitte \"@public\" ein. Der Schlüssel wird für öffentliche Server nicht benötigt."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 16b9bb508..46577ce90 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Ο απομακρυσμένος σταθμός έχει αποσυνδεθεί"), ("Failed to turn off", "Αποτυχία απενεργοποίησης"), ("Turned off", "Απενεργοποιημένο"), - ("In privacy mode", "Σε λειτουργία απορρήτου"), - ("Out privacy mode", "Εκτός λειτουργίας απορρήτου"), ("Language", "Γλώσσα"), ("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"), ("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 9e77b6ad5..3afb27bc6 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -203,5 +203,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("elevated_switch_display_msg", "Switch to the primary display because multiple displays are not supported in elevated mode."), ("selinux_tip", "SELinux is enabled on your device, which may prevent RustDesk from running properly as controlled side."), ("id_input_tip", "You can input an ID, a direct IP, or a domain with a port (:).\nIf you want to access a device on another server, please append the server address (@?key=), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"@public\", the key is not needed for public server"), + ("privacy_mode_impl_mag_tip", "Mode 1 (deprecated)"), + ("privacy_mode_impl_virtual_display_tip", "Mode 2 (recommended)"), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index eaf837dad..35230ccb1 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", ""), ("Failed to turn off", ""), ("Turned off", ""), - ("In privacy mode", ""), - ("Out privacy mode", ""), ("Language", ""), ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 8492fb572..cfbe50fa8 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Par salio"), ("Failed to turn off", "Error al apagar"), ("Turned off", "Apagado"), - ("In privacy mode", "En modo de privacidad"), - ("Out privacy mode", "Fuera del modo de privacidad"), ("Language", "Idioma"), ("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"), ("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Color real (4:4:4)"), ("Enable blocking user input", "Habilitar el bloqueo de la entrada del usuario"), ("id_input_tip", "Puedes introducir una ID, una IP directa o un dominio con un puerto (:).\nSi quieres acceder a un dispositivo en otro servidor, por favor añade la ip del servidor (@?key=), por ejemplo,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSi quieres acceder a un dispositivo en un servidor público, por favor, introduce \"@public\", la clave no es necesaria para un servidor público."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index f206b098f..0d8c8e356 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "میزبان خارج شد"), ("Failed to turn off", "خاموش کردن انجام نشد"), ("Turned off", "خاموش شد"), - ("In privacy mode", "در حالت حریم خصوصی"), - ("Out privacy mode", "خارج از حالت حریم خصوصی"), ("Language", "زبان"), ("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"), ("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index c457cd75b..7f840558e 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Appareil distant déconnecté"), ("Failed to turn off", "Échec de la désactivation"), ("Turned off", "Désactivé"), - ("In privacy mode", "en mode privé"), - ("Out privacy mode", "hors mode de confidentialité"), ("Language", "Langue"), ("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"), ("Ignore Battery Optimizations", "Ignorer les optimisations batterie"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 61f887645..298a07d6e 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "A távoli fél kilépett"), ("Failed to turn off", "Nem sikerült kikapcsolni"), ("Turned off", "Kikapcsolva"), - ("In privacy mode", "Belépés inkognitó módba"), - ("Out privacy mode", "Kilépés inkognitó módból"), ("Language", "Nyelv"), ("Keep RustDesk background service", "RustDesk futtatása a háttérben"), ("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 7ca7ffa38..128a805c5 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Rekan keluar"), ("Failed to turn off", "Gagal mematikan"), ("Turned off", "Dimatikan"), - ("In privacy mode", "Dalam mode privasi"), - ("Out privacy mode", "Keluar dari mode privasi"), ("Language", "Bahasa"), ("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada service background"), ("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", "Aktifkan pemblokiran input pengguna"), ("id_input_tip", "Kamu bisa memasukkan ID, IP langsung, atau domain dengan port kostum yang sudah ditentukan (:).\nJika kamu ingin mengakses perangkat lain yang berbeda server, tambahkan alamat server setelah penulisan ID(@?key=), sebagai contoh,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJika kamu ingin mengakses perangkat yang menggunakan server publik, masukkan \"@public\", server public tidak memerlukan key khusus"), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index f5b651eab..82851a199 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Uscita dal dispostivo remoto"), ("Failed to turn off", "Impossibile spegnere"), ("Turned off", "Spegni"), - ("In privacy mode", "In modalità privacy"), - ("Out privacy mode", "Uscita dalla modalità privacy"), ("Language", "Lingua"), ("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"), ("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Colore reale (4:4:4)"), ("Enable blocking user input", "Abilita blocco input utente"), ("id_input_tip", "Puoi inserire un ID, un IP diretto o un dominio con una porta (:).\nSe vuoi accedere as un dispositivo in un altro server, aggiungi l'indirizzo del server (@?key=), ad esempio\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSe vuoi accedere as un dispositivo in un server pubblico, inserisci \"@public\", per il server pubblico la chiave non è necessaria"), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 0014692fd..a5c0dff2f 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "相手が終了しました"), ("Failed to turn off", "オフにできませんでした"), ("Turned off", "オフになりました"), - ("In privacy mode", "プライバシーモード開始"), - ("Out privacy mode", "プライバシーモード終了"), ("Language", "言語"), ("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"), ("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index f92598613..4a0d59b55 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "다른 사용자가 나감"), ("Failed to turn off", "종료에 실패함"), ("Turned off", "종료됨"), - ("In privacy mode", "개인정보 보호 모드 진입"), - ("Out privacy mode", "개인정보 보호 모드 나감"), ("Language", "언어"), ("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"), ("Ignore Battery Optimizations", "배터리 최적화 무시하기"), @@ -569,8 +567,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Plug out all", "모두 플러그 아웃"), ("True color (4:4:4)", "트루컬러(4:4:4)"), ("Enable blocking user input", "사용자 입력 차단 허용"), - ("id_input_tip", "입력된 ID, IP, 도메인과 포트(:)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(@?key=)를 추가하세요. - 예:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\n - 공개 서버의 장치에 액세스하려면 \"@public\",을 입력하세요. 공개 서버에는 키가 필요하지 않습니다"), + ("id_input_tip", "입력된 ID, IP, 도메인과 포트(:)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(@?key=)를 추가하세요"), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index c0364fc8d..5d9ec3f3a 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Пирдің шығуы"), ("Failed to turn off", "Сөндіру сәтсіз болды"), ("Turned off", "Өшірілген"), - ("In privacy mode", "Құпиялылық модасында"), - ("Out privacy mode", "Құпиялылық модасынан Шығу"), ("Language", "Тіл"), ("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"), ("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 72297cb82..69b210311 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Nuotolinis mazgas neveikia"), ("Failed to turn off", "Nepavyko išjungti"), ("Turned off", "Išjungti"), - ("In privacy mode", "Privatumo režimas"), - ("Out privacy mode", "Išėjimas iš privatumo režimo"), ("Language", "Kalba"), ("Keep RustDesk background service", "Palikti RustDesk fonine paslauga"), ("Ignore Battery Optimizations", "Ignoruoti akumuliatoriaus optimizavimą"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 066da1074..c5876c695 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Iziet no attālās ierīces"), ("Failed to turn off", "Neizdevās izslēgt"), ("Turned off", "Izslēgts"), - ("In privacy mode", "Privātuma režīmā"), - ("Out privacy mode", "Izslēgts privātuma režīms"), ("Language", "Valoda"), ("Keep RustDesk background service", "Saglabāt RustDesk fona servisu"), ("Ignore Battery Optimizations", "Ignorēt akumulatora optimizāciju"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Īstā krāsa (4:4:4)"), ("Enable blocking user input", "Iespējot lietotāja ievades bloķēšanu"), ("id_input_tip", "Varat ievadīt ID, tiešo IP vai domēnu ar portu (:).\nJa vēlaties piekļūt ierīcei citā serverī, lūdzu, pievienojiet servera adresi (@?key=), piemēram,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJa vēlaties piekļūt ierīcei publiskajā serverī, lūdzu, ievadiet \"@public\", publiskajam serverim atslēga nav nepieciešama"), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 71e353029..280f5e9a0 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Peer afgesloten"), ("Failed to turn off", "Uitschakelen mislukt"), ("Turned off", "Uitgeschakeld"), - ("In privacy mode", "In privacymodus"), - ("Out privacy mode", "Uit privacymodus"), ("Language", "Taal"), ("Keep RustDesk background service", "RustDesk achtergronddienst behouden"), ("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Ware kleur (4:4:4)"), ("Enable blocking user input", "Blokkeren van gebruikersinvoer inschakelen"), ("id_input_tip", "Je kunt een ID, een direct IP of een domein met een poort (:) invoeren. Als je toegang wilt als apparaat op een andere server, voeg dan het serveradres toe (@?key=), bijvoorbeeld \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.Als je toegang wilt als apparaat op een openbare server, voer dan \"@public\" in, voor de openbare server is de sleutel niet nodig."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 20e2e865f..75066f7ab 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Wyjście ze zdalnego urządzenia"), ("Failed to turn off", "Nie udało się wyłączyć"), ("Turned off", "Wyłączony"), - ("In privacy mode", "Uruchom tryb prywatności"), - ("Out privacy mode", "Opuść tryb prywatności"), ("Language", "Język"), ("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"), ("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "True color (4:4:4)"), ("Enable blocking user input", "Zablokuj wprowadzanie danych przez użytkownika"), ("id_input_tip", "Możesz wprowadzić identyfikator, bezpośredni adres IP lub domenę z portem (:).\nJeżeli chcesz uzyskać dostęp do urządzenia na innym serwerze, dołącz adres serwera (@?key=, np. \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJeżeli chcesz uzyskać dostęp do urządzenia na serwerze publicznym, wpisz \"@public\", klucz nie jest potrzebny dla serwera publicznego."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index adff02864..8ca5a0b84 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Saída do Remoto"), ("Failed to turn off", "Falha ao desligar"), ("Turned off", "Desligado"), - ("In privacy mode", "Em modo de privacidade"), - ("Out privacy mode", "Sair do modo de privacidade"), ("Language", "Linguagem"), ("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"), ("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index c1f589113..708784c20 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Parceiro saiu"), ("Failed to turn off", "Falha ao desligar"), ("Turned off", "Desligado"), - ("In privacy mode", "No modo de privacidade"), - ("Out privacy mode", "Fora do modo de privacidade"), ("Language", "Idioma"), ("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"), ("Ignore Battery Optimizations", "Ignorar otimizações de bateria"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 1e22e09f6..c1eac74f8 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Ieșire dispozitiv pereche"), ("Failed to turn off", "Dezactivare nereușită"), ("Turned off", "Închis"), - ("In privacy mode", "În modul privat"), - ("Out privacy mode", "Ieșit din modul privat"), ("Language", "Limbă"), ("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"), ("Ignore Battery Optimizations", "Ignoră optimizările de baterie"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 628700021..d0087c7e9 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Удалённый узел отключён"), ("Failed to turn off", "Невозможно отключить"), ("Turned off", "Отключён"), - ("In privacy mode", "В режиме конфиденциальности"), - ("Out privacy mode", "Выход из режима конфиденциальности"), ("Language", "Язык"), ("Keep RustDesk background service", "Держать в фоне службу RustDesk"), ("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Истинный цвет (4:4:4)"), ("Enable blocking user input", "Блокировать ввод пользователя"), ("id_input_tip", "Можно ввести идентификатор, прямой IP-адрес или домен с портом (<домен>:<порт>).\nЕсли необходимо получить доступ к устройству на другом сервере, добавьте адрес сервера (@<адрес_сервера>?key=<ключ_значение>), например:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли необходимо получить доступ к устройству на общедоступном сервере, введите \"@public\", ключ для публичного сервера не требуется."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index f7111cfa7..072b532e6 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Peer exit"), ("Failed to turn off", "Nepodarilo sa vypnúť"), ("Turned off", "Vypnutý"), - ("In privacy mode", "V režime súkromia"), - ("Out privacy mode", "Mimo režimu súkromia"), ("Language", "Jazyk"), ("Keep RustDesk background service", "Ponechať službu RustDesk na pozadí"), ("Ignore Battery Optimizations", "Ignorovať optimalizácie batérie"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Skutočná farba (4:4:4)"), ("Enable blocking user input", "Povoliť blokovanie vstupu od používateľa"), ("id_input_tip", "Môžete zadať ID, priamu IP adresu alebo doménu s portom (:).\nAk chcete získať prístup k zariadeniu na inom serveri, doplňte adresu servera (@?key=), napríklad,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAk chcete získať prístup k zariadeniu na verejnom serveri, zadajte \"@public\", kľúč nie je potrebný pre verejný server."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 39b694909..382b8a89d 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Odjemalec se je zaprl"), ("Failed to turn off", "Ni bilo mogoče izklopiti"), ("Turned off", "Izklopljeno"), - ("In privacy mode", "V zasebnem načinu"), - ("Out privacy mode", "Iz zasebnega načina"), ("Language", "Jezik"), ("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"), ("Ignore Battery Optimizations", "Prezri optimizacije baterije"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 7bc044116..c47a154ca 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Dalje peer"), ("Failed to turn off", "Dështoi të fiket"), ("Turned off", "I fikur"), - ("In privacy mode", "Në modalitetin e privatësisë"), - ("Out privacy mode", "Jashtë modaliteti i privatësisë"), ("Language", "Gjuha"), ("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"), ("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 8df9454e2..832e81283 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Klijent izašao"), ("Failed to turn off", "Greška kod isključenja"), ("Turned off", "Isključeno"), - ("In privacy mode", "U modu privatnosti"), - ("Out privacy mode", "Van moda privatnosti"), ("Language", "Jezik"), ("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"), ("Ignore Battery Optimizations", "Zanemari optimizacije baterije"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index d110f6c2d..b2e42a291 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Avsluta klient"), ("Failed to turn off", "Misslyckades med avstängning"), ("Turned off", "Avstängd"), - ("In privacy mode", "I säkerhetsläge"), - ("Out privacy mode", "Ur säkerhetsläge"), ("Language", "Språk"), ("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"), ("Ignore Battery Optimizations", "Ignorera batterioptimering"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index f6ca3f7c4..027baebad 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", ""), ("Failed to turn off", ""), ("Turned off", ""), - ("In privacy mode", ""), - ("Out privacy mode", ""), ("Language", ""), ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 2bce9eb36..b81b77b0a 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "อีกฝั่งออก"), ("Failed to turn off", "การปิดล้มเหลว"), ("Turned off", "ปิด"), - ("In privacy mode", "อยู่ในโหมดความเป็นส่วนตัว"), - ("Out privacy mode", "อยู่นอกโหมดความเป็นส่วนตัว"), ("Language", "ภาษา"), ("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"), ("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 6dcab0848..3fc58963a 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "eş çıkışı"), ("Failed to turn off", "kapatılamadı"), ("Turned off", "Kapatıldı"), - ("In privacy mode", "Gizlilik modunda"), - ("Out privacy mode", "Gizlilik modu dışında"), ("Language", "Dil"), ("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"), ("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 33583f339..4ea5bc250 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "對方退出"), ("Failed to turn off", "關閉失敗"), ("Turned off", "已關閉"), - ("In privacy mode", "開啟隱私模式"), - ("Out privacy mode", "退出隱私模式"), ("Language", "語言"), ("Keep RustDesk background service", "保持 RustDesk 後台服務"), ("Ignore Battery Optimizations", "忽略電池最佳化"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 94db41c2b..c20e25948 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Вийти з віддаленого пристрою"), ("Failed to turn off", "Не вдалося вимкнути"), ("Turned off", "Вимкнений"), - ("In privacy mode", "У режимі конфіденційності"), - ("Out privacy mode", "Вихід із режиму конфіденційності"), ("Language", "Мова"), ("Keep RustDesk background service", "Зберегти фонову службу RustDesk"), ("Ignore Battery Optimizations", "Ігнорувати оптимізації батареї"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Спражній колір (4:4:4)"), ("Enable blocking user input", "Увімкнути блокування введення користувача"), ("id_input_tip", "Ви можете ввести ID, безпосередню IP, або ж домен з портом (<домен>:<порт>).\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"@public\", ключ для публічного сервера не потрібен."), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index c2e37cf99..89b6a189f 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Peer exit", "Người dùng từ xa đã thoát"), ("Failed to turn off", "Không thể tắt"), ("Turned off", "Đã tắt"), - ("In privacy mode", "Vào chế độ riêng tư"), - ("Out privacy mode", "Thoát chế độ riêng tư"), ("Language", "Ngôn ngữ"), ("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"), ("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"), @@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", ""), ("id_input_tip", ""), + ("privacy_mode_impl_mag_tip", ""), + ("privacy_mode_impl_virtual_display_tip", ""), + ("Enter privacy mode", ""), + ("Exit privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lib.rs b/src/lib.rs index 8aa520579..2498045a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,8 +61,7 @@ mod hbbs_http; #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] pub mod clipboard_file; -#[cfg(windows)] -pub mod privacy_win_mag; +pub mod privacy_mode; #[cfg(all(windows, feature = "virtual_display_driver"))] pub mod virtual_display_manager; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 4e1263b54..89458dfe2 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -3,7 +3,7 @@ use crate::common::PORTABLE_APPNAME_RUNTIME_ENV_KEY; use crate::{ ipc, license::*, - privacy_win_mag::{self, WIN_MAG_INJECTED_PROCESS_EXE}, + privacy_mode::win_mag::{self, WIN_MAG_INJECTED_PROCESS_EXE}, }; use hbb_common::{ allow_err, @@ -472,7 +472,7 @@ async fn run_service(_arguments: Vec) -> ResultType<()> { log::info!("Got service control event: {:?}", control_event); match control_event { ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, - ServiceControl::Stop => { + ServiceControl::Stop | ServiceControl::Preshutdown | ServiceControl::Shutdown => { send_close(crate::POSTFIX_SERVICE).ok(); ServiceControlHandlerResult::NoError } @@ -848,8 +848,8 @@ fn get_default_install_path() -> String { } pub fn check_update_broker_process() -> ResultType<()> { - let process_exe = privacy_win_mag::INJECTED_PROCESS_EXE; - let origin_process_exe = privacy_win_mag::ORIGIN_PROCESS_EXE; + let process_exe = win_mag::INJECTED_PROCESS_EXE; + let origin_process_exe = win_mag::ORIGIN_PROCESS_EXE; let exe_file = std::env::current_exe()?; let Some(cur_dir) = exe_file.parent() else { @@ -926,8 +926,8 @@ pub fn copy_exe_cmd(src_exe: &str, exe: &str, path: &str) -> ResultType {main_exe} copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\" ", - ORIGIN_PROCESS_EXE = privacy_win_mag::ORIGIN_PROCESS_EXE, - broker_exe = privacy_win_mag::INJECTED_PROCESS_EXE, + ORIGIN_PROCESS_EXE = win_mag::ORIGIN_PROCESS_EXE, + broker_exe = win_mag::INJECTED_PROCESS_EXE, )) } diff --git a/src/privacy_mode.rs b/src/privacy_mode.rs new file mode 100644 index 000000000..105367cc4 --- /dev/null +++ b/src/privacy_mode.rs @@ -0,0 +1,313 @@ +#[cfg(all(windows, feature = "virtual_display_driver"))] +use crate::platform::is_installed; +#[cfg(windows)] +use crate::{ + display_service, + ipc::{connect, Data}, +}; +use crate::{ipc::PrivacyModeState, ui_interface::get_option}; +#[cfg(windows)] +use hbb_common::tokio; +use hbb_common::{anyhow::anyhow, bail, lazy_static, ResultType}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +#[cfg(windows)] +mod win_input; + +#[cfg(windows)] +pub mod win_mag; + +#[cfg(all(windows, feature = "virtual_display_driver"))] +mod win_virtual_display; +#[cfg(all(windows, feature = "virtual_display_driver"))] +pub use win_virtual_display::restore_reg_connectivity; + +pub const INVALID_PRIVACY_MODE_CONN_ID: i32 = 0; +pub const OCCUPIED: &'static str = "Privacy occupied by another one"; +pub const TURN_OFF_OTHER_ID: &'static str = + "Failed to turn off privacy mode that belongs to someone else"; +pub const NO_DISPLAYS: &'static str = "No displays"; + +#[cfg(windows)] +pub const PRIVACY_MODE_IMPL_WIN_MAG: &str = win_mag::PRIVACY_MODE_IMPL; + +#[cfg(all(windows, feature = "virtual_display_driver"))] +pub const PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY: &str = win_virtual_display::PRIVACY_MODE_IMPL; + +pub trait PrivacyMode: Sync + Send { + fn init(&self) -> ResultType<()>; + fn clear(&mut self); + fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType; + fn turn_off_privacy(&mut self, conn_id: i32, state: Option) + -> ResultType<()>; + + fn pre_conn_id(&self) -> i32; + + #[inline] + fn check_on_conn_id(&self, conn_id: i32) -> ResultType { + let pre_conn_id = self.pre_conn_id(); + if pre_conn_id == conn_id { + return Ok(true); + } + if pre_conn_id != INVALID_PRIVACY_MODE_CONN_ID { + bail!(OCCUPIED); + } + Ok(false) + } + + #[inline] + fn check_off_conn_id(&self, conn_id: i32) -> ResultType<()> { + let pre_conn_id = self.pre_conn_id(); + if pre_conn_id != INVALID_PRIVACY_MODE_CONN_ID + && conn_id != INVALID_PRIVACY_MODE_CONN_ID + && pre_conn_id != conn_id + { + bail!(TURN_OFF_OTHER_ID) + } + Ok(()) + } +} + +lazy_static::lazy_static! { + pub static ref DEFAULT_PRIVACY_MODE_IMPL: String = { + #[cfg(windows)] + { + if display_service::is_privacy_mode_mag_supported() { + PRIVACY_MODE_IMPL_WIN_MAG + } else { + #[cfg(feature = "virtual_display_driver")] + { + if is_installed() { + PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY + } else { + "" + } + } + #[cfg(not(feature = "virtual_display_driver"))] + { + "" + } + }.to_owned() + } + #[cfg(not(windows))] + { + "".to_owned() + } + }; + + static ref CUR_PRIVACY_MODE_IMPL: Arc> = { + let mut cur_impl = get_option("privacy-mode-impl-key".to_owned()); + if !get_supported_privacy_mode_impl().iter().any(|(k, _)| k == &cur_impl) { + cur_impl = DEFAULT_PRIVACY_MODE_IMPL.to_owned(); + } + Arc::new(Mutex::new(cur_impl)) + }; + static ref PRIVACY_MODE: Arc>>> = { + let cur_impl = (*CUR_PRIVACY_MODE_IMPL.lock().unwrap()).clone(); + let privacy_mode = match PRIVACY_MODE_CREATOR.lock().unwrap().get(&(&cur_impl as &str)) { + Some(creator) => Some(creator()), + None => None, + }; + Arc::new(Mutex::new(privacy_mode)) + }; +} + +pub type PrivacyModeCreator = fn() -> Box; +lazy_static::lazy_static! { + static ref PRIVACY_MODE_CREATOR: Arc>> = { + #[cfg(not(windows))] + let map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new(); + #[cfg(windows)] + let mut map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new(); + #[cfg(windows)] + { + map.insert(win_mag::PRIVACY_MODE_IMPL, || { + Box::new(win_mag::PrivacyModeImpl::default()) + }); + + #[cfg(feature = "virtual_display_driver")] + map.insert(win_virtual_display::PRIVACY_MODE_IMPL, || { + Box::new(win_virtual_display::PrivacyModeImpl::default()) + }); + } + Arc::new(Mutex::new(map)) + }; +} + +#[inline] +pub fn init() -> Option> { + Some(PRIVACY_MODE.lock().unwrap().as_ref()?.init()) +} + +#[inline] +pub fn clear() -> Option<()> { + Some(PRIVACY_MODE.lock().unwrap().as_mut()?.clear()) +} + +#[inline] +pub fn switch(impl_key: &str) { + let mut cur_impl_lock = CUR_PRIVACY_MODE_IMPL.lock().unwrap(); + if *cur_impl_lock == impl_key { + return; + } + if let Some(creator) = PRIVACY_MODE_CREATOR.lock().unwrap().get(impl_key) { + *PRIVACY_MODE.lock().unwrap() = Some(creator()); + *cur_impl_lock = impl_key.to_owned(); + } +} + +fn get_supported_impl(impl_key: &str) -> String { + let supported_impls = get_supported_privacy_mode_impl(); + if supported_impls.iter().any(|(k, _)| k == &impl_key) { + return impl_key.to_owned(); + }; + // fallback + let mut cur_impl = get_option("privacy-mode-impl-key".to_owned()); + if !get_supported_privacy_mode_impl() + .iter() + .any(|(k, _)| k == &cur_impl) + { + // fallback + cur_impl = DEFAULT_PRIVACY_MODE_IMPL.to_owned(); + } + cur_impl +} + +#[inline] +pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> { + // Check if privacy mode is already on or occupied by another one + let mut privacy_mode_lock = PRIVACY_MODE.lock().unwrap(); + + // Check or switch privacy mode implementation + let impl_key = get_supported_impl(impl_key); + let mut cur_impl_lock = CUR_PRIVACY_MODE_IMPL.lock().unwrap(); + + if let Some(privacy_mode) = privacy_mode_lock.as_ref() { + let check_on_conn_id = privacy_mode.check_on_conn_id(conn_id); + match check_on_conn_id.as_ref() { + Ok(true) => { + if *cur_impl_lock == impl_key { + return Some(Ok(true)); + } else { + // Same peer, switch to new implementation. + } + } + Err(_) => return Some(check_on_conn_id), + _ => {} + } + } + + if *cur_impl_lock != impl_key { + if let Some(creator) = PRIVACY_MODE_CREATOR + .lock() + .unwrap() + .get(&(&impl_key as &str)) + { + if let Some(privacy_mode) = privacy_mode_lock.as_mut() { + privacy_mode.clear(); + } + + *privacy_mode_lock = Some(creator()); + *cur_impl_lock = impl_key.to_owned(); + } else { + return Some(Err(anyhow!("Unsupported privacy mode: {}", impl_key))); + } + } + + // turn on privacy mode + Some(privacy_mode_lock.as_mut()?.turn_on_privacy(conn_id)) +} + +#[inline] +pub fn turn_off_privacy(conn_id: i32, state: Option) -> Option> { + Some( + PRIVACY_MODE + .lock() + .unwrap() + .as_mut()? + .turn_off_privacy(conn_id, state), + ) +} + +#[inline] +pub fn check_on_conn_id(conn_id: i32) -> Option> { + Some( + PRIVACY_MODE + .lock() + .unwrap() + .as_ref()? + .check_on_conn_id(conn_id), + ) +} + +#[cfg(windows)] +#[tokio::main(flavor = "current_thread")] +async fn set_privacy_mode_state( + conn_id: i32, + state: PrivacyModeState, + impl_key: String, + ms_timeout: u64, +) -> ResultType<()> { + let mut c = connect(ms_timeout, "_cm").await?; + c.send(&Data::PrivacyModeState((conn_id, state, impl_key))) + .await +} + +pub fn get_supported_privacy_mode_impl() -> Vec<(&'static str, &'static str)> { + #[cfg(target_os = "windows")] + { + let mut vec_impls = Vec::new(); + if display_service::is_privacy_mode_mag_supported() { + vec_impls.push((PRIVACY_MODE_IMPL_WIN_MAG, "privacy_mode_impl_mag_tip")); + } + #[cfg(feature = "virtual_display_driver")] + if is_installed() { + vec_impls.push(( + PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY, + "privacy_mode_impl_virtual_display_tip", + )); + } + vec_impls + } + #[cfg(not(target_os = "windows"))] + { + Vec::new() + } +} + +#[inline] +pub fn is_current_privacy_mode_impl(impl_key: &str) -> bool { + *CUR_PRIVACY_MODE_IMPL.lock().unwrap() == impl_key +} + +#[inline] +#[cfg(not(windows))] +pub fn check_privacy_mode_err( + _privacy_mode_id: i32, + _display_idx: usize, + _timeout_millis: u64, +) -> String { + "".to_owned() +} + +#[inline] +#[cfg(windows)] +pub fn check_privacy_mode_err( + privacy_mode_id: i32, + display_idx: usize, + timeout_millis: u64, +) -> String { + if is_current_privacy_mode_impl(PRIVACY_MODE_IMPL_WIN_MAG) { + crate::video_service::test_create_capturer(privacy_mode_id, display_idx, timeout_millis) + } else { + "".to_owned() + } +} + +#[inline] +pub fn is_privacy_mode_supported() -> bool { + !DEFAULT_PRIVACY_MODE_IMPL.is_empty() +} diff --git a/src/privacy_mode/win_input.rs b/src/privacy_mode/win_input.rs new file mode 100644 index 000000000..1332ab75e --- /dev/null +++ b/src/privacy_mode/win_input.rs @@ -0,0 +1,258 @@ +use hbb_common::{allow_err, bail, lazy_static, log, ResultType}; +use std::sync::{ + mpsc::{channel, Sender}, + Mutex, +}; +use winapi::{ + ctypes::c_int, + shared::{ + minwindef::{DWORD, FALSE, HMODULE, LOBYTE, LPARAM, LRESULT, UINT, WPARAM}, + ntdef::NULL, + windef::{HHOOK, POINT}, + }, + um::{ + errhandlingapi::GetLastError, libloaderapi::GetModuleHandleExA, + processthreadsapi::GetCurrentThreadId, winuser::*, + }, +}; + +const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2; +const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4; + +const WM_USER_EXIT_HOOK: u32 = WM_USER + 1; + +lazy_static::lazy_static! { + static ref CUR_HOOK_THREAD_ID: Mutex = Mutex::new(0); +} + +fn do_hook(tx: Sender) -> ResultType<(HHOOK, HHOOK)> { + let invalid_ret = (0 as HHOOK, 0 as HHOOK); + + let mut cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap(); + if *cur_hook_thread_id != 0 { + // unreachable! + tx.send("Already hooked".to_owned())?; + return Ok(invalid_ret); + } + + unsafe { + let mut hm_keyboard = 0 as HMODULE; + if 0 == GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + DefWindowProcA as _, + &mut hm_keyboard as _, + ) { + tx.send(format!( + "Failed to GetModuleHandleExA, error: {}", + GetLastError() + ))?; + return Ok(invalid_ret); + } + let mut hm_mouse = 0 as HMODULE; + if 0 == GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + DefWindowProcA as _, + &mut hm_mouse as _, + ) { + tx.send(format!( + "Failed to GetModuleHandleExA, error: {}", + GetLastError() + ))?; + return Ok(invalid_ret); + } + + let hook_keyboard = SetWindowsHookExA( + WH_KEYBOARD_LL, + Some(privacy_mode_hook_keyboard), + hm_keyboard, + 0, + ); + if hook_keyboard.is_null() { + tx.send(format!(" SetWindowsHookExA keyboard {}", GetLastError()))?; + return Ok(invalid_ret); + } + + let hook_mouse = SetWindowsHookExA(WH_MOUSE_LL, Some(privacy_mode_hook_mouse), hm_mouse, 0); + if hook_mouse.is_null() { + if FALSE == UnhookWindowsHookEx(hook_keyboard) { + // Fatal error + log::error!(" UnhookWindowsHookEx keyboard {}", GetLastError()); + } + tx.send(format!(" SetWindowsHookExA mouse {}", GetLastError()))?; + return Ok(invalid_ret); + } + + *cur_hook_thread_id = GetCurrentThreadId(); + tx.send("".to_owned())?; + return Ok((hook_keyboard, hook_mouse)); + } +} + +pub fn hook() -> ResultType<()> { + let (tx, rx) = channel(); + std::thread::spawn(move || { + let hook_keyboard; + let hook_mouse; + unsafe { + match do_hook(tx.clone()) { + Ok(hooks) => { + hook_keyboard = hooks.0; + hook_mouse = hooks.1; + } + Err(e) => { + // Fatal error + allow_err!(tx.send(format!("Unexpected err when hook {}", e))); + return; + } + } + if hook_keyboard.is_null() { + return; + } + + let mut msg = MSG { + hwnd: NULL as _, + message: 0 as _, + wParam: 0 as _, + lParam: 0 as _, + time: 0 as _, + pt: POINT { + x: 0 as _, + y: 0 as _, + }, + }; + while FALSE != GetMessageA(&mut msg, NULL as _, 0, 0) { + if msg.message == WM_USER_EXIT_HOOK { + break; + } + + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + if FALSE == UnhookWindowsHookEx(hook_keyboard as _) { + // Fatal error + log::error!("Failed UnhookWindowsHookEx keyboard {}", GetLastError()); + } + + if FALSE == UnhookWindowsHookEx(hook_mouse as _) { + // Fatal error + log::error!("Failed UnhookWindowsHookEx mouse {}", GetLastError()); + } + + *CUR_HOOK_THREAD_ID.lock().unwrap() = 0; + } + }); + + match rx.recv() { + Ok(msg) => { + if msg == "" { + Ok(()) + } else { + bail!(msg) + } + } + Err(e) => { + bail!("Failed to wait hook result {}", e) + } + } +} + +pub fn unhook() -> ResultType<()> { + unsafe { + let cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap(); + if *cur_hook_thread_id != 0 { + if FALSE == PostThreadMessageA(*cur_hook_thread_id, WM_USER_EXIT_HOOK, 0, 0) { + bail!("Failed to post message to exit hook, {}", GetLastError()); + } + } + } + Ok(()) +} + +#[no_mangle] +pub extern "system" fn privacy_mode_hook_keyboard( + code: c_int, + w_param: WPARAM, + l_param: LPARAM, +) -> LRESULT { + if code < 0 { + unsafe { + return CallNextHookEx(NULL as _, code, w_param, l_param); + } + } + + let ks = l_param as PKBDLLHOOKSTRUCT; + let w_param2 = w_param as UINT; + + unsafe { + if (*ks).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE { + // Disable alt key. Alt + Tab will switch windows. + if (*ks).flags & LLKHF_ALTDOWN == LLKHF_ALTDOWN { + return 1; + } + + match w_param2 { + WM_KEYDOWN => { + // Disable all keys other than P and Ctrl. + if ![80, 162, 163].contains(&(*ks).vkCode) { + return 1; + } + + // NOTE: GetKeyboardState may not work well... + + // Check if Ctrl + P is pressed + let cltr_down = (GetKeyState(VK_CONTROL) as u16) & (0x8000 as u16) > 0; + let key = LOBYTE((*ks).vkCode as _); + if cltr_down && (key == 'p' as u8 || key == 'P' as u8) { + // Ctrl + P is pressed, turn off privacy mode + if let Some(Err(e)) = super::turn_off_privacy( + super::INVALID_PRIVACY_MODE_CONN_ID, + Some(crate::ipc::PrivacyModeState::OffByPeer), + ) { + log::error!("Failed to off_privacy {}", e); + } + } + } + WM_KEYUP => { + log::trace!("WM_KEYUP {}", (*ks).vkCode); + } + _ => { + log::trace!("KEYBOARD OTHER {} {}", w_param2, (*ks).vkCode); + } + } + } + } + unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) } +} + +#[no_mangle] +pub extern "system" fn privacy_mode_hook_mouse( + code: c_int, + w_param: WPARAM, + l_param: LPARAM, +) -> LRESULT { + if code < 0 { + unsafe { + return CallNextHookEx(NULL as _, code, w_param, l_param); + } + } + + let ms = l_param as PMOUSEHOOKSTRUCT; + unsafe { + if (*ms).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE { + return 1; + } + } + unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) } +} + +mod test { + #[test] + fn privacy_hook() { + //use super::*; + + // privacy_hook::hook().unwrap(); + // std::thread::sleep(std::time::Duration::from_millis(50)); + // privacy_hook::unhook().unwrap(); + } +} diff --git a/src/privacy_mode/win_mag.rs b/src/privacy_mode/win_mag.rs new file mode 100644 index 000000000..321894462 --- /dev/null +++ b/src/privacy_mode/win_mag.rs @@ -0,0 +1,416 @@ +use super::{PrivacyMode, INVALID_PRIVACY_MODE_CONN_ID}; +use crate::{ipc::PrivacyModeState, platform::windows::get_user_token}; +use hbb_common::{allow_err, bail, log, ResultType}; +use std::{ + ffi::CString, + time::{Duration, Instant}, +}; +use winapi::{ + shared::{ + minwindef::FALSE, + ntdef::{HANDLE, NULL}, + windef::HWND, + }, + um::{ + errhandlingapi::GetLastError, + handleapi::CloseHandle, + libloaderapi::{GetModuleHandleA, GetProcAddress}, + memoryapi::{VirtualAllocEx, WriteProcessMemory}, + processthreadsapi::{ + CreateProcessAsUserW, QueueUserAPC, ResumeThread, TerminateProcess, + PROCESS_INFORMATION, STARTUPINFOW, + }, + winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS}, + winnt::{MEM_COMMIT, PAGE_READWRITE}, + winuser::*, + }, +}; + +pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_mag"; + +pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe"; +pub const WIN_MAG_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe"; +pub const INJECTED_PROCESS_EXE: &'static str = WIN_MAG_INJECTED_PROCESS_EXE; +const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow"; + +struct WindowHandlers { + hthread: u64, + hprocess: u64, +} + +impl Drop for WindowHandlers { + fn drop(&mut self) { + self.reset(); + } +} + +impl WindowHandlers { + fn reset(&mut self) { + unsafe { + if self.hprocess != 0 { + let _res = TerminateProcess(self.hprocess as _, 0); + CloseHandle(self.hprocess as _); + } + self.hprocess = 0; + if self.hthread != 0 { + CloseHandle(self.hthread as _); + } + self.hthread = 0; + } + } + + fn is_default(&self) -> bool { + self.hthread == 0 && self.hprocess == 0 + } +} + +pub struct PrivacyModeImpl { + conn_id: i32, + handlers: WindowHandlers, +} + +impl Default for PrivacyModeImpl { + fn default() -> Self { + Self { + conn_id: INVALID_PRIVACY_MODE_CONN_ID, + handlers: WindowHandlers { + hthread: 0, + hprocess: 0, + }, + } + } +} + +impl PrivacyMode for PrivacyModeImpl { + fn init(&self) -> ResultType<()> { + Ok(()) + } + + fn clear(&mut self) { + allow_err!(self.turn_off_privacy(self.conn_id, None)); + } + + fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType { + if self.check_on_conn_id(conn_id)? { + log::debug!("Privacy mode of conn {} is already on", conn_id); + return Ok(true); + } + + let exe_file = std::env::current_exe()?; + if let Some(cur_dir) = exe_file.parent() { + if !cur_dir.join("WindowInjection.dll").exists() { + return Ok(false); + } + } else { + bail!( + "Invalid exe parent for {}", + exe_file.to_string_lossy().as_ref() + ); + } + + if self.handlers.is_default() { + log::info!("turn_on_privacy, dll not found when started, try start"); + self.start()?; + std::thread::sleep(std::time::Duration::from_millis(1_000)); + } + + let hwnd = wait_find_privacy_hwnd(0)?; + if hwnd.is_null() { + bail!("No privacy window created"); + } + super::win_input::hook()?; + unsafe { + ShowWindow(hwnd as _, SW_SHOW); + } + self.conn_id = conn_id; + Ok(true) + } + + fn turn_off_privacy( + &mut self, + conn_id: i32, + state: Option, + ) -> ResultType<()> { + self.check_off_conn_id(conn_id)?; + super::win_input::unhook()?; + + unsafe { + let hwnd = wait_find_privacy_hwnd(0)?; + if !hwnd.is_null() { + ShowWindow(hwnd, SW_HIDE); + } + } + + if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { + if let Some(state) = state { + allow_err!(super::set_privacy_mode_state( + conn_id, + state, + PRIVACY_MODE_IMPL.to_string(), + 1_000 + )); + } + self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned(); + } + + Ok(()) + } + + #[inline] + fn pre_conn_id(&self) -> i32 { + self.conn_id + } +} + +impl PrivacyModeImpl { + pub fn start(&mut self) -> ResultType<()> { + if self.handlers.hprocess != 0 { + return Ok(()); + } + + log::info!("Start privacy mode window broker, check_update_broker_process"); + if let Err(e) = crate::platform::windows::check_update_broker_process() { + log::warn!( + "Failed to check update broker process. Privacy mode may not work properly. {}", + e + ); + } + + let exe_file = std::env::current_exe()?; + let Some(cur_dir) = exe_file.parent() else { + bail!("Cannot get parent of current exe file"); + }; + + let dll_file = cur_dir.join("WindowInjection.dll"); + if !dll_file.exists() { + bail!( + "Failed to find required file {}", + dll_file.to_string_lossy().as_ref() + ); + } + + let hwnd = wait_find_privacy_hwnd(1_000)?; + if !hwnd.is_null() { + log::info!("Privacy window is ready"); + return Ok(()); + } + + // let cmdline = cur_dir.join("MiniBroker.exe").to_string_lossy().to_string(); + let cmdline = cur_dir + .join(INJECTED_PROCESS_EXE) + .to_string_lossy() + .to_string(); + + unsafe { + let cmd_utf16: Vec = cmdline.encode_utf16().chain(Some(0).into_iter()).collect(); + + let mut start_info = STARTUPINFOW { + cb: 0, + lpReserved: NULL as _, + lpDesktop: NULL as _, + lpTitle: NULL as _, + dwX: 0, + dwY: 0, + dwXSize: 0, + dwYSize: 0, + dwXCountChars: 0, + dwYCountChars: 0, + dwFillAttribute: 0, + dwFlags: 0, + wShowWindow: 0, + cbReserved2: 0, + lpReserved2: NULL as _, + hStdInput: NULL as _, + hStdOutput: NULL as _, + hStdError: NULL as _, + }; + let mut proc_info = PROCESS_INFORMATION { + hProcess: NULL as _, + hThread: NULL as _, + dwProcessId: 0, + dwThreadId: 0, + }; + + let session_id = WTSGetActiveConsoleSessionId(); + let token = get_user_token(session_id, true); + if token.is_null() { + bail!("Failed to get token of current user"); + } + + let create_res = CreateProcessAsUserW( + token, + NULL as _, + cmd_utf16.as_ptr() as _, + NULL as _, + NULL as _, + FALSE, + CREATE_SUSPENDED | DETACHED_PROCESS, + NULL, + NULL as _, + &mut start_info, + &mut proc_info, + ); + CloseHandle(token); + if 0 == create_res { + bail!( + "Failed to create privacy window process {}, code {}", + cmdline, + GetLastError() + ); + }; + + inject_dll( + proc_info.hProcess, + proc_info.hThread, + dll_file.to_string_lossy().as_ref(), + )?; + + if 0xffffffff == ResumeThread(proc_info.hThread) { + // CloseHandle + CloseHandle(proc_info.hThread); + CloseHandle(proc_info.hProcess); + + bail!( + "Failed to create privacy window process, {}", + GetLastError() + ); + } + + self.handlers.hthread = proc_info.hThread as _; + self.handlers.hprocess = proc_info.hProcess as _; + + let hwnd = wait_find_privacy_hwnd(1_000)?; + if hwnd.is_null() { + bail!("Failed to get hwnd after started"); + } + } + + Ok(()) + } + + #[inline] + pub fn stop(&mut self) { + self.handlers.reset(); + } +} + +impl Drop for PrivacyModeImpl { + fn drop(&mut self) { + if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { + allow_err!(self.turn_off_privacy(self.conn_id, None)); + } + } +} + +unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> { + let dll_file_utf16: Vec = dll_file.encode_utf16().chain(Some(0).into_iter()).collect(); + + let buf = VirtualAllocEx( + hproc, + NULL as _, + dll_file_utf16.len() * 2, + MEM_COMMIT, + PAGE_READWRITE, + ); + if buf.is_null() { + bail!("Failed VirtualAllocEx"); + } + + let mut written: usize = 0; + if 0 == WriteProcessMemory( + hproc, + buf, + dll_file_utf16.as_ptr() as _, + dll_file_utf16.len() * 2, + &mut written, + ) { + bail!("Failed WriteProcessMemory"); + } + + let kernel32_modulename = CString::new("kernel32")?; + let hmodule = GetModuleHandleA(kernel32_modulename.as_ptr() as _); + if hmodule.is_null() { + bail!("Failed GetModuleHandleA"); + } + + let load_librarya_name = CString::new("LoadLibraryW")?; + let load_librarya = GetProcAddress(hmodule, load_librarya_name.as_ptr() as _); + if load_librarya.is_null() { + bail!("Failed GetProcAddress of LoadLibraryW"); + } + + if 0 == QueueUserAPC(Some(std::mem::transmute(load_librarya)), hthread, buf as _) { + bail!("Failed QueueUserAPC"); + } + + Ok(()) +} + +fn wait_find_privacy_hwnd(msecs: u128) -> ResultType { + let tm_begin = Instant::now(); + let wndname = CString::new(PRIVACY_WINDOW_NAME)?; + loop { + unsafe { + let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _); + if !hwnd.is_null() { + return Ok(hwnd); + } + } + + if msecs == 0 || tm_begin.elapsed().as_millis() > msecs { + return Ok(NULL as _); + } + + std::thread::sleep(Duration::from_millis(100)); + } +} + +pub fn create_capturer( + privacy_mode_id: i32, + origin: (i32, i32), + width: usize, + height: usize, +) -> ResultType> { + if !super::is_current_privacy_mode_impl(PRIVACY_MODE_IMPL) { + return Ok(None); + } + + match scrap::CapturerMag::new(origin, width, height) { + Ok(mut c1) => { + let mut ok = false; + let check_begin = Instant::now(); + while check_begin.elapsed().as_secs() < 5 { + match c1.exclude("", PRIVACY_WINDOW_NAME) { + Ok(false) => { + ok = false; + std::thread::sleep(std::time::Duration::from_millis(500)); + } + Err(e) => { + bail!( + "Failed to exclude privacy window {} - {}, err: {}", + "", + PRIVACY_WINDOW_NAME, + e + ); + } + _ => { + ok = true; + break; + } + } + } + if !ok { + bail!( + "Failed to exclude privacy window {} - {} ", + "", + PRIVACY_WINDOW_NAME + ); + } + log::debug!("Create magnifier capture for {}", privacy_mode_id); + Ok(Some(c1)) + } + Err(e) => { + bail!(format!("Failed to create magnifier capture {}", e)); + } + } +} diff --git a/src/privacy_mode/win_virtual_display.rs b/src/privacy_mode/win_virtual_display.rs new file mode 100644 index 000000000..1d3ffa30e --- /dev/null +++ b/src/privacy_mode/win_virtual_display.rs @@ -0,0 +1,566 @@ +use super::{PrivacyMode, PrivacyModeState, INVALID_PRIVACY_MODE_CONN_ID, NO_DISPLAYS}; +use crate::virtual_display_manager; +use hbb_common::{allow_err, bail, config::Config, log, ResultType}; +use std::ops::{Deref, DerefMut}; +use virtual_display::MonitorMode; +use winapi::{ + shared::{ + minwindef::{DWORD, FALSE}, + ntdef::{NULL, WCHAR}, + }, + um::{ + errhandlingapi::GetLastError, + wingdi::{ + DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, + DISPLAY_DEVICE_MIRRORING_DRIVER, DISPLAY_DEVICE_PRIMARY_DEVICE, DM_POSITION, + }, + winuser::{ + ChangeDisplaySettingsExW, EnumDisplayDevicesW, EnumDisplaySettingsExW, + EnumDisplaySettingsW, CDS_NORESET, CDS_RESET, CDS_SET_PRIMARY, CDS_UPDATEREGISTRY, + DISP_CHANGE_SUCCESSFUL, EDD_GET_DEVICE_INTERFACE_NAME, ENUM_CURRENT_SETTINGS, + ENUM_REGISTRY_SETTINGS, + }, + }, +}; + +pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_virtual_display"; + +const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; +const CONFIG_KEY_REG_RECOVERY: &str = "reg_recovery"; + +struct Display { + dm: DEVMODEW, + name: [WCHAR; 32], + primary: bool, +} + +pub struct PrivacyModeImpl { + conn_id: i32, + displays: Vec, + virtual_displays: Vec, + virtual_displays_added: Vec, +} + +impl Default for PrivacyModeImpl { + fn default() -> Self { + Self { + conn_id: INVALID_PRIVACY_MODE_CONN_ID, + displays: Vec::new(), + virtual_displays: Vec::new(), + virtual_displays_added: Vec::new(), + } + } +} + +struct TurnOnGuard<'a> { + privacy_mode: &'a mut PrivacyModeImpl, + succeeded: bool, +} + +impl<'a> Deref for TurnOnGuard<'a> { + type Target = PrivacyModeImpl; + + fn deref(&self) -> &Self::Target { + self.privacy_mode + } +} + +impl<'a> DerefMut for TurnOnGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.privacy_mode + } +} + +impl<'a> Drop for TurnOnGuard<'a> { + fn drop(&mut self) { + if !self.succeeded { + self.privacy_mode + .turn_off_privacy(INVALID_PRIVACY_MODE_CONN_ID, None) + .ok(); + } + } +} + +impl PrivacyModeImpl { + // mainly from https://github.com/fufesou/rustdesk/blob/44c3a52ca8502cf53b58b59db130611778d34dbe/libs/scrap/src/dxgi/mod.rs#L365 + fn set_displays(&mut self) { + self.displays.clear(); + self.virtual_displays.clear(); + + let mut i: DWORD = 0; + loop { + #[allow(invalid_value)] + let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; + dd.cb = std::mem::size_of::() as _; + let ok = unsafe { EnumDisplayDevicesW(std::ptr::null(), i, &mut dd as _, 0) }; + if ok == FALSE { + break; + } + i += 1; + if 0 == (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) + || (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0 + { + continue; + } + #[allow(invalid_value)] + let mut dm: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; + dm.dmSize = std::mem::size_of::() as _; + dm.dmDriverExtra = 0; + unsafe { + if FALSE + == EnumDisplaySettingsExW( + dd.DeviceName.as_ptr(), + ENUM_CURRENT_SETTINGS, + &mut dm as _, + 0, + ) + { + if FALSE + == EnumDisplaySettingsExW( + dd.DeviceName.as_ptr(), + ENUM_REGISTRY_SETTINGS, + &mut dm as _, + 0, + ) + { + continue; + } + } + } + + let primary = (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) > 0; + let display = Display { + dm, + name: dd.DeviceName, + primary, + }; + + if let Ok(s) = std::string::String::from_utf16(&dd.DeviceString) { + if &s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING { + self.virtual_displays.push(display); + continue; + } + } + self.displays.push(display); + } + } + + fn restore(&mut self) { + Self::restore_displays(&self.displays); + Self::restore_displays(&self.virtual_displays); + allow_err!(Self::commit_change_display(0)); + self.restore_plug_out_monitor(); + } + + fn restore_plug_out_monitor(&mut self) { + let _ = virtual_display_manager::plug_out_peer_request(&self.virtual_displays_added); + self.virtual_displays_added.clear(); + } + + fn restore_displays(displays: &[Display]) { + for display in displays { + unsafe { + let mut dm = display.dm.clone(); + let flags = if display.primary { + CDS_NORESET | CDS_UPDATEREGISTRY | CDS_SET_PRIMARY + } else { + CDS_NORESET | CDS_UPDATEREGISTRY + }; + ChangeDisplaySettingsExW( + display.name.as_ptr(), + &mut dm, + std::ptr::null_mut(), + flags, + std::ptr::null_mut(), + ); + } + } + } + + fn set_primary_display(&mut self) -> ResultType<()> { + let display = &self.virtual_displays[0]; + + #[allow(invalid_value)] + let mut new_primary_dm: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; + new_primary_dm.dmSize = std::mem::size_of::() as _; + new_primary_dm.dmDriverExtra = 0; + unsafe { + if FALSE + == EnumDisplaySettingsW( + display.name.as_ptr(), + ENUM_CURRENT_SETTINGS, + &mut new_primary_dm, + ) + { + bail!( + "Failed EnumDisplaySettingsW, device name: {:?}, error code: {}", + std::string::String::from_utf16(&display.name), + GetLastError() + ); + } + + let mut i: DWORD = 0; + loop { + let mut flags = CDS_UPDATEREGISTRY | CDS_NORESET; + #[allow(invalid_value)] + let mut dd: DISPLAY_DEVICEW = std::mem::MaybeUninit::uninit().assume_init(); + dd.cb = std::mem::size_of::() as _; + if FALSE + == EnumDisplayDevicesW(NULL as _, i, &mut dd, EDD_GET_DEVICE_INTERFACE_NAME) + { + break; + } + i += 1; + if (dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0 { + continue; + } + + if dd.DeviceName == display.name { + flags |= CDS_SET_PRIMARY; + } + + #[allow(invalid_value)] + let mut dm: DEVMODEW = std::mem::MaybeUninit::uninit().assume_init(); + dm.dmSize = std::mem::size_of::() as _; + dm.dmDriverExtra = 0; + if FALSE + == EnumDisplaySettingsW(dd.DeviceName.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) + { + bail!( + "Failed EnumDisplaySettingsW, device name: {:?}, error code: {}", + std::string::String::from_utf16(&dd.DeviceName), + GetLastError() + ); + } + + dm.u1.s2_mut().dmPosition.x -= new_primary_dm.u1.s2().dmPosition.x; + dm.u1.s2_mut().dmPosition.y -= new_primary_dm.u1.s2().dmPosition.y; + dm.dmFields |= DM_POSITION; + let rc = ChangeDisplaySettingsExW( + dd.DeviceName.as_ptr(), + &mut dm, + NULL as _, + flags, + NULL, + ); + + if rc != DISP_CHANGE_SUCCESSFUL { + log::error!( + "Failed ChangeDisplaySettingsEx, device name: {:?}, flags: {}, ret: {}", + std::string::String::from_utf16(&dd.DeviceName), + flags, + rc + ); + bail!("Failed ChangeDisplaySettingsEx, ret: {}", rc); + } + } + } + + Ok(()) + } + + fn disable_physical_displays(&self) -> ResultType<()> { + for display in &self.displays { + let mut dm = display.dm.clone(); + unsafe { + dm.u1.s2_mut().dmPosition.x = 10000; + dm.u1.s2_mut().dmPosition.y = 10000; + dm.dmPelsHeight = 0; + dm.dmPelsWidth = 0; + let flags = CDS_UPDATEREGISTRY | CDS_NORESET; + let rc = ChangeDisplaySettingsExW( + display.name.as_ptr(), + &mut dm, + NULL as _, + flags, + NULL as _, + ); + if rc != DISP_CHANGE_SUCCESSFUL { + log::error!( + "Failed ChangeDisplaySettingsEx, device name: {:?}, flags: {}, ret: {}", + std::string::String::from_utf16(&display.name), + flags, + rc + ); + bail!("Failed ChangeDisplaySettingsEx, ret: {}", rc); + } + } + } + Ok(()) + } + + #[inline] + fn default_display_modes() -> Vec { + vec![MonitorMode { + width: 1920, + height: 1080, + sync: 60, + }] + } + + pub fn ensure_virtual_display(&mut self) -> ResultType<()> { + if self.virtual_displays.is_empty() { + let displays = + virtual_display_manager::plug_in_peer_request(vec![Self::default_display_modes()])?; + self.virtual_displays_added.extend(displays); + self.set_displays(); + } + + Ok(()) + } + + #[inline] + fn commit_change_display(flags: DWORD) -> ResultType<()> { + unsafe { + // use winapi::{ + // shared::windef::HDESK, + // um::{ + // processthreadsapi::GetCurrentThreadId, + // winnt::MAXIMUM_ALLOWED, + // winuser::{CloseDesktop, GetThreadDesktop, OpenInputDesktop, SetThreadDesktop}, + // }, + // }; + // let mut desk_input: HDESK = NULL as _; + // let desk_current: HDESK = GetThreadDesktop(GetCurrentThreadId()); + // if !desk_current.is_null() { + // desk_input = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED); + // if desk_input.is_null() { + // SetThreadDesktop(desk_input); + // } + // } + + let ret = ChangeDisplaySettingsExW(NULL as _, NULL as _, NULL as _, flags, NULL as _); + if ret != DISP_CHANGE_SUCCESSFUL { + bail!("Failed ChangeDisplaySettingsEx, ret: {}", ret); + } + + // if !desk_current.is_null() { + // SetThreadDesktop(desk_current); + // } + // if !desk_input.is_null() { + // CloseDesktop(desk_input); + // } + } + Ok(()) + } +} + +impl PrivacyMode for PrivacyModeImpl { + fn init(&self) -> ResultType<()> { + Ok(()) + } + + fn clear(&mut self) { + allow_err!(self.turn_off_privacy(self.conn_id, None)); + } + + fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType { + if self.check_on_conn_id(conn_id)? { + log::debug!("Privacy mode of conn {} is already on", conn_id); + return Ok(true); + } + self.set_displays(); + if self.displays.is_empty() { + log::debug!("No displays"); + bail!(NO_DISPLAYS); + } + + let mut guard = TurnOnGuard { + privacy_mode: self, + succeeded: false, + }; + + guard.ensure_virtual_display()?; + if guard.virtual_displays.is_empty() { + log::debug!("No virtual displays"); + bail!("No virtual displays"); + } + + let reg_connectivity_1 = reg_display_settings::read_reg_connectivity()?; + guard.set_primary_display()?; + guard.disable_physical_displays()?; + Self::commit_change_display(CDS_RESET)?; + let reg_connectivity_2 = reg_display_settings::read_reg_connectivity()?; + + if let Some(reg_recovery) = + reg_display_settings::diff_recent_connectivity(reg_connectivity_1, reg_connectivity_2) + { + Config::set_option( + CONFIG_KEY_REG_RECOVERY.to_owned(), + serde_json::to_string(®_recovery)?, + ); + } else { + reset_config_reg_connectivity(); + }; + + // OpenInputDesktop and block the others' input ? + guard.conn_id = conn_id; + guard.succeeded = true; + + allow_err!(super::win_input::hook()); + + Ok(true) + } + + fn turn_off_privacy( + &mut self, + conn_id: i32, + state: Option, + ) -> ResultType<()> { + self.check_off_conn_id(conn_id)?; + super::win_input::unhook()?; + self.restore(); + restore_reg_connectivity(); + + if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { + if let Some(state) = state { + allow_err!(super::set_privacy_mode_state( + conn_id, + state, + PRIVACY_MODE_IMPL.to_string(), + 1_000 + )); + } + self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned(); + } + + Ok(()) + } + + #[inline] + fn pre_conn_id(&self) -> i32 { + self.conn_id + } +} + +impl Drop for PrivacyModeImpl { + fn drop(&mut self) { + if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { + allow_err!(self.turn_off_privacy(self.conn_id, None)); + } + } +} + +#[inline] +fn reset_config_reg_connectivity() { + Config::set_option(CONFIG_KEY_REG_RECOVERY.to_owned(), "".to_owned()); +} + +pub fn restore_reg_connectivity() { + let config_recovery_value = Config::get_option(CONFIG_KEY_REG_RECOVERY); + if config_recovery_value.is_empty() { + return; + } + if let Ok(reg_recovery) = + serde_json::from_str::(&config_recovery_value) + { + if let Err(e) = reg_display_settings::restore_reg_connectivity(reg_recovery) { + log::error!("Failed restore_reg_connectivity, error: {}", e); + } + } + reset_config_reg_connectivity(); +} + +mod reg_display_settings { + use hbb_common::ResultType; + use serde_derive::{Deserialize, Serialize}; + use std::collections::HashMap; + use winreg::{enums::*, RegValue}; + const REG_GRAPHICS_DRIVERS_PATH: &str = "SYSTEM\\CurrentControlSet\\Control\\GraphicsDrivers"; + const REG_CONNECTIVITY_PATH: &str = "Connectivity"; + + #[derive(Serialize, Deserialize, Debug)] + pub(super) struct RegRecovery { + path: String, + key: String, + old: (Vec, isize), + new: (Vec, isize), + } + + pub(super) fn read_reg_connectivity() -> ResultType>> + { + let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); + let reg_connectivity = hklm.open_subkey_with_flags( + format!("{}\\{}", REG_GRAPHICS_DRIVERS_PATH, REG_CONNECTIVITY_PATH), + KEY_READ, + )?; + + let mut map_connectivity = HashMap::new(); + for key in reg_connectivity.enum_keys() { + let key = key?; + let mut map_item = HashMap::new(); + let reg_item = reg_connectivity.open_subkey_with_flags(&key, KEY_READ)?; + for value in reg_item.enum_values() { + let (name, value) = value?; + map_item.insert(name, value); + } + map_connectivity.insert(key, map_item); + } + Ok(map_connectivity) + } + + pub(super) fn diff_recent_connectivity( + map1: HashMap>, + map2: HashMap>, + ) -> Option { + for (subkey, map_item2) in map2 { + if let Some(map_item1) = map1.get(&subkey) { + let key = "Recent"; + if let Some(value1) = map_item1.get(key) { + if let Some(value2) = map_item2.get(key) { + if value1 != value2 { + return Some(RegRecovery { + path: format!( + "{}\\{}\\{}", + REG_GRAPHICS_DRIVERS_PATH, REG_CONNECTIVITY_PATH, subkey + ), + key: key.to_owned(), + old: (value1.bytes.clone(), value1.vtype.clone() as isize), + new: (value2.bytes.clone(), value2.vtype.clone() as isize), + }); + } + } + } + } + } + None + } + + pub(super) fn restore_reg_connectivity(reg_recovery: RegRecovery) -> ResultType<()> { + let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); + let reg_item = hklm.open_subkey_with_flags(®_recovery.path, KEY_READ | KEY_WRITE)?; + let cur_reg_value = reg_item.get_raw_value(®_recovery.key)?; + let new_reg_value = RegValue { + bytes: reg_recovery.new.0, + vtype: isize_to_reg_type(reg_recovery.new.1), + }; + if cur_reg_value != new_reg_value { + return Ok(()); + } + let reg_value = RegValue { + bytes: reg_recovery.old.0, + vtype: isize_to_reg_type(reg_recovery.old.1), + }; + reg_item.set_raw_value(®_recovery.key, ®_value)?; + Ok(()) + } + + #[inline] + fn isize_to_reg_type(i: isize) -> RegType { + match i { + 0 => RegType::REG_NONE, + 1 => RegType::REG_SZ, + 2 => RegType::REG_EXPAND_SZ, + 3 => RegType::REG_BINARY, + 4 => RegType::REG_DWORD, + 5 => RegType::REG_DWORD_BIG_ENDIAN, + 6 => RegType::REG_LINK, + 7 => RegType::REG_MULTI_SZ, + 8 => RegType::REG_RESOURCE_LIST, + 9 => RegType::REG_FULL_RESOURCE_DESCRIPTOR, + 10 => RegType::REG_RESOURCE_REQUIREMENTS_LIST, + 11 => RegType::REG_QWORD, + _ => RegType::REG_NONE, + } + } +} diff --git a/src/privacy_win_mag.rs b/src/privacy_win_mag.rs deleted file mode 100644 index 16c6a01ab..000000000 --- a/src/privacy_win_mag.rs +++ /dev/null @@ -1,592 +0,0 @@ -use crate::{ - ipc::{connect, Data, PrivacyModeState}, - platform::windows::get_user_token, -}; -use hbb_common::{allow_err, bail, lazy_static, log, tokio, ResultType}; -use std::{ - ffi::CString, - sync::Mutex, - time::{Duration, Instant}, -}; -use winapi::{ - ctypes::c_int, - shared::{ - minwindef::{DWORD, FALSE, HMODULE, LOBYTE, LPARAM, LRESULT, UINT, WPARAM}, - ntdef::{HANDLE, NULL}, - windef::{HHOOK, HWND, POINT}, - }, - um::{ - errhandlingapi::GetLastError, - handleapi::CloseHandle, - libloaderapi::{GetModuleHandleA, GetModuleHandleExA, GetProcAddress}, - memoryapi::{VirtualAllocEx, WriteProcessMemory}, - processthreadsapi::{ - CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread, TerminateProcess, - PROCESS_INFORMATION, STARTUPINFOW, - }, - winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS}, - winnt::{MEM_COMMIT, PAGE_READWRITE}, - winuser::*, - }, -}; - -pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe"; -pub const WIN_MAG_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe"; -pub const INJECTED_PROCESS_EXE: &'static str = WIN_MAG_INJECTED_PROCESS_EXE; -pub const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow"; - -pub const OCCUPIED: &'static str = "Privacy occupied by another one"; -pub const TURN_OFF_OTHER_ID: &'static str = - "Failed to turn off privacy mode that belongs to someone else"; -pub const NO_DISPLAYS: &'static str = "No displays"; - -pub const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2; -pub const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4; - -const WM_USER_EXIT_HOOK: u32 = WM_USER + 1; - -lazy_static::lazy_static! { - static ref CONN_ID: Mutex = Mutex::new(0); - static ref CUR_HOOK_THREAD_ID: Mutex = Mutex::new(0); - static ref WND_HANDLERS: Mutex = Mutex::new(WindowHandlers{hthread: 0, hprocess: 0}); -} - -struct WindowHandlers { - hthread: u64, - hprocess: u64, -} - -impl Drop for WindowHandlers { - fn drop(&mut self) { - self.reset(); - } -} - -impl WindowHandlers { - fn reset(&mut self) { - unsafe { - if self.hprocess != 0 { - let _res = TerminateProcess(self.hprocess as _, 0); - CloseHandle(self.hprocess as _); - } - self.hprocess = 0; - if self.hthread != 0 { - CloseHandle(self.hthread as _); - } - self.hthread = 0; - } - } - - fn is_default(&self) -> bool { - self.hthread == 0 && self.hprocess == 0 - } -} - -pub fn turn_on_privacy(conn_id: i32) -> ResultType { - let pre_conn_id = *CONN_ID.lock().unwrap(); - if pre_conn_id == conn_id { - return Ok(true); - } - if pre_conn_id != 0 { - bail!("Privacy occupied by another one"); - } - - let exe_file = std::env::current_exe()?; - if let Some(cur_dir) = exe_file.parent() { - if !cur_dir.join("WindowInjection.dll").exists() { - return Ok(false); - } - } else { - bail!( - "Invalid exe parent for {}", - exe_file.to_string_lossy().as_ref() - ); - } - - if WND_HANDLERS.lock().unwrap().is_default() { - log::info!("turn_on_privacy, dll not found when started, try start"); - start()?; - std::thread::sleep(std::time::Duration::from_millis(1_000)); - } - - let hwnd = wait_find_privacy_hwnd(0)?; - if hwnd.is_null() { - bail!("No privacy window created"); - } - privacy_hook::hook()?; - unsafe { - ShowWindow(hwnd as _, SW_SHOW); - } - *CONN_ID.lock().unwrap() = conn_id; - Ok(true) -} - -pub fn turn_off_privacy(conn_id: i32, state: Option) -> ResultType<()> { - let pre_conn_id = *CONN_ID.lock().unwrap(); - if pre_conn_id != 0 && conn_id != 0 && pre_conn_id != conn_id { - bail!("Failed to turn off privacy mode that belongs to someone else") - } - - privacy_hook::unhook()?; - - unsafe { - let hwnd = wait_find_privacy_hwnd(0)?; - if !hwnd.is_null() { - ShowWindow(hwnd, SW_HIDE); - } - } - - if pre_conn_id != 0 { - if let Some(state) = state { - allow_err!(set_privacy_mode_state(pre_conn_id, state, 1_000)); - } - *CONN_ID.lock().unwrap() = 0; - } - - Ok(()) -} - -pub fn start() -> ResultType<()> { - let mut wnd_handlers = WND_HANDLERS.lock().unwrap(); - if wnd_handlers.hprocess != 0 { - return Ok(()); - } - - log::info!("Start privacy mode window broker, check_update_broker_process"); - if let Err(e) = crate::platform::windows::check_update_broker_process() { - log::warn!( - "Failed to check update broker process. Privacy mode may not work properly. {}", - e - ); - } - - let exe_file = std::env::current_exe()?; - let Some(cur_dir) = exe_file - .parent() else { - bail!("Cannot get parent of current exe file"); - }; - - let dll_file = cur_dir.join("WindowInjection.dll"); - if !dll_file.exists() { - bail!( - "Failed to find required file {}", - dll_file.to_string_lossy().as_ref() - ); - } - - let hwnd = wait_find_privacy_hwnd(1_000)?; - if !hwnd.is_null() { - log::info!("Privacy window is ready"); - return Ok(()); - } - - // let cmdline = cur_dir.join("MiniBroker.exe").to_string_lossy().to_string(); - let cmdline = cur_dir - .join(INJECTED_PROCESS_EXE) - .to_string_lossy() - .to_string(); - - unsafe { - let cmd_utf16: Vec = cmdline.encode_utf16().chain(Some(0).into_iter()).collect(); - - let mut start_info = STARTUPINFOW { - cb: 0, - lpReserved: NULL as _, - lpDesktop: NULL as _, - lpTitle: NULL as _, - dwX: 0, - dwY: 0, - dwXSize: 0, - dwYSize: 0, - dwXCountChars: 0, - dwYCountChars: 0, - dwFillAttribute: 0, - dwFlags: 0, - wShowWindow: 0, - cbReserved2: 0, - lpReserved2: NULL as _, - hStdInput: NULL as _, - hStdOutput: NULL as _, - hStdError: NULL as _, - }; - let mut proc_info = PROCESS_INFORMATION { - hProcess: NULL as _, - hThread: NULL as _, - dwProcessId: 0, - dwThreadId: 0, - }; - - let session_id = WTSGetActiveConsoleSessionId(); - let token = get_user_token(session_id, true); - if token.is_null() { - bail!("Failed to get token of current user"); - } - - let create_res = CreateProcessAsUserW( - token, - NULL as _, - cmd_utf16.as_ptr() as _, - NULL as _, - NULL as _, - FALSE, - CREATE_SUSPENDED | DETACHED_PROCESS, - NULL, - NULL as _, - &mut start_info, - &mut proc_info, - ); - CloseHandle(token); - if 0 == create_res { - bail!( - "Failed to create privacy window process {}, code {}", - cmdline, - GetLastError() - ); - }; - - inject_dll( - proc_info.hProcess, - proc_info.hThread, - dll_file.to_string_lossy().as_ref(), - )?; - - if 0xffffffff == ResumeThread(proc_info.hThread) { - // CloseHandle - CloseHandle(proc_info.hThread); - CloseHandle(proc_info.hProcess); - - bail!( - "Failed to create privacy window process, {}", - GetLastError() - ); - } - - wnd_handlers.hthread = proc_info.hThread as _; - wnd_handlers.hprocess = proc_info.hProcess as _; - - let hwnd = wait_find_privacy_hwnd(1_000)?; - if hwnd.is_null() { - bail!("Failed to get hwnd after started"); - } - } - - Ok(()) -} - -#[inline] -pub fn stop() { - WND_HANDLERS.lock().unwrap().reset(); -} - -unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> { - let dll_file_utf16: Vec = dll_file.encode_utf16().chain(Some(0).into_iter()).collect(); - - let buf = VirtualAllocEx( - hproc, - NULL as _, - dll_file_utf16.len() * 2, - MEM_COMMIT, - PAGE_READWRITE, - ); - if buf.is_null() { - bail!("Failed VirtualAllocEx"); - } - - let mut written: usize = 0; - if 0 == WriteProcessMemory( - hproc, - buf, - dll_file_utf16.as_ptr() as _, - dll_file_utf16.len() * 2, - &mut written, - ) { - bail!("Failed WriteProcessMemory"); - } - - let kernel32_modulename = CString::new("kernel32")?; - let hmodule = GetModuleHandleA(kernel32_modulename.as_ptr() as _); - if hmodule.is_null() { - bail!("Failed GetModuleHandleA"); - } - - let load_librarya_name = CString::new("LoadLibraryW")?; - let load_librarya = GetProcAddress(hmodule, load_librarya_name.as_ptr() as _); - if load_librarya.is_null() { - bail!("Failed GetProcAddress of LoadLibraryW"); - } - - if 0 == QueueUserAPC(Some(std::mem::transmute(load_librarya)), hthread, buf as _) { - bail!("Failed QueueUserAPC"); - } - - Ok(()) -} - -fn wait_find_privacy_hwnd(msecs: u128) -> ResultType { - let tm_begin = Instant::now(); - let wndname = CString::new(PRIVACY_WINDOW_NAME)?; - loop { - unsafe { - let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _); - if !hwnd.is_null() { - return Ok(hwnd); - } - } - - if msecs == 0 || tm_begin.elapsed().as_millis() > msecs { - return Ok(NULL as _); - } - - std::thread::sleep(Duration::from_millis(100)); - } -} - -#[tokio::main(flavor = "current_thread")] -async fn set_privacy_mode_state( - conn_id: i32, - state: PrivacyModeState, - ms_timeout: u64, -) -> ResultType<()> { - let mut c = connect(ms_timeout, "_cm").await?; - c.send(&Data::PrivacyModeState((conn_id, state))).await -} - -pub(super) mod privacy_hook { - - use super::*; - use std::sync::mpsc::{channel, Sender}; - - fn do_hook(tx: Sender) -> ResultType<(HHOOK, HHOOK)> { - let invalid_ret = (0 as HHOOK, 0 as HHOOK); - - let mut cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap(); - if *cur_hook_thread_id != 0 { - // unreachable! - tx.send("Already hooked".to_owned())?; - return Ok(invalid_ret); - } - - unsafe { - let mut hm_keyboard = 0 as HMODULE; - if 0 == GetModuleHandleExA( - GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS - | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - DefWindowProcA as _, - &mut hm_keyboard as _, - ) { - tx.send(format!( - "Failed to GetModuleHandleExA, error: {}", - GetLastError() - ))?; - return Ok(invalid_ret); - } - let mut hm_mouse = 0 as HMODULE; - if 0 == GetModuleHandleExA( - GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS - | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - DefWindowProcA as _, - &mut hm_mouse as _, - ) { - tx.send(format!( - "Failed to GetModuleHandleExA, error: {}", - GetLastError() - ))?; - return Ok(invalid_ret); - } - - let hook_keyboard = SetWindowsHookExA( - WH_KEYBOARD_LL, - Some(privacy_mode_hook_keyboard), - hm_keyboard, - 0, - ); - if hook_keyboard.is_null() { - tx.send(format!(" SetWindowsHookExA keyboard {}", GetLastError()))?; - return Ok(invalid_ret); - } - - let hook_mouse = - SetWindowsHookExA(WH_MOUSE_LL, Some(privacy_mode_hook_mouse), hm_mouse, 0); - if hook_mouse.is_null() { - if FALSE == UnhookWindowsHookEx(hook_keyboard) { - // Fatal error - log::error!(" UnhookWindowsHookEx keyboard {}", GetLastError()); - } - tx.send(format!(" SetWindowsHookExA mouse {}", GetLastError()))?; - return Ok(invalid_ret); - } - - *cur_hook_thread_id = GetCurrentThreadId(); - tx.send("".to_owned())?; - return Ok((hook_keyboard, hook_mouse)); - } - } - - pub fn hook() -> ResultType<()> { - let (tx, rx) = channel(); - std::thread::spawn(move || { - let hook_keyboard; - let hook_mouse; - unsafe { - match do_hook(tx.clone()) { - Ok(hooks) => { - hook_keyboard = hooks.0; - hook_mouse = hooks.1; - } - Err(e) => { - // Fatal error - allow_err!(tx.send(format!("Unexpected err when hook {}", e))); - return; - } - } - if hook_keyboard.is_null() { - return; - } - - let mut msg = MSG { - hwnd: NULL as _, - message: 0 as _, - wParam: 0 as _, - lParam: 0 as _, - time: 0 as _, - pt: POINT { - x: 0 as _, - y: 0 as _, - }, - }; - while FALSE != GetMessageA(&mut msg, NULL as _, 0, 0) { - if msg.message == WM_USER_EXIT_HOOK { - break; - } - - TranslateMessage(&msg); - DispatchMessageA(&msg); - } - - if FALSE == UnhookWindowsHookEx(hook_keyboard as _) { - // Fatal error - log::error!("Failed UnhookWindowsHookEx keyboard {}", GetLastError()); - } - - if FALSE == UnhookWindowsHookEx(hook_mouse as _) { - // Fatal error - log::error!("Failed UnhookWindowsHookEx mouse {}", GetLastError()); - } - - *CUR_HOOK_THREAD_ID.lock().unwrap() = 0; - } - }); - - match rx.recv() { - Ok(msg) => { - if msg == "" { - Ok(()) - } else { - bail!(msg) - } - } - Err(e) => { - bail!("Failed to wait hook result {}", e) - } - } - } - - pub fn unhook() -> ResultType<()> { - unsafe { - let cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap(); - if *cur_hook_thread_id != 0 { - if FALSE == PostThreadMessageA(*cur_hook_thread_id, WM_USER_EXIT_HOOK, 0, 0) { - bail!("Failed to post message to exit hook, {}", GetLastError()); - } - } - } - Ok(()) - } - - #[no_mangle] - pub extern "system" fn privacy_mode_hook_keyboard( - code: c_int, - w_param: WPARAM, - l_param: LPARAM, - ) -> LRESULT { - if code < 0 { - unsafe { - return CallNextHookEx(NULL as _, code, w_param, l_param); - } - } - - let ks = l_param as PKBDLLHOOKSTRUCT; - let w_param2 = w_param as UINT; - - unsafe { - if (*ks).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE { - // Disable alt key. Alt + Tab will switch windows. - if (*ks).flags & LLKHF_ALTDOWN == LLKHF_ALTDOWN { - return 1; - } - - match w_param2 { - WM_KEYDOWN => { - // Disable all keys other than P and Ctrl. - if ![80, 162, 163].contains(&(*ks).vkCode) { - return 1; - } - - // NOTE: GetKeyboardState may not work well... - - // Check if Ctrl + P is pressed - let cltr_down = (GetKeyState(VK_CONTROL) as u16) & (0x8000 as u16) > 0; - let key = LOBYTE((*ks).vkCode as _); - if cltr_down && (key == 'p' as u8 || key == 'P' as u8) { - // Ctrl + P is pressed, turn off privacy mode - if let Err(e) = - turn_off_privacy(0, Some(crate::ipc::PrivacyModeState::OffByPeer)) - { - log::error!("Failed to off_privacy {}", e); - } - } - } - WM_KEYUP => { - log::trace!("WM_KEYUP {}", (*ks).vkCode); - } - _ => { - log::trace!("KEYBOARD OTHER {} {}", w_param2, (*ks).vkCode); - } - } - } - } - unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) } - } - - #[no_mangle] - pub extern "system" fn privacy_mode_hook_mouse( - code: c_int, - w_param: WPARAM, - l_param: LPARAM, - ) -> LRESULT { - if code < 0 { - unsafe { - return CallNextHookEx(NULL as _, code, w_param, l_param); - } - } - - let ms = l_param as PMOUSEHOOKSTRUCT; - unsafe { - if (*ms).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE { - return 1; - } - } - unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) } - } -} - -mod test { - #[test] - fn privacy_hook() { - //use super::*; - - // privacy_hook::hook().unwrap(); - // std::thread::sleep(std::time::Duration::from_millis(50)); - // privacy_hook::unhook().unwrap(); - } -} diff --git a/src/server/connection.rs b/src/server/connection.rs index f55041f41..10e59701e 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -17,11 +17,10 @@ use crate::{ new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender, }, common::{get_default_sound_input, set_sound_input}, - display_service, video_service, + display_service, ipc, privacy_mode, video_service, VERSION, }; #[cfg(any(target_os = "android", target_os = "ios"))] use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel}; -use crate::{ipc, VERSION}; use cidr_utils::cidr::IpCidr; #[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))] @@ -506,24 +505,27 @@ impl Connection { ipc::Data::ClipboardFile(clip) => { allow_err!(conn.stream.send(&clip_2_msg(clip)).await); } - ipc::Data::PrivacyModeState((_, state)) => { + ipc::Data::PrivacyModeState((_, state, impl_key)) => { let msg_out = match state { ipc::PrivacyModeState::OffSucceeded => { video_service::set_privacy_mode_conn_id(0); crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOffSucceeded, + impl_key, ) } ipc::PrivacyModeState::OffByPeer => { video_service::set_privacy_mode_conn_id(0); crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOffByPeer, + impl_key, ) } ipc::PrivacyModeState::OffUnknown => { video_service::set_privacy_mode_conn_id(0); crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOffUnknown, + impl_key, ) } }; @@ -683,9 +685,7 @@ impl Connection { let video_privacy_conn_id = video_service::get_privacy_mode_conn_id(); if video_privacy_conn_id == id { video_service::set_privacy_mode_conn_id(0); - let _ = privacy_mode::turn_off_privacy(id); - } else if video_privacy_conn_id == 0 { - let _ = privacy_mode::turn_off_privacy(0); + let _ = Self::turn_off_privacy_to_msg(id); } #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -1059,7 +1059,13 @@ impl Connection { pi.hostname = DEVICE_NAME.lock().unwrap().clone(); pi.platform = "Android".into(); } - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] + #[cfg(all(target_os = "macos", not(feature = "unix-file-copy-paste")))] + let platform_additions = serde_json::Map::new(); + #[cfg(any( + target_os = "windows", + target_os = "linux", + all(target_os = "macos", feature = "unix-file-copy-paste") + ))] let mut platform_additions = serde_json::Map::new(); #[cfg(target_os = "linux")] { @@ -1087,6 +1093,10 @@ impl Connection { platform_additions.insert("virtual_displays".into(), json!(&virtual_displays)); } } + platform_additions.insert( + "supported_privacy_mode_impl".into(), + json!(privacy_mode::get_supported_privacy_mode_impl()), + ); } #[cfg(any( @@ -1158,7 +1168,7 @@ impl Connection { pi.username = username; pi.sas_enabled = sas_enabled; pi.features = Some(Features { - privacy_mode: display_service::is_privacy_mode_supported(), + privacy_mode: privacy_mode::is_privacy_mode_supported(), ..Default::default() }) .into(); @@ -1474,6 +1484,7 @@ impl Connection { fn try_start_cm_ipc(&mut self) { if let Some(p) = self.start_cm_ipc_para.take() { tokio::spawn(async move { + #[cfg(windows)] let tx_from_cm_clone = p.tx_from_cm.clone(); if let Err(err) = start_ipc( p.rx_to_cm, @@ -2090,6 +2101,9 @@ impl Connection { Some(misc::Union::ToggleVirtualDisplay(t)) => { self.toggle_virtual_display(t).await; } + Some(misc::Union::TogglePrivacyMode(t)) => { + self.toggle_privacy_mode(t).await; + } Some(misc::Union::ChatMessage(c)) => { self.send_to_cm(ipc::Data::ChatMessage { text: c.text }); self.chat_unanswered = true; @@ -2362,6 +2376,14 @@ impl Connection { } } + async fn toggle_privacy_mode(&mut self, t: TogglePrivacyMode) { + if t.on { + self.turn_on_privacy(t.impl_key).await; + } else { + self.turn_off_privacy(t.impl_key).await; + } + } + #[cfg(not(any(target_os = "android", target_os = "ios")))] fn change_resolution(&mut self, r: &Resolution) { if self.keyboard { @@ -2535,70 +2557,21 @@ impl Connection { } } } - if let Ok(q) = o.privacy_mode.enum_value() { - if self.keyboard { - match q { - BoolOption::Yes => { - let msg_out = if !display_service::is_privacy_mode_supported() { - crate::common::make_privacy_mode_msg_with_details( - back_notification::PrivacyModeState::PrvNotSupported, - "Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(), - ) - } else { - match privacy_mode::turn_on_privacy(self.inner.id) { - Ok(true) => { - let err_msg = video_service::test_create_capturer( - self.inner.id, - self.display_idx, - 5_000, - ); - if err_msg.is_empty() { - video_service::set_privacy_mode_conn_id(self.inner.id); - crate::common::make_privacy_mode_msg( - back_notification::PrivacyModeState::PrvOnSucceeded, - ) - } else { - log::error!( - "Wait privacy mode timeout, turn off privacy mode" - ); - video_service::set_privacy_mode_conn_id(0); - let _ = privacy_mode::turn_off_privacy(self.inner.id); - crate::common::make_privacy_mode_msg_with_details( - back_notification::PrivacyModeState::PrvOnFailed, - err_msg, - ) - } - } - Ok(false) => crate::common::make_privacy_mode_msg( - back_notification::PrivacyModeState::PrvOnFailedPlugin, - ), - Err(e) => { - log::error!("Failed to turn on privacy mode. {}", e); - if video_service::get_privacy_mode_conn_id() == 0 { - let _ = privacy_mode::turn_off_privacy(0); - } - crate::common::make_privacy_mode_msg_with_details( - back_notification::PrivacyModeState::PrvOnFailed, - e.to_string(), - ) - } - } - }; - self.send(msg_out).await; + // For compatibility with old versions ( < 1.2.4 ). + if hbb_common::get_version_number(&self.lr.version) + < hbb_common::get_version_number("1.2.4") + { + if let Ok(q) = o.privacy_mode.enum_value() { + if self.keyboard { + match q { + BoolOption::Yes => { + self.turn_on_privacy("".to_owned()).await; + } + BoolOption::No => { + self.turn_off_privacy("".to_owned()).await; + } + _ => {} } - BoolOption::No => { - let msg_out = if !display_service::is_privacy_mode_supported() { - crate::common::make_privacy_mode_msg_with_details( - back_notification::PrivacyModeState::PrvNotSupported, - "Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(), - ) - } else { - video_service::set_privacy_mode_conn_id(0); - privacy_mode::turn_off_privacy(self.inner.id) - }; - self.send(msg_out).await; - } - _ => {} } } } @@ -2628,6 +2601,107 @@ impl Connection { } } + async fn turn_on_privacy(&mut self, impl_key: String) { + let msg_out = if !privacy_mode::is_privacy_mode_supported() { + crate::common::make_privacy_mode_msg_with_details( + back_notification::PrivacyModeState::PrvNotSupported, + "Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(), + impl_key, + ) + } else { + match privacy_mode::turn_on_privacy(&impl_key, self.inner.id) { + Some(Ok(res)) => { + if res { + let err_msg = privacy_mode::check_privacy_mode_err( + self.inner.id, + self.display_idx, + 5_000, + ); + if err_msg.is_empty() { + video_service::set_privacy_mode_conn_id(self.inner.id); + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::PrvOnSucceeded, + impl_key, + ) + } else { + log::error!( + "Check privacy mode failed: {}, turn off privacy mode.", + &err_msg + ); + video_service::set_privacy_mode_conn_id(0); + let _ = Self::turn_off_privacy_to_msg(self.inner.id); + crate::common::make_privacy_mode_msg_with_details( + back_notification::PrivacyModeState::PrvOnFailed, + err_msg, + impl_key, + ) + } + } else { + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::PrvOnFailedPlugin, + impl_key, + ) + } + } + Some(Err(e)) => { + log::error!("Failed to turn on privacy mode. {}", e); + if video_service::get_privacy_mode_conn_id() == 0 { + let _ = Self::turn_off_privacy_to_msg(0); + } + crate::common::make_privacy_mode_msg_with_details( + back_notification::PrivacyModeState::PrvOnFailed, + e.to_string(), + impl_key, + ) + } + None => crate::common::make_privacy_mode_msg_with_details( + back_notification::PrivacyModeState::PrvOffFailed, + "Not supported".to_string(), + impl_key, + ), + } + }; + self.send(msg_out).await; + } + + async fn turn_off_privacy(&mut self, impl_key: String) { + let msg_out = if !privacy_mode::is_privacy_mode_supported() { + crate::common::make_privacy_mode_msg_with_details( + back_notification::PrivacyModeState::PrvNotSupported, + // This error message is used for magnifier. It is ok to use it here. + "Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(), + impl_key, + ) + } else { + video_service::set_privacy_mode_conn_id(0); + Self::turn_off_privacy_to_msg(self.inner.id) + }; + self.send(msg_out).await; + } + + pub fn turn_off_privacy_to_msg(_conn_id: i32) -> Message { + let impl_key = "".to_owned(); + match privacy_mode::turn_off_privacy(_conn_id, None) { + Some(Ok(_)) => crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::PrvOffSucceeded, + impl_key, + ), + Some(Err(e)) => { + log::error!("Failed to turn off privacy mode {}", e); + crate::common::make_privacy_mode_msg_with_details( + back_notification::PrivacyModeState::PrvOffFailed, + e.to_string(), + impl_key, + ) + } + None => crate::common::make_privacy_mode_msg_with_details( + back_notification::PrivacyModeState::PrvOffFailed, + "Not supported".to_string(), + impl_key, + ), + } + } + async fn on_close(&mut self, reason: &str, lock: bool) { if self.closed { return; @@ -2923,47 +2997,6 @@ fn try_activate_screen() { }); } -mod privacy_mode { - use super::*; - #[cfg(windows)] - use crate::privacy_win_mag; - - pub(super) fn turn_off_privacy(_conn_id: i32) -> Message { - #[cfg(windows)] - { - let res = privacy_win_mag::turn_off_privacy(_conn_id, None); - match res { - Ok(_) => crate::common::make_privacy_mode_msg( - back_notification::PrivacyModeState::PrvOffSucceeded, - ), - Err(e) => { - log::error!("Failed to turn off privacy mode {}", e); - crate::common::make_privacy_mode_msg_with_details( - back_notification::PrivacyModeState::PrvOffFailed, - e.to_string(), - ) - } - } - } - #[cfg(not(windows))] - { - crate::common::make_privacy_mode_msg(back_notification::PrivacyModeState::PrvOffFailed) - } - } - - pub(super) fn turn_on_privacy(_conn_id: i32) -> ResultType { - #[cfg(windows)] - { - let plugin_exist = privacy_win_mag::turn_on_privacy(_conn_id)?; - Ok(plugin_exist) - } - #[cfg(not(windows))] - { - Ok(true) - } - } -} - pub enum AlarmAuditType { IpWhitelist = 0, ExceedThirtyAttempts = 1, @@ -3208,8 +3241,6 @@ mod raii { display_service::reset_resolutions(); #[cfg(all(windows, feature = "virtual_display_driver"))] let _ = virtual_display_manager::reset_all(); - #[cfg(all(windows))] - crate::privacy_win_mag::stop(); } } } diff --git a/src/server/display_service.rs b/src/server/display_service.rs index a9a49818c..967dfa878 100644 --- a/src/server/display_service.rs +++ b/src/server/display_service.rs @@ -138,12 +138,10 @@ pub fn capture_cursor_embedded() -> bool { } #[inline] -pub fn is_privacy_mode_supported() -> bool { - #[cfg(windows)] +#[cfg(windows)] +pub fn is_privacy_mode_mag_supported() -> bool { return *IS_CAPTURER_MAGNIFIER_SUPPORTED && get_version_number(&crate::VERSION) > get_version_number("1.1.9"); - #[cfg(not(windows))] - return false; } pub fn new() -> GenericService { diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 22fe7a89f..02db8c7f3 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -29,7 +29,11 @@ use crate::common::SimpleCallOnReturn; #[cfg(target_os = "linux")] use crate::platform::linux::is_x11; #[cfg(windows)] -use crate::{platform::windows::is_process_consent_running, privacy_win_mag}; +use crate::{ + platform::windows::is_process_consent_running, + privacy_mode::{is_current_privacy_mode_impl, PRIVACY_MODE_IMPL_WIN_MAG}, + ui_interface::is_installed, +}; use hbb_common::{ anyhow::anyhow, tokio::sync::{ @@ -182,43 +186,13 @@ fn create_capturer( if privacy_mode_id > 0 { #[cfg(windows)] { - match scrap::CapturerMag::new(display.origin(), display.width(), display.height()) { - Ok(mut c1) => { - let mut ok = false; - let check_begin = Instant::now(); - while check_begin.elapsed().as_secs() < 5 { - match c1.exclude("", privacy_win_mag::PRIVACY_WINDOW_NAME) { - Ok(false) => { - ok = false; - std::thread::sleep(std::time::Duration::from_millis(500)); - } - Err(e) => { - bail!( - "Failed to exclude privacy window {} - {}, err: {}", - "", - privacy_win_mag::PRIVACY_WINDOW_NAME, - e - ); - } - _ => { - ok = true; - break; - } - } - } - if !ok { - bail!( - "Failed to exclude privacy window {} - {} ", - "", - privacy_win_mag::PRIVACY_WINDOW_NAME - ); - } - log::debug!("Create magnifier capture for {}", privacy_mode_id); - c = Some(Box::new(c1)); - } - Err(e) => { - bail!(format!("Failed to create magnifier capture {}", e)); - } + if let Some(c1) = crate::privacy_mode::win_mag::create_capturer( + privacy_mode_id, + display.origin(), + display.width(), + display.height(), + )? { + c = Some(Box::new(c1)); } } } @@ -274,17 +248,20 @@ pub fn test_create_capturer( } } +// Note: This function is extremely expensive, do not call it frequently. #[cfg(windows)] fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> ResultType<()> { - if capturer_privacy_mode_id != 0 { - if privacy_mode_id != capturer_privacy_mode_id { - if !is_process_consent_running()? { + if capturer_privacy_mode_id != 0 && is_current_privacy_mode_impl(PRIVACY_MODE_IMPL_WIN_MAG) { + if !is_installed() { + if privacy_mode_id != capturer_privacy_mode_id { + if !is_process_consent_running()? { + bail!("consent.exe is not running"); + } + } + if is_process_consent_running()? { bail!("consent.exe is running"); } } - if is_process_consent_running()? { - bail!("consent.exe is running"); - } } Ok(()) } @@ -352,9 +329,14 @@ fn get_capturer(current: usize, portable_service_running: bool) -> ResultType ResultType<()> { let wait_begin = Instant::now(); while wait_begin.elapsed().as_millis() < timeout_millis as _ { check_privacy_mode_changed(&sp, c.privacy_mode_id)?; - #[cfg(windows)] - check_uac_switch(c.privacy_mode_id, c._capturer_privacy_mode_id)?; frame_controller.try_wait_next(&mut fetched_conn_ids, 300); // break if all connections have received current frame if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() { @@ -683,6 +663,7 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu if privacy_mode_id_2 != 0 { let msg_out = crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOnByOther, + "".to_owned(), ); sp.send_to_others(msg_out, privacy_mode_id_2); } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 12d9db276..c4cdbf784 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -19,8 +19,6 @@ use crate::ipc::{self, Data}; use clipboard::ContextSend; #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::tokio::sync::mpsc::unbounded_channel; -#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] -use hbb_common::tokio::sync::Mutex as TokioMutex; use hbb_common::{ allow_err, config::Config, @@ -34,8 +32,9 @@ use hbb_common::{ sync::mpsc::{self, UnboundedSender}, task::spawn_blocking, }, - ResultType, }; +#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] +use hbb_common::{tokio::sync::Mutex as TokioMutex, ResultType}; use serde_derive::Serialize; #[derive(Serialize, Clone)] @@ -401,7 +400,7 @@ impl IpcTaskRunner { log::info!("cm ipc connection disconnect"); break; } - Data::PrivacyModeState((_id, _)) => { + Data::PrivacyModeState((_id, _, _)) => { #[cfg(windows)] cm_inner_send(_id, data); } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index d681de830..6bfbab5ee 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1007,8 +1007,13 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver Session { } } + pub fn toggle_privacy_mode(&self, impl_key: String, on: bool) { + let mut misc = Misc::new(); + misc.set_toggle_privacy_mode(TogglePrivacyMode { + impl_key, + on, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + self.send(Data::Message(msg_out)); + } + pub fn get_toggle_option(&self, name: String) -> bool { self.lc.read().unwrap().get_toggle_option(&name) } diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs index fc9af6dea..b08c7dab9 100644 --- a/src/virtual_display_manager.rs +++ b/src/virtual_display_manager.rs @@ -6,8 +6,8 @@ use std::{ // virtual display index range: 0 - 2 are reserved for headless and other special uses. const VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS: u32 = 0; -const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 3; -const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 10; +const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 1; +const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 5; lazy_static::lazy_static! { static ref VIRTUAL_DISPLAY_MANAGER: Arc> = @@ -165,6 +165,7 @@ pub fn plug_in_peer_request(modes: Vec>) -> Re log::error!("Plug in monitor failed {}", e); } } + break; } } }