mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-06-06 17:32:51 +08:00
Merge pull request #6406 from fufesou/feat/virtual_display_privacy_mode
Feat/Windows - virtual display privacy mode
This commit is contained in:
commit
24eb6f8c38
@ -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);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class PrivacyModeState {
|
||||
static void init(String id) {
|
||||
final key = tag(id);
|
||||
if (!Get.isRegistered(tag: key)) {
|
||||
final RxBool state = false.obs;
|
||||
final RxString state = ''.obs;
|
||||
Get.put(state, tag: key);
|
||||
}
|
||||
}
|
||||
@ -21,11 +21,11 @@ class PrivacyModeState {
|
||||
if (Get.isRegistered(tag: key)) {
|
||||
Get.delete(tag: key);
|
||||
} else {
|
||||
Get.find<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 {
|
||||
|
@ -481,24 +481,6 @@ 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'))));
|
||||
}
|
||||
// swap key
|
||||
if (ffiModel.keyboard &&
|
||||
((Platform.isMacOS && pi.platform != kPeerPlatformMacOS) ||
|
||||
@ -517,7 +499,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 +549,69 @@ 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, on: privacyModeState.isEmpty);
|
||||
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(translate(implName)),
|
||||
value: privacyModeState.value == implKey,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
bind.sessionTogglePrivacyMode(
|
||||
sessionId: sessionId, implKey: implKey, on: value);
|
||||
});
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
|
@ -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,62 @@ 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) => CkbMenuButton(
|
||||
value: e.value,
|
||||
onChanged: e.onChanged,
|
||||
child: e.child,
|
||||
ffi: ffi))
|
||||
.toList()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
menuChildren.add(widget.pluginItem);
|
||||
|
||||
return _IconSubmenuButton(
|
||||
tooltip: 'Display Settings',
|
||||
svg: "assets/display.svg",
|
||||
ffi: widget.ffi,
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
menuChildren: menuChildren,
|
||||
);
|
||||
}
|
||||
|
||||
viewStyle() {
|
||||
@ -1495,32 +1526,39 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
||||
}
|
||||
|
||||
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
|
||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||
|
||||
final children = <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,
|
||||
|
@ -21,6 +21,7 @@ import '../../models/input_model.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../../utils/image.dart';
|
||||
import '../widgets/dialog.dart';
|
||||
|
||||
final initText = '1' * 1024;
|
||||
|
||||
@ -807,6 +808,16 @@ void showOptions(
|
||||
List<TToggleMenu> displayToggles =
|
||||
await toolbarDisplayToggle(context, id, gFFI);
|
||||
|
||||
List<TToggleMenu> privacyModeList = [];
|
||||
// privacy mode
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if (gFFI.ffiModel.keyboard && gFFI.ffiModel.pi.features.privacyMode) {
|
||||
privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI);
|
||||
if (privacyModeList.length == 1) {
|
||||
displayToggles.add(privacyModeList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
dialogManager.show((setState, close, context) {
|
||||
var viewStyle =
|
||||
(viewStyleRadios.isNotEmpty ? viewStyleRadios[0].groupValue : '').obs;
|
||||
@ -849,10 +860,21 @@ void showOptions(
|
||||
title: e.value.child)))
|
||||
.toList();
|
||||
|
||||
Widget privacyModeWidget = Offstage();
|
||||
if (privacyModeList.length > 1) {
|
||||
privacyModeWidget = ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: Text(translate('Privacy mode')),
|
||||
onTap: () => setPrivacyModeDialog(
|
||||
dialogManager, privacyModeList, privacyModeState),
|
||||
);
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: displays + radios + toggles),
|
||||
children: displays + radios + toggles + [privacyModeWidget]),
|
||||
);
|
||||
}, clickMaskDismiss: true, backDismiss: true);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
@ -259,6 +260,30 @@ void showServerSettingsWithValue(
|
||||
});
|
||||
}
|
||||
|
||||
void setPrivacyModeDialog(
|
||||
OverlayDialogManager dialogManager,
|
||||
List<TToggleMenu> privacyModeList,
|
||||
RxString privacyModeState,
|
||||
) async {
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show((setState, close, context) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('Privacy mode')),
|
||||
content: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: privacyModeList
|
||||
.map((value) => CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: value.child,
|
||||
value: value.value,
|
||||
onChanged: value.onChanged,
|
||||
))
|
||||
.toList()),
|
||||
);
|
||||
}, backDismiss: true, clickMaskDismiss: true);
|
||||
}
|
||||
|
||||
Future<String?> validateAsync(String value) async {
|
||||
value = value.trim();
|
||||
if (value.isEmpty) {
|
||||
|
@ -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) {
|
||||
//
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -14,8 +14,6 @@ use crossbeam_queue::ArrayQueue;
|
||||
use hbb_common::sleep;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::{PeerConfig, TransferSerde},
|
||||
@ -34,8 +32,10 @@ use hbb_common::{
|
||||
sync::mpsc,
|
||||
time::{self, Duration, Instant, Interval},
|
||||
},
|
||||
ResultType, Stream,
|
||||
Stream,
|
||||
};
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use hbb_common::{tokio::sync::Mutex as TokioMutex, ResultType};
|
||||
use scrap::CodecFormat;
|
||||
|
||||
use crate::client::{
|
||||
@ -926,6 +926,24 @@ impl<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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -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],
|
||||
@ -1070,10 +1057,12 @@ pub async fn post_request_sync(url: String, body: String, header: &str) -> Resul
|
||||
pub fn make_privacy_mode_msg_with_details(
|
||||
state: back_notification::PrivacyModeState,
|
||||
details: String,
|
||||
impl_key: String,
|
||||
) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
let mut back_notification = BackNotification {
|
||||
details,
|
||||
impl_key,
|
||||
..Default::default()
|
||||
};
|
||||
back_notification.set_privacy_mode_state(state);
|
||||
@ -1084,8 +1073,8 @@ pub fn make_privacy_mode_msg_with_details(
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Message {
|
||||
make_privacy_mode_msg_with_details(state, "".to_owned())
|
||||
pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState, impl_key: String) -> Message {
|
||||
make_privacy_mode_msg_with_details(state, "".to_owned(), impl_key)
|
||||
}
|
||||
|
||||
pub fn is_keyboard_mode_supported(
|
||||
|
@ -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);
|
||||
|
@ -224,6 +224,12 @@ pub fn session_toggle_option(session_id: SessionID, value: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_toggle_privacy_mode(session_id: SessionID, impl_key: String, on: bool) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.toggle_privacy_mode(impl_key, on);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<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};
|
||||
|
10
src/ipc.rs
10
src/ipc.rs
@ -28,7 +28,7 @@ use hbb_common::{
|
||||
ResultType,
|
||||
};
|
||||
|
||||
use crate::rendezvous_mediator::RendezvousMediator;
|
||||
use crate::{common::is_server, privacy_mode, rendezvous_mediator::RendezvousMediator};
|
||||
|
||||
// State with timestamp, because std::time::Instant cannot be serialized
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@ -207,7 +207,7 @@ pub enum Data {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
ClipboardFile(ClipboardFile),
|
||||
ClipboardFileEnabled(bool),
|
||||
PrivacyModeState((i32, PrivacyModeState)),
|
||||
PrivacyModeState((i32, PrivacyModeState, String)),
|
||||
TestRendezvousServer,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Keyboard(DataKeyboard),
|
||||
@ -352,6 +352,9 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
if EXIT_RECV_CLOSE.load(Ordering::SeqCst) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
crate::server::input_service::fix_key_down_timeout_at_exit();
|
||||
if is_server() {
|
||||
let _ = privacy_mode::turn_off_privacy(0, Some(PrivacyModeState::OffByPeer));
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
@ -442,6 +445,9 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
}
|
||||
Some(value) => {
|
||||
let _chk = CheckIfRestart::new();
|
||||
if let Some(v) = value.get("privacy-mode-impl-key") {
|
||||
crate::privacy_mode::switch(v);
|
||||
}
|
||||
Config::set_options(value);
|
||||
allow_err!(stream.send(&Data::Options(None)).await);
|
||||
}
|
||||
|
@ -177,7 +177,6 @@ pub mod client {
|
||||
Key::MetaRight => Some(ControlKey::RWin),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn event_lock_screen() -> KeyEvent {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Peer exit", "被控端退出"),
|
||||
("Failed to turn off", "退出失败"),
|
||||
("Turned off", "退出"),
|
||||
("In privacy mode", "进入隐私模式"),
|
||||
("Out privacy mode", "退出隐私模式"),
|
||||
("Language", "语言"),
|
||||
("Keep RustDesk background service", "保持 RustDesk 后台服务"),
|
||||
("Ignore Battery Optimizations", "忽略电池优化"),
|
||||
@ -570,5 +568,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("True color (4:4:4)", "真彩模式(4:4:4)"),
|
||||
("Enable blocking user input", "允许阻止用户输入"),
|
||||
("id_input_tip", ""),
|
||||
("privacy_mode_impl_mag_tip", "模式 1 (不推荐)"),
|
||||
("privacy_mode_impl_virtual_display_tip", "模式 2 (推荐)"),
|
||||
("Enter privacy mode", "进入隐私模式"),
|
||||
("Exit privacy mode", "退出隐私模式"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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", "Mode 1 (deprecated)"),
|
||||
("privacy_mode_impl_virtual_display_tip", "Mode 2 (recommended)"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
|
313
src/privacy_mode.rs
Normal file
313
src/privacy_mode.rs
Normal file
@ -0,0 +1,313 @@
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
use crate::platform::is_installed;
|
||||
#[cfg(windows)]
|
||||
use crate::{
|
||||
display_service,
|
||||
ipc::{connect, Data},
|
||||
};
|
||||
use crate::{ipc::PrivacyModeState, ui_interface::get_option};
|
||||
#[cfg(windows)]
|
||||
use hbb_common::tokio;
|
||||
use hbb_common::{anyhow::anyhow, bail, lazy_static, ResultType};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
mod win_input;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod win_mag;
|
||||
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
mod win_virtual_display;
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
pub use win_virtual_display::restore_reg_connectivity;
|
||||
|
||||
pub const INVALID_PRIVACY_MODE_CONN_ID: i32 = 0;
|
||||
pub const OCCUPIED: &'static str = "Privacy occupied by another one";
|
||||
pub const TURN_OFF_OTHER_ID: &'static str =
|
||||
"Failed to turn off privacy mode that belongs to someone else";
|
||||
pub const NO_DISPLAYS: &'static str = "No displays";
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const PRIVACY_MODE_IMPL_WIN_MAG: &str = win_mag::PRIVACY_MODE_IMPL;
|
||||
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
pub const PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY: &str = win_virtual_display::PRIVACY_MODE_IMPL;
|
||||
|
||||
pub trait PrivacyMode: Sync + Send {
|
||||
fn init(&self) -> ResultType<()>;
|
||||
fn clear(&mut self);
|
||||
fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType<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();
|
||||
|
||||
// Check or switch privacy mode implementation
|
||||
let impl_key = get_supported_impl(impl_key);
|
||||
let mut cur_impl_lock = CUR_PRIVACY_MODE_IMPL.lock().unwrap();
|
||||
|
||||
if let Some(privacy_mode) = privacy_mode_lock.as_ref() {
|
||||
let check_on_conn_id = privacy_mode.check_on_conn_id(conn_id);
|
||||
match check_on_conn_id.as_ref() {
|
||||
Ok(true) => {
|
||||
if *cur_impl_lock == impl_key {
|
||||
return Some(Ok(true));
|
||||
} else {
|
||||
// Same peer, switch to new implementation.
|
||||
}
|
||||
}
|
||||
Err(_) => return Some(check_on_conn_id),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if *cur_impl_lock != impl_key {
|
||||
if let Some(creator) = PRIVACY_MODE_CREATOR
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&(&impl_key as &str))
|
||||
{
|
||||
if let Some(privacy_mode) = privacy_mode_lock.as_mut() {
|
||||
privacy_mode.clear();
|
||||
}
|
||||
|
||||
*privacy_mode_lock = Some(creator());
|
||||
*cur_impl_lock = impl_key.to_owned();
|
||||
} else {
|
||||
return Some(Err(anyhow!("Unsupported privacy mode: {}", impl_key)));
|
||||
}
|
||||
}
|
||||
|
||||
// turn on privacy mode
|
||||
Some(privacy_mode_lock.as_mut()?.turn_on_privacy(conn_id))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn turn_off_privacy(conn_id: i32, state: Option<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()
|
||||
}
|
258
src/privacy_mode/win_input.rs
Normal file
258
src/privacy_mode/win_input.rs
Normal 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
416
src/privacy_mode/win_mag.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
566
src/privacy_mode/win_virtual_display.rs
Normal file
566
src/privacy_mode/win_virtual_display.rs
Normal 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(®_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(®_recovery.path, KEY_READ | KEY_WRITE)?;
|
||||
let cur_reg_value = reg_item.get_raw_value(®_recovery.key)?;
|
||||
let new_reg_value = RegValue {
|
||||
bytes: reg_recovery.new.0,
|
||||
vtype: isize_to_reg_type(reg_recovery.new.1),
|
||||
};
|
||||
if cur_reg_value != new_reg_value {
|
||||
return Ok(());
|
||||
}
|
||||
let reg_value = RegValue {
|
||||
bytes: reg_recovery.old.0,
|
||||
vtype: isize_to_reg_type(reg_recovery.old.1),
|
||||
};
|
||||
reg_item.set_raw_value(®_recovery.key, ®_value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn isize_to_reg_type(i: isize) -> RegType {
|
||||
match i {
|
||||
0 => RegType::REG_NONE,
|
||||
1 => RegType::REG_SZ,
|
||||
2 => RegType::REG_EXPAND_SZ,
|
||||
3 => RegType::REG_BINARY,
|
||||
4 => RegType::REG_DWORD,
|
||||
5 => RegType::REG_DWORD_BIG_ENDIAN,
|
||||
6 => RegType::REG_LINK,
|
||||
7 => RegType::REG_MULTI_SZ,
|
||||
8 => RegType::REG_RESOURCE_LIST,
|
||||
9 => RegType::REG_FULL_RESOURCE_DESCRIPTOR,
|
||||
10 => RegType::REG_RESOURCE_REQUIREMENTS_LIST,
|
||||
11 => RegType::REG_QWORD,
|
||||
_ => RegType::REG_NONE,
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -19,8 +19,6 @@ use crate::ipc::{self, Data};
|
||||
use clipboard::ContextSend;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::tokio::sync::mpsc::unbounded_channel;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::Config,
|
||||
@ -34,8 +32,9 @@ use hbb_common::{
|
||||
sync::mpsc::{self, UnboundedSender},
|
||||
task::spawn_blocking,
|
||||
},
|
||||
ResultType,
|
||||
};
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use hbb_common::{tokio::sync::Mutex as TokioMutex, ResultType};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
@ -401,7 +400,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
log::info!("cm ipc connection disconnect");
|
||||
break;
|
||||
}
|
||||
Data::PrivacyModeState((_id, _)) => {
|
||||
Data::PrivacyModeState((_id, _, _)) => {
|
||||
#[cfg(windows)]
|
||||
cm_inner_send(_id, data);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -317,6 +317,18 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_privacy_mode(&self, impl_key: String, on: bool) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_toggle_privacy_mode(TogglePrivacyMode {
|
||||
impl_key,
|
||||
on,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
pub fn get_toggle_option(&self, name: String) -> bool {
|
||||
self.lc.read().unwrap().get_toggle_option(&name)
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user