feat/virtual_display_privacy_mode

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2023-11-14 12:11:38 +08:00
parent d64afdcff1
commit 90ac8b7b0b
67 changed files with 2228 additions and 974 deletions

View File

@ -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<Widget> buttons = [];
bool hasOk = false;
@ -1983,8 +1984,8 @@ List<String>? 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<IDTextEditingController>();
idController.text = formatID(id);
}
if (Get.isRegistered<TextEditingController>()){
if (Get.isRegistered<TextEditingController>()) {
final fieldTextEditingController = Get.find<TextEditingController>();
fieldTextEditingController.text = formatID(id);
}

View File

@ -10,7 +10,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);
}
}
@ -20,11 +20,11 @@ class PrivacyModeState {
if (Get.isRegistered(tag: key)) {
Get.delete(tag: key);
} else {
Get.find<RxBool>(tag: key).value = false;
Get.find<RxString>(tag: key).value = '';
}
}
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
static RxString find(String id) => Get.find<RxString>(tag: tag(id));
}
class BlockInputState {

View File

@ -482,22 +482,13 @@ Future<List<TToggleMenu>> 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'))));
if (!isDesktop && ffiModel.keyboard && pi.features.privacyMode) {
final privacyModeState = PrivacyModeState.find(id);
final privacyModeList =
toolbarPrivacyMode(privacyModeState, context, id, ffi);
if (privacyModeList.isNotEmpty) {
v.addAll(privacyModeList);
}
}
// swap key
if (ffiModel.keyboard &&
@ -517,7 +508,7 @@ Future<List<TToggleMenu>> 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 +558,73 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
return v;
}
var togglePrivacyModeTime = DateTime.now().subtract(const Duration(hours: 1));
List<TToggleMenu> toolbarPrivacyMode(
RxString privacyModeState, BuildContext context, String id, FFI ffi) {
final ffiModel = ffi.ffiModel;
final pi = ffiModel.pi;
final sessionId = ffi.sessionId;
getDefaultMenu(Future<void> 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<dynamic>?;
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<dynamic>)[0] as String;
return [
getDefaultMenu((sid, opt) async {
bind.sessionTogglePrivacyMode(sessionId: sid, implKey: implKey);
togglePrivacyModeTime = DateTime.now();
})
];
} else {
return privacyModeImpls.map((e) {
final implKey = (e as List<dynamic>)[0] as String;
final implName = (e)[1] as String;
return TToggleMenu(
child: Text(isDesktop
? translate(implName)
: '${translate('Privacy mode')} - ${translate(implName)}'),
value: privacyModeState.value == implKey,
onChanged:
(privacyModeState.isEmpty || privacyModeState.value == implKey)
? (value) {
if (value == null) return;
togglePrivacyModeTime = DateTime.now();
bind.sessionTogglePrivacyMode(
sessionId: sessionId, implKey: implKey);
}
: null);
}).toList();
}
}

View File

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

View File

@ -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<String, dynamic> 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<dynamic> 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<dynamic>;
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 {

View File

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

View File

@ -468,7 +468,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
}
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,64 @@ class _DisplayMenuState extends State<_DisplayMenu> {
@override
Widget build(BuildContext context) {
_screenAdjustor.updateScreen();
return _IconSubmenuButton(
tooltip: 'Display Settings',
svg: "assets/display.svg",
final menuChildren = <Widget>[
_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) => Obx(() => CkbMenuButton(
value: e.value,
onChanged: (privacyModeState.isEmpty || e.value)
? e.onChanged
: null,
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 +1528,39 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
}
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
final privacyModeState = PrivacyModeState.find(widget.id);
final children = <Widget>[];
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,

View File

@ -967,11 +967,21 @@ class FfiModel with ChangeNotifier {
}
updatePrivacyMode(
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
Map<String, dynamic> 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) {
//
}

View File

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

View File

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

View File

@ -926,6 +926,24 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
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<T: InvokeUiSession> Remote<T> {
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<T: InvokeUiSession> Remote<T> {
.handle_back_msg_privacy_mode(
state.enum_value_or(back_notification::PrivacyModeState::PrvStateUnknown),
notification.details,
notification.impl_key,
)
.await
{
@ -1615,9 +1635,20 @@ impl<T: InvokeUiSession> Remote<T> {
}
#[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<T: InvokeUiSession> Remote<T> {
&mut self,
state: back_notification::PrivacyModeState,
details: String,
impl_key: String,
) -> bool {
match state {
back_notification::PrivacyModeState::PrvOnByOther => {
@ -1641,22 +1673,22 @@ impl<T: InvokeUiSession> Remote<T> {
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<T: InvokeUiSession> Remote<T> {
},
"",
);
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<T: InvokeUiSession> Remote<T> {
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);
}
_ => {}
}

View File

@ -378,19 +378,6 @@ pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc<Mutex<String>>>)
}
}
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],
@ -1068,10 +1055,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);
@ -1082,8 +1071,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(keyboard_mode: &KeyboardMode, version_number: i64, peer_platform: &str) -> bool {

View File

@ -244,6 +244,8 @@ pub fn core_main() -> Option<Vec<String>> {
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);

View File

@ -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) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.toggle_privacy_mode(impl_key);
}
}
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
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<bool> {
}
}
pub fn main_default_privacy_mode_impl() -> SyncReturn<String> {
SyncReturn(crate::privacy_mode::DEFAULT_PRIVACY_MODE_IMPL.to_owned())
}
pub fn main_supported_privacy_mode_impls() -> SyncReturn<String> {
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};

View File

@ -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);
}
@ -603,7 +609,7 @@ async fn check_pid(postfix: &str) {
file.read_to_string(&mut content).ok();
let pid = content.parse::<usize>().unwrap_or(0);
if pid > 0 {
use hbb_common::sysinfo::{System};
use hbb_common::sysinfo::System;
let mut sys = System::new();
sys.refresh_processes();
if let Some(p) = sys.process(pid.into()) {

View File

@ -177,7 +177,6 @@ pub mod client {
Key::MetaRight => Some(ControlKey::RWin),
_ => None,
}
}
pub fn event_lock_screen() -> KeyEvent {

View File

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

View File

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

View File

@ -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", "旧的 Windows API"),
("privacy_mode_impl_virtual_display_tip", "禁用物理显示器"),
("Enter privacy mode", "进入隐私模式"),
("Exit privacy mode", "退出隐私模式"),
].iter().cloned().collect();
}

View File

@ -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 (<doména>:<port>).\nPokud chcete přistupovat k zařízení na jiném serveru, připojte adresu serveru (<id>@<adresa_serveru>?key=<hodnota_klíče>), například,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nPokud chcete přistupovat k zařízení na veřejném serveru, zadejte \"<id>@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();
}

View File

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

View File

@ -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 (<domain>:<port>) eingeben.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen möchten, fügen Sie bitte die Serveradresse (<id>@<server_address>?key=<key_value>) 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 \"<id>@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();
}

View File

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

View File

@ -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 (<domain>:<port>).\nIf you want to access a device on another server, please append the server address (<id>@<server_address>?key=<key_value>), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"<id>@public\", the key is not needed for public server"),
("privacy_mode_impl_mag_tip", "Old Windows magnifier API"),
("privacy_mode_impl_virtual_display_tip", "Disable physical displays"),
].iter().cloned().collect();
}

View File

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

View File

@ -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 (<dominio>:<puerto>).\nSi quieres acceder a un dispositivo en otro servidor, por favor añade la ip del servidor (<id>@<dirección_servidor>?key=<clave_valor>), por ejemplo,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSi quieres acceder a un dispositivo en un servidor público, por favor, introduce \"<id>@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();
}

View File

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

View File

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

View File

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

View File

@ -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 (<domain>:<port>).\nJika kamu ingin mengakses perangkat lain yang berbeda server, tambahkan alamat server setelah penulisan ID(<id>@<server_address>?key=<key_value>), sebagai contoh,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJika kamu ingin mengakses perangkat yang menggunakan server publik, masukkan \"<id>@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();
}

View File

@ -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 (<dominio>:<porta>).\nSe vuoi accedere as un dispositivo in un altro server, aggiungi l'indirizzo del server (<id>@<indirizzo_server >?key=<valore_chiave>), ad esempio\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSe vuoi accedere as un dispositivo in un server pubblico, inserisci \"<id>@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();
}

View File

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

View File

@ -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, 도메인과 포트(<domain>:<port>)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(<id>@<server_address>?key=<key_value>)를 추가하세요.
:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\n
\"<id>@public\",을 입력하세요. 공개 서버에는 키가 필요하지 않습니다"),
("id_input_tip", "입력된 ID, IP, 도메인과 포트(<domain>:<port>)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(<id>@<server_address>?key=<key_value>)를 추가하세요"),
("privacy_mode_impl_mag_tip", ""),
("privacy_mode_impl_virtual_display_tip", ""),
("Enter privacy mode", ""),
("Exit privacy mode", ""),
].iter().cloned().collect();
}

View File

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

View File

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

View File

@ -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 (<domēns>:<ports>).\nJa vēlaties piekļūt ierīcei citā serverī, lūdzu, pievienojiet servera adresi (<id>@<servera_adrese>?key=<atslēgas_vērtība>), piemēram,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJa vēlaties piekļūt ierīcei publiskajā serverī, lūdzu, ievadiet \"<id>@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();
}

View File

@ -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 (<domein>:<poort>) invoeren. Als je toegang wilt als apparaat op een andere server, voeg dan het serveradres toe (<id>@<server_adres>?key=<key_value>), bijvoorbeeld \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.Als je toegang wilt als apparaat op een openbare server, voer dan \"<id>@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();
}

View File

@ -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 (<adres_domenowy>:<port>).\nJeżeli chcesz uzyskać dostęp do urządzenia na innym serwerze, dołącz adres serwera (<id>@<adres_serwera>?key=<wartość_klucza>, np. \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJeżeli chcesz uzyskać dostęp do urządzenia na serwerze publicznym, wpisz \"<id>@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();
}

View File

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

View File

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

View File

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

View File

@ -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Если необходимо получить доступ к устройству на другом сервере, добавьте адрес сервера (<id>@<адрес_сервера>?key=<ключ_значение>), например:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли необходимо получить доступ к устройству на общедоступном сервере, введите \"<id>@public\", ключ для публичного сервера не требуется."),
("privacy_mode_impl_mag_tip", ""),
("privacy_mode_impl_virtual_display_tip", ""),
("Enter privacy mode", ""),
("Exit privacy mode", ""),
].iter().cloned().collect();
}

View File

@ -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 (<doména>:<port>).\nAk chcete získať prístup k zariadeniu na inom serveri, doplňte adresu servera (<id>@<adresa_servera>?key=<hodnota_kľúča>), napríklad,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAk chcete získať prístup k zariadeniu na verejnom serveri, zadajte \"<id>@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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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Якщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (<id>@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"<id>@public\", ключ для публічного сервера не потрібен."),
("privacy_mode_impl_mag_tip", ""),
("privacy_mode_impl_virtual_display_tip", ""),
("Enter privacy mode", ""),
("Exit privacy mode", ""),
].iter().cloned().collect();
}

View File

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

View File

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

View File

@ -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<OsString>) -> 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<String>
{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,
))
}

297
src/privacy_mode.rs Normal file
View File

@ -0,0 +1,297 @@
#[cfg(windows)]
use crate::ipc::{connect, Data};
#[cfg(all(windows, feature = "virtual_display_driver"))]
use crate::platform::is_installed;
use crate::{display_service, 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<bool>;
fn turn_off_privacy(&mut self, conn_id: i32, state: Option<PrivacyModeState>)
-> ResultType<()>;
fn pre_conn_id(&self) -> i32;
#[inline]
fn check_on_conn_id(&self, conn_id: i32) -> ResultType<bool> {
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<Mutex<String>> = {
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<Mutex<Option<Box<dyn PrivacyMode>>>> = {
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<dyn PrivacyMode>;
lazy_static::lazy_static! {
static ref PRIVACY_MODE_CREATOR: Arc<Mutex<HashMap<&'static str, PrivacyModeCreator>>> = {
#[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<ResultType<()>> {
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<ResultType<bool>> {
// Check if privacy mode is already on or occupied by another one
let mut privacy_mode_lock = PRIVACY_MODE.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) | Err(_) => return Some(check_on_conn_id),
_ => {}
}
}
// 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 *cur_impl_lock != impl_key {
if let Some(creator) = PRIVACY_MODE_CREATOR
.lock()
.unwrap()
.get(&(&impl_key as &str))
{
*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<PrivacyModeState>) -> Option<ResultType<()>> {
Some(
PRIVACY_MODE
.lock()
.unwrap()
.as_mut()?
.turn_off_privacy(conn_id, state),
)
}
#[inline]
pub fn check_on_conn_id(conn_id: i32) -> Option<ResultType<bool>> {
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()
}

View File

@ -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<DWORD> = Mutex::new(0);
}
fn do_hook(tx: Sender<String>) -> 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();
}
}

416
src/privacy_mode/win_mag.rs Normal file
View File

@ -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<bool> {
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<PrivacyModeState>,
) -> 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<u16> = 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<u16> = 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<HWND> {
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<Option<scrap::CapturerMag>> {
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));
}
}
}

View File

@ -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<Display>,
virtual_displays: Vec<Display>,
virtual_displays_added: Vec<u32>,
}
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::<DISPLAY_DEVICEW>() 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::<DEVMODEW>() 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::<DEVMODEW>() 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::<DISPLAY_DEVICEW>() 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::<DEVMODEW>() 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<MonitorMode> {
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<bool> {
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(&reg_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<PrivacyModeState>,
) -> 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::<reg_display_settings::RegRecovery>(&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<u8>, isize),
new: (Vec<u8>, isize),
}
pub(super) fn read_reg_connectivity() -> ResultType<HashMap<String, HashMap<String, RegValue>>>
{
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<String, HashMap<String, RegValue>>,
map2: HashMap<String, HashMap<String, RegValue>>,
) -> Option<RegRecovery> {
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(&reg_recovery.path, KEY_READ | KEY_WRITE)?;
let cur_reg_value = reg_item.get_raw_value(&reg_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(&reg_recovery.key, &reg_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,
}
}
}

View File

@ -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<i32> = Mutex::new(0);
static ref CUR_HOOK_THREAD_ID: Mutex<DWORD> = Mutex::new(0);
static ref WND_HANDLERS: Mutex<WindowHandlers> = 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<bool> {
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<PrivacyModeState>) -> 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<u16> = 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<u16> = 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<HWND> {
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<String>) -> 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();
}
}

View File

@ -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<bool> {
#[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();
}
}
}

View File

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

View File

@ -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<Ca
#[cfg(windows)]
let mut capturer_privacy_mode_id = privacy_mode_id;
#[cfg(windows)]
if capturer_privacy_mode_id != 0 {
if is_process_consent_running()? {
capturer_privacy_mode_id = 0;
{
if capturer_privacy_mode_id != 0 && is_current_privacy_mode_impl(PRIVACY_MODE_IMPL_WIN_MAG)
{
if !is_installed() {
if is_process_consent_running()? {
capturer_privacy_mode_id = 0;
}
}
}
}
log::debug!(
@ -586,8 +568,6 @@ fn run(vs: VideoService) -> 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);
}

View File

@ -401,7 +401,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
log::info!("cm ipc connection disconnect");
break;
}
Data::PrivacyModeState((_id, _)) => {
Data::PrivacyModeState((_id, _, _)) => {
#[cfg(windows)]
cm_inner_send(_id, data);
}

View File

@ -1007,8 +1007,13 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
let mut mouse_time = 0;
#[cfg(not(feature = "flutter"))]
let mut id = "".to_owned();
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[allow(unused_mut, dead_code)]
#[cfg(any(
target_os = "windows",
all(
any(target_os = "linux", target_os = "macos"),
feature = "unix-file-copy-paste"
)
))]
let mut enable_file_transfer = "".to_owned();
loop {

View File

@ -317,6 +317,18 @@ impl<T: InvokeUiSession> Session<T> {
}
}
pub fn toggle_privacy_mode(&self, impl_key: String) {
let mut misc = Misc::new();
misc.set_toggle_privacy_mode(TogglePrivacyMode {
impl_key,
on: !self.lc.read().unwrap().get_toggle_option("privacy-mode"),
..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)
}

View File

@ -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<Mutex<VirtualDisplayManager>> =
@ -165,6 +165,7 @@ pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> Re
log::error!("Plug in monitor failed {}", e);
}
}
break;
}
}
}