mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-06-07 18:02:48 +08:00
feat/virtual_display_privacy_mode
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
d64afdcff1
commit
90ac8b7b0b
@ -959,6 +959,7 @@ class CustomAlertDialog extends StatelessWidget {
|
|||||||
void msgBox(SessionID sessionId, String type, String title, String text,
|
void msgBox(SessionID sessionId, String type, String title, String text,
|
||||||
String link, OverlayDialogManager dialogManager,
|
String link, OverlayDialogManager dialogManager,
|
||||||
{bool? hasCancel, ReconnectHandle? reconnect, int? reconnectTimeout}) {
|
{bool? hasCancel, ReconnectHandle? reconnect, int? reconnectTimeout}) {
|
||||||
|
|
||||||
dialogManager.dismissAll();
|
dialogManager.dismissAll();
|
||||||
List<Widget> buttons = [];
|
List<Widget> buttons = [];
|
||||||
bool hasOk = false;
|
bool hasOk = false;
|
||||||
|
@ -10,7 +10,7 @@ class PrivacyModeState {
|
|||||||
static void init(String id) {
|
static void init(String id) {
|
||||||
final key = tag(id);
|
final key = tag(id);
|
||||||
if (!Get.isRegistered(tag: key)) {
|
if (!Get.isRegistered(tag: key)) {
|
||||||
final RxBool state = false.obs;
|
final RxString state = ''.obs;
|
||||||
Get.put(state, tag: key);
|
Get.put(state, tag: key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,11 +20,11 @@ class PrivacyModeState {
|
|||||||
if (Get.isRegistered(tag: key)) {
|
if (Get.isRegistered(tag: key)) {
|
||||||
Get.delete(tag: key);
|
Get.delete(tag: key);
|
||||||
} else {
|
} 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 {
|
class BlockInputState {
|
||||||
|
@ -482,22 +482,13 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
child: Text(translate('Lock after session end'))));
|
child: Text(translate('Lock after session end'))));
|
||||||
}
|
}
|
||||||
// privacy mode
|
// privacy mode
|
||||||
if (ffiModel.keyboard && pi.features.privacyMode) {
|
if (!isDesktop && ffiModel.keyboard && pi.features.privacyMode) {
|
||||||
final option = 'privacy-mode';
|
final privacyModeState = PrivacyModeState.find(id);
|
||||||
final rxValue = PrivacyModeState.find(id);
|
final privacyModeList =
|
||||||
v.add(TToggleMenu(
|
toolbarPrivacyMode(privacyModeState, context, id, ffi);
|
||||||
value: rxValue.value,
|
if (privacyModeList.isNotEmpty) {
|
||||||
onChanged: (value) {
|
v.addAll(privacyModeList);
|
||||||
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
|
// swap key
|
||||||
if (ffiModel.keyboard &&
|
if (ffiModel.keyboard &&
|
||||||
@ -517,7 +508,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
|
|
||||||
if (useTextureRender &&
|
if (useTextureRender &&
|
||||||
pi.isSupportMultiDisplay &&
|
pi.isSupportMultiDisplay &&
|
||||||
PrivacyModeState.find(id).isFalse &&
|
PrivacyModeState.find(id).isEmpty &&
|
||||||
pi.displaysCount.value > 1 &&
|
pi.displaysCount.value > 1 &&
|
||||||
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
|
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
|
||||||
final value =
|
final value =
|
||||||
@ -567,3 +558,73 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var togglePrivacyModeTime = DateTime.now().subtract(const Duration(hours: 1));
|
||||||
|
|
||||||
|
List<TToggleMenu> toolbarPrivacyMode(
|
||||||
|
RxString privacyModeState, BuildContext context, String id, FFI ffi) {
|
||||||
|
final ffiModel = ffi.ffiModel;
|
||||||
|
final pi = ffiModel.pi;
|
||||||
|
final sessionId = ffi.sessionId;
|
||||||
|
|
||||||
|
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc) {
|
||||||
|
return TToggleMenu(
|
||||||
|
value: privacyModeState.isNotEmpty,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
if (ffiModel.pi.currentDisplay != 0 &&
|
||||||
|
ffiModel.pi.currentDisplay != kAllDisplayValue) {
|
||||||
|
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
|
||||||
|
'Please switch to Display 1 first', '', ffi.dialogManager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final option = 'privacy-mode';
|
||||||
|
toggleFunc(sessionId, option);
|
||||||
|
},
|
||||||
|
child: Text(translate('Privacy mode')));
|
||||||
|
}
|
||||||
|
|
||||||
|
final privacyModeImpls =
|
||||||
|
pi.platformAdditions[kPlatformAdditionsSupportedPrivacyModeImpl]
|
||||||
|
as List<dynamic>?;
|
||||||
|
if (privacyModeImpls == null) {
|
||||||
|
return [
|
||||||
|
getDefaultMenu((sid, opt) async {
|
||||||
|
bind.sessionToggleOption(sessionId: sid, value: opt);
|
||||||
|
togglePrivacyModeTime = DateTime.now();
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (privacyModeImpls.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privacyModeImpls.length == 1) {
|
||||||
|
final implKey = (privacyModeImpls[0] as List<dynamic>)[0] as String;
|
||||||
|
return [
|
||||||
|
getDefaultMenu((sid, opt) async {
|
||||||
|
bind.sessionTogglePrivacyMode(sessionId: sid, implKey: implKey);
|
||||||
|
togglePrivacyModeTime = DateTime.now();
|
||||||
|
})
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return privacyModeImpls.map((e) {
|
||||||
|
final implKey = (e as List<dynamic>)[0] as String;
|
||||||
|
final implName = (e)[1] as String;
|
||||||
|
return TToggleMenu(
|
||||||
|
child: Text(isDesktop
|
||||||
|
? translate(implName)
|
||||||
|
: '${translate('Privacy mode')} - ${translate(implName)}'),
|
||||||
|
value: privacyModeState.value == implKey,
|
||||||
|
onChanged:
|
||||||
|
(privacyModeState.isEmpty || privacyModeState.value == implKey)
|
||||||
|
? (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
togglePrivacyModeTime = DateTime.now();
|
||||||
|
bind.sessionTogglePrivacyMode(
|
||||||
|
sessionId: sessionId, implKey: implKey);
|
||||||
|
}
|
||||||
|
: null);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ const String kPlatformAdditionsHeadless = "headless";
|
|||||||
const String kPlatformAdditionsIsInstalled = "is_installed";
|
const String kPlatformAdditionsIsInstalled = "is_installed";
|
||||||
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
|
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
|
||||||
const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
|
const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
|
||||||
|
const String kPlatformAdditionsSupportedPrivacyModeImpl = "supported_privacy_mode_impl";
|
||||||
|
|
||||||
const String kPeerPlatformWindows = "Windows";
|
const String kPeerPlatformWindows = "Windows";
|
||||||
const String kPeerPlatformLinux = "Linux";
|
const String kPeerPlatformLinux = "Linux";
|
||||||
|
@ -1060,7 +1060,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
|||||||
tmpWrapper() {
|
tmpWrapper() {
|
||||||
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
|
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
|
||||||
Map<String, dynamic> oldOptions =
|
Map<String, dynamic> oldOptions =
|
||||||
jsonDecode(bind.mainGetOptionsSync() as String);
|
jsonDecode(bind.mainGetOptionsSync());
|
||||||
old(String key) {
|
old(String key) {
|
||||||
return (oldOptions[key] ?? '').trim();
|
return (oldOptions[key] ?? '').trim();
|
||||||
}
|
}
|
||||||
@ -1151,6 +1151,7 @@ class _DisplayState extends State<_Display> {
|
|||||||
scrollStyle(context),
|
scrollStyle(context),
|
||||||
imageQuality(context),
|
imageQuality(context),
|
||||||
codec(context),
|
codec(context),
|
||||||
|
privacyModeImpl(context),
|
||||||
other(context),
|
other(context),
|
||||||
]).marginOnly(bottom: _kListViewBottomMargin));
|
]).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) {
|
Widget otherRow(String label, String key) {
|
||||||
final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
|
final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
|
||||||
onChanged(bool b) async {
|
onChanged(bool b) async {
|
||||||
|
@ -17,6 +17,7 @@ import '../../common/widgets/overlay.dart';
|
|||||||
import '../../common/widgets/remote_input.dart';
|
import '../../common/widgets/remote_input.dart';
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
|
import '../../common/widgets/toolbar.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/desktop_render_texture.dart';
|
import '../../models/desktop_render_texture.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
@ -292,9 +293,8 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}(),
|
}(),
|
||||||
// Use Overlay to enable rebuild every time on menu button click.
|
// Use Overlay to enable rebuild every time on menu button click.
|
||||||
_ffi.ffiModel.pi.isSet.isTrue
|
_ffi.ffiModel.pi.isSet.isTrue
|
||||||
? Overlay(initialEntries: [
|
? Overlay(
|
||||||
OverlayEntry(builder: remoteToolbar)
|
initialEntries: [OverlayEntry(builder: remoteToolbar)])
|
||||||
])
|
|
||||||
: remoteToolbar(context),
|
: remoteToolbar(context),
|
||||||
_ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(),
|
_ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(),
|
||||||
],
|
],
|
||||||
@ -309,12 +309,17 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
final imageReady = _ffi.ffiModel.pi.isSet.isTrue &&
|
final imageReady = _ffi.ffiModel.pi.isSet.isTrue &&
|
||||||
_ffi.ffiModel.waitForFirstImage.isFalse;
|
_ffi.ffiModel.waitForFirstImage.isFalse;
|
||||||
if (imageReady) {
|
if (imageReady) {
|
||||||
|
// 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.
|
// `dismissAll()` is to ensure that the state is clean.
|
||||||
// It's ok to call dismissAll() here.
|
// It's ok to call dismissAll() here.
|
||||||
_ffi.dialogManager.dismissAll();
|
_ffi.dialogManager.dismissAll();
|
||||||
// Recreate the block state to refresh the state.
|
// Recreate the block state to refresh the state.
|
||||||
_blockableOverlayState = BlockableOverlayState();
|
_blockableOverlayState = BlockableOverlayState();
|
||||||
_blockableOverlayState.applyFfi(_ffi);
|
_blockableOverlayState.applyFfi(_ffi);
|
||||||
|
}
|
||||||
// Block the whole `bodyWidget()` when dialog shows.
|
// Block the whole `bodyWidget()` when dialog shows.
|
||||||
return BlockableOverlay(
|
return BlockableOverlay(
|
||||||
underlying: bodyWidget(),
|
underlying: bodyWidget(),
|
||||||
|
@ -468,7 +468,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toolbarItems.add(Obx(() {
|
toolbarItems.add(Obx(() {
|
||||||
if (PrivacyModeState.find(widget.id).isFalse &&
|
if (PrivacyModeState.find(widget.id).isEmpty &&
|
||||||
pi.displaysCount.value > 1) {
|
pi.displaysCount.value > 1) {
|
||||||
return _MonitorMenu(
|
return _MonitorMenu(
|
||||||
id: widget.id,
|
id: widget.id,
|
||||||
@ -1034,13 +1034,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_screenAdjustor.updateScreen();
|
_screenAdjustor.updateScreen();
|
||||||
return _IconSubmenuButton(
|
|
||||||
tooltip: 'Display Settings',
|
final menuChildren = <Widget>[
|
||||||
svg: "assets/display.svg",
|
|
||||||
ffi: widget.ffi,
|
|
||||||
color: _ToolbarTheme.blueColor,
|
|
||||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
|
||||||
menuChildren: [
|
|
||||||
_screenAdjustor.adjustWindow(context),
|
_screenAdjustor.adjustWindow(context),
|
||||||
viewStyle(),
|
viewStyle(),
|
||||||
scrollStyle(),
|
scrollStyle(),
|
||||||
@ -1057,9 +1052,47 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
toggles(),
|
toggles(),
|
||||||
widget.pluginItem,
|
];
|
||||||
|
// 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(),
|
||||||
|
_SubmenuButton(
|
||||||
|
ffi: widget.ffi,
|
||||||
|
child: Text(translate('Privacy Mode')),
|
||||||
|
menuChildren: privacyModeList
|
||||||
|
.map((e) => Obx(() => CkbMenuButton(
|
||||||
|
value: e.value,
|
||||||
|
onChanged: (privacyModeState.isEmpty || e.value)
|
||||||
|
? e.onChanged
|
||||||
|
: null,
|
||||||
|
child: e.child,
|
||||||
|
ffi: ffi)))
|
||||||
|
.toList()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
menuChildren.add(widget.pluginItem);
|
||||||
|
|
||||||
|
return _IconSubmenuButton(
|
||||||
|
tooltip: 'Display Settings',
|
||||||
|
svg: "assets/display.svg",
|
||||||
|
ffi: widget.ffi,
|
||||||
|
color: _ToolbarTheme.blueColor,
|
||||||
|
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||||
|
menuChildren: menuChildren,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
viewStyle() {
|
viewStyle() {
|
||||||
return futureBuilder(
|
return futureBuilder(
|
||||||
@ -1495,24 +1528,31 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
|
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
|
||||||
|
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||||
|
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
for (var i = 0; i < kMaxVirtualDisplayCount; i++) {
|
for (var i = 0; i < kMaxVirtualDisplayCount; i++) {
|
||||||
children.add(CkbMenuButton(
|
children.add(Obx(() => CkbMenuButton(
|
||||||
value: virtualDisplays.contains(i + 1),
|
value: virtualDisplays.contains(i + 1),
|
||||||
onChanged: (bool? value) async {
|
onChanged: privacyModeState.isNotEmpty
|
||||||
|
? null
|
||||||
|
: (bool? value) async {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
bind.sessionToggleVirtualDisplay(
|
bind.sessionToggleVirtualDisplay(
|
||||||
sessionId: widget.ffi.sessionId, index: i + 1, on: value);
|
sessionId: widget.ffi.sessionId,
|
||||||
|
index: i + 1,
|
||||||
|
on: value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text('${translate('Virtual display')} ${i + 1}'),
|
child: Text('${translate('Virtual display')} ${i + 1}'),
|
||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
children.add(Divider());
|
children.add(Divider());
|
||||||
children.add(MenuButton(
|
children.add(Obx(() => MenuButton(
|
||||||
onPressed: () {
|
onPressed: privacyModeState.isNotEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
bind.sessionToggleVirtualDisplay(
|
bind.sessionToggleVirtualDisplay(
|
||||||
sessionId: widget.ffi.sessionId,
|
sessionId: widget.ffi.sessionId,
|
||||||
index: kAllVirtualDisplay,
|
index: kAllVirtualDisplay,
|
||||||
@ -1520,7 +1560,7 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
|||||||
},
|
},
|
||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
child: Text(translate('Plug out all')),
|
child: Text(translate('Plug out all')),
|
||||||
));
|
)));
|
||||||
return _SubmenuButton(
|
return _SubmenuButton(
|
||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
menuChildren: children,
|
menuChildren: children,
|
||||||
|
@ -967,11 +967,21 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatePrivacyMode(
|
updatePrivacyMode(
|
||||||
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
PrivacyModeState.find(peerId).value = bind.sessionGetToggleOptionSync(
|
final isOn = bind.sessionGetToggleOptionSync(
|
||||||
sessionId: sessionId, arg: 'privacy-mode');
|
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) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -518,6 +518,11 @@ message ToggleVirtualDisplay {
|
|||||||
bool on = 2;
|
bool on = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message TogglePrivacyMode {
|
||||||
|
string impl_key = 1;
|
||||||
|
bool on = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message PermissionInfo {
|
message PermissionInfo {
|
||||||
enum Permission {
|
enum Permission {
|
||||||
Keyboard = 0;
|
Keyboard = 0;
|
||||||
@ -656,6 +661,8 @@ message BackNotification {
|
|||||||
}
|
}
|
||||||
// Supplementary message, for "PrvOnFailed" and "PrvOffFailed"
|
// Supplementary message, for "PrvOnFailed" and "PrvOffFailed"
|
||||||
string details = 3;
|
string details = 3;
|
||||||
|
// The key of the implementation
|
||||||
|
string impl_key = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ElevationRequestWithLogon {
|
message ElevationRequestWithLogon {
|
||||||
@ -721,6 +728,7 @@ message Misc {
|
|||||||
CaptureDisplays capture_displays = 30;
|
CaptureDisplays capture_displays = 30;
|
||||||
int32 refresh_video_display = 31;
|
int32 refresh_video_display = 31;
|
||||||
ToggleVirtualDisplay toggle_virtual_display = 32;
|
ToggleVirtualDisplay toggle_virtual_display = 32;
|
||||||
|
TogglePrivacyMode toggle_privacy_mode = 33;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1552,10 +1552,12 @@ impl LoginConfigHandler {
|
|||||||
}
|
}
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut msg = OptionMessage::new();
|
let mut msg = OptionMessage::new();
|
||||||
|
if self.version < hbb_common::get_version_number("1.2.4") {
|
||||||
if self.get_toggle_option("privacy-mode") {
|
if self.get_toggle_option("privacy-mode") {
|
||||||
msg.privacy_mode = BoolOption::Yes.into();
|
msg.privacy_mode = BoolOption::Yes.into();
|
||||||
n += 1;
|
n += 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
Some(msg)
|
Some(msg)
|
||||||
} else {
|
} else {
|
||||||
|
@ -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 {
|
fn contains_key_frame(vf: &VideoFrame) -> bool {
|
||||||
use video_frame::Union::*;
|
use video_frame::Union::*;
|
||||||
match &vf.union {
|
match &vf.union {
|
||||||
@ -1026,6 +1044,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
self.handler.close_success();
|
self.handler.close_success();
|
||||||
self.handler.adapt_size();
|
self.handler.adapt_size();
|
||||||
self.send_opts_after_login(peer).await;
|
self.send_opts_after_login(peer).await;
|
||||||
|
self.send_toggle_privacy_mode_msg(peer).await;
|
||||||
}
|
}
|
||||||
let incoming_format = CodecFormat::from(&vf);
|
let incoming_format = CodecFormat::from(&vf);
|
||||||
if self.video_format != incoming_format {
|
if self.video_format != incoming_format {
|
||||||
@ -1557,6 +1576,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
.handle_back_msg_privacy_mode(
|
.handle_back_msg_privacy_mode(
|
||||||
state.enum_value_or(back_notification::PrivacyModeState::PrvStateUnknown),
|
state.enum_value_or(back_notification::PrivacyModeState::PrvStateUnknown),
|
||||||
notification.details,
|
notification.details,
|
||||||
|
notification.impl_key,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@ -1615,9 +1635,20 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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();
|
let mut config = self.handler.load_config();
|
||||||
config.privacy_mode.v = on;
|
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.save_config(config);
|
||||||
|
|
||||||
self.handler.update_privacy_mode();
|
self.handler.update_privacy_mode();
|
||||||
@ -1627,6 +1658,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
state: back_notification::PrivacyModeState,
|
state: back_notification::PrivacyModeState,
|
||||||
details: String,
|
details: String,
|
||||||
|
impl_key: String,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match state {
|
match state {
|
||||||
back_notification::PrivacyModeState::PrvOnByOther => {
|
back_notification::PrivacyModeState::PrvOnByOther => {
|
||||||
@ -1641,22 +1673,22 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
back_notification::PrivacyModeState::PrvNotSupported => {
|
back_notification::PrivacyModeState::PrvNotSupported => {
|
||||||
self.handler
|
self.handler
|
||||||
.msgbox("custom-error", "Privacy mode", "Unsupported", "");
|
.msgbox("custom-error", "Privacy mode", "Unsupported", "");
|
||||||
self.update_privacy_mode(false);
|
self.update_privacy_mode(impl_key, false);
|
||||||
}
|
}
|
||||||
back_notification::PrivacyModeState::PrvOnSucceeded => {
|
back_notification::PrivacyModeState::PrvOnSucceeded => {
|
||||||
self.handler
|
self.handler
|
||||||
.msgbox("custom-nocancel", "Privacy mode", "In privacy mode", "");
|
.msgbox("custom-nocancel", "Privacy mode", "Enter privacy mode", "");
|
||||||
self.update_privacy_mode(true);
|
self.update_privacy_mode(impl_key, true);
|
||||||
}
|
}
|
||||||
back_notification::PrivacyModeState::PrvOnFailedDenied => {
|
back_notification::PrivacyModeState::PrvOnFailedDenied => {
|
||||||
self.handler
|
self.handler
|
||||||
.msgbox("custom-error", "Privacy mode", "Peer denied", "");
|
.msgbox("custom-error", "Privacy mode", "Peer denied", "");
|
||||||
self.update_privacy_mode(false);
|
self.update_privacy_mode(impl_key, false);
|
||||||
}
|
}
|
||||||
back_notification::PrivacyModeState::PrvOnFailedPlugin => {
|
back_notification::PrivacyModeState::PrvOnFailedPlugin => {
|
||||||
self.handler
|
self.handler
|
||||||
.msgbox("custom-error", "Privacy mode", "Please install plugins", "");
|
.msgbox("custom-error", "Privacy mode", "Please install plugins", "");
|
||||||
self.update_privacy_mode(false);
|
self.update_privacy_mode(impl_key, false);
|
||||||
}
|
}
|
||||||
back_notification::PrivacyModeState::PrvOnFailed => {
|
back_notification::PrivacyModeState::PrvOnFailed => {
|
||||||
self.handler.msgbox(
|
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 => {
|
back_notification::PrivacyModeState::PrvOffSucceeded => {
|
||||||
self.handler
|
self.handler
|
||||||
.msgbox("custom-nocancel", "Privacy mode", "Out privacy mode", "");
|
.msgbox("custom-nocancel", "Privacy mode", "Exit privacy mode", "");
|
||||||
self.update_privacy_mode(false);
|
self.update_privacy_mode(impl_key, false);
|
||||||
}
|
}
|
||||||
back_notification::PrivacyModeState::PrvOffByPeer => {
|
back_notification::PrivacyModeState::PrvOffByPeer => {
|
||||||
self.handler
|
self.handler
|
||||||
.msgbox("custom-error", "Privacy mode", "Peer exit", "");
|
.msgbox("custom-error", "Privacy mode", "Peer exit", "");
|
||||||
self.update_privacy_mode(false);
|
self.update_privacy_mode(impl_key, false);
|
||||||
}
|
}
|
||||||
back_notification::PrivacyModeState::PrvOffFailed => {
|
back_notification::PrivacyModeState::PrvOffFailed => {
|
||||||
self.handler.msgbox(
|
self.handler.msgbox(
|
||||||
@ -1697,7 +1729,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
self.handler
|
self.handler
|
||||||
.msgbox("custom-error", "Privacy mode", "Turned off", "");
|
.msgbox("custom-error", "Privacy mode", "Turned off", "");
|
||||||
// log::error!("Privacy mode is turned off with unknown reason");
|
// 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")]
|
#[cfg(feature = "use_rubato")]
|
||||||
pub fn resample_channels(
|
pub fn resample_channels(
|
||||||
data: &[f32],
|
data: &[f32],
|
||||||
@ -1068,10 +1055,12 @@ pub async fn post_request_sync(url: String, body: String, header: &str) -> Resul
|
|||||||
pub fn make_privacy_mode_msg_with_details(
|
pub fn make_privacy_mode_msg_with_details(
|
||||||
state: back_notification::PrivacyModeState,
|
state: back_notification::PrivacyModeState,
|
||||||
details: String,
|
details: String,
|
||||||
|
impl_key: String,
|
||||||
) -> Message {
|
) -> Message {
|
||||||
let mut misc = Misc::new();
|
let mut misc = Misc::new();
|
||||||
let mut back_notification = BackNotification {
|
let mut back_notification = BackNotification {
|
||||||
details,
|
details,
|
||||||
|
impl_key,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
back_notification.set_privacy_mode_state(state);
|
back_notification.set_privacy_mode_state(state);
|
||||||
@ -1082,8 +1071,8 @@ pub fn make_privacy_mode_msg_with_details(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Message {
|
pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState, impl_key: String) -> Message {
|
||||||
make_privacy_mode_msg_with_details(state, "".to_owned())
|
make_privacy_mode_msg_with_details(state, "".to_owned(), impl_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_keyboard_mode_supported(keyboard_mode: &KeyboardMode, version_number: i64, peer_platform: &str) -> bool {
|
pub fn is_keyboard_mode_supported(keyboard_mode: &KeyboardMode, version_number: i64, peer_platform: &str) -> bool {
|
||||||
|
@ -244,6 +244,8 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||||||
return None;
|
return None;
|
||||||
} else if args[0] == "--server" {
|
} else if args[0] == "--server" {
|
||||||
log::info!("start --server with user {}", crate::username());
|
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"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
{
|
{
|
||||||
crate::start_server(true);
|
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) {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
session.toggle_privacy_mode(impl_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
|
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) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_flutter_option(k))
|
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")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod server_side {
|
pub mod server_side {
|
||||||
use hbb_common::{config, log};
|
use hbb_common::{config, log};
|
||||||
|
12
src/ipc.rs
12
src/ipc.rs
@ -28,7 +28,7 @@ use hbb_common::{
|
|||||||
ResultType,
|
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
|
// State with timestamp, because std::time::Instant cannot be serialized
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@ -207,7 +207,7 @@ pub enum Data {
|
|||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
ClipboardFile(ClipboardFile),
|
ClipboardFile(ClipboardFile),
|
||||||
ClipboardFileEnabled(bool),
|
ClipboardFileEnabled(bool),
|
||||||
PrivacyModeState((i32, PrivacyModeState)),
|
PrivacyModeState((i32, PrivacyModeState, String)),
|
||||||
TestRendezvousServer,
|
TestRendezvousServer,
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
Keyboard(DataKeyboard),
|
Keyboard(DataKeyboard),
|
||||||
@ -352,6 +352,9 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
if EXIT_RECV_CLOSE.load(Ordering::SeqCst) {
|
if EXIT_RECV_CLOSE.load(Ordering::SeqCst) {
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
crate::server::input_service::fix_key_down_timeout_at_exit();
|
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);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -442,6 +445,9 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
}
|
}
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let _chk = CheckIfRestart::new();
|
let _chk = CheckIfRestart::new();
|
||||||
|
if let Some(v) = value.get("privacy-mode-impl-key") {
|
||||||
|
crate::privacy_mode::switch(v);
|
||||||
|
}
|
||||||
Config::set_options(value);
|
Config::set_options(value);
|
||||||
allow_err!(stream.send(&Data::Options(None)).await);
|
allow_err!(stream.send(&Data::Options(None)).await);
|
||||||
}
|
}
|
||||||
@ -603,7 +609,7 @@ async fn check_pid(postfix: &str) {
|
|||||||
file.read_to_string(&mut content).ok();
|
file.read_to_string(&mut content).ok();
|
||||||
let pid = content.parse::<usize>().unwrap_or(0);
|
let pid = content.parse::<usize>().unwrap_or(0);
|
||||||
if pid > 0 {
|
if pid > 0 {
|
||||||
use hbb_common::sysinfo::{System};
|
use hbb_common::sysinfo::System;
|
||||||
let mut sys = System::new();
|
let mut sys = System::new();
|
||||||
sys.refresh_processes();
|
sys.refresh_processes();
|
||||||
if let Some(p) = sys.process(pid.into()) {
|
if let Some(p) = sys.process(pid.into()) {
|
||||||
|
@ -177,7 +177,6 @@ pub mod client {
|
|||||||
Key::MetaRight => Some(ControlKey::RWin),
|
Key::MetaRight => Some(ControlKey::RWin),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event_lock_screen() -> KeyEvent {
|
pub fn event_lock_screen() -> KeyEvent {
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "خروج القرين"),
|
("Peer exit", "خروج القرين"),
|
||||||
("Failed to turn off", "فشل ايقاف التشغيل"),
|
("Failed to turn off", "فشل ايقاف التشغيل"),
|
||||||
("Turned off", "مطفئ"),
|
("Turned off", "مطفئ"),
|
||||||
("In privacy mode", "في وضع الخصوصية"),
|
|
||||||
("Out privacy mode", "الخروج من وضع الخصوصية"),
|
|
||||||
("Language", "اللغة"),
|
("Language", "اللغة"),
|
||||||
("Keep RustDesk background service", "ابق خدمة RustDesk تعمل في الخلفية"),
|
("Keep RustDesk background service", "ابق خدمة RustDesk تعمل في الخلفية"),
|
||||||
("Ignore Battery Optimizations", "تجاهل تحسينات البطارية"),
|
("Ignore Battery Optimizations", "تجاهل تحسينات البطارية"),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "El peer ha sortit"),
|
("Peer exit", "El peer ha sortit"),
|
||||||
("Failed to turn off", "Error en apagar"),
|
("Failed to turn off", "Error en apagar"),
|
||||||
("Turned off", "Apagat"),
|
("Turned off", "Apagat"),
|
||||||
("In privacy mode", "En mode de privacitat"),
|
|
||||||
("Out privacy mode", "Fora del mode de privacitat"),
|
|
||||||
("Language", "Idioma"),
|
("Language", "Idioma"),
|
||||||
("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
|
("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
|
||||||
("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", "退出失败"),
|
("Failed to turn off", "退出失败"),
|
||||||
("Turned off", "退出"),
|
("Turned off", "退出"),
|
||||||
("In privacy mode", "进入隐私模式"),
|
|
||||||
("Out privacy mode", "退出隐私模式"),
|
|
||||||
("Language", "语言"),
|
("Language", "语言"),
|
||||||
("Keep RustDesk background service", "保持 RustDesk 后台服务"),
|
("Keep RustDesk background service", "保持 RustDesk 后台服务"),
|
||||||
("Ignore Battery Optimizations", "忽略电池优化"),
|
("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)"),
|
("True color (4:4:4)", "真彩模式(4:4:4)"),
|
||||||
("Enable blocking user input", "允许阻止用户输入"),
|
("Enable blocking user input", "允许阻止用户输入"),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", "旧的 Windows API"),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", "禁用物理显示器"),
|
||||||
|
("Enter privacy mode", "进入隐私模式"),
|
||||||
|
("Exit privacy mode", "退出隐私模式"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Ukončení protistrany"),
|
("Peer exit", "Ukončení protistrany"),
|
||||||
("Failed to turn off", "Nepodařilo se vypnout"),
|
("Failed to turn off", "Nepodařilo se vypnout"),
|
||||||
("Turned off", "Vypnutý"),
|
("Turned off", "Vypnutý"),
|
||||||
("In privacy mode", "v režimu ochrany soukromí"),
|
|
||||||
("Out privacy mode", "mimo režim ochrany soukromí"),
|
|
||||||
("Language", "Jazyk"),
|
("Language", "Jazyk"),
|
||||||
("Keep RustDesk background service", "Zachovat službu RustDesk na pozadí"),
|
("Keep RustDesk background service", "Zachovat službu RustDesk na pozadí"),
|
||||||
("Ignore Battery Optimizations", "Ignorovat optimalizaci baterie"),
|
("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)"),
|
("True color (4:4:4)", "Skutečné barvy (4:4:4)"),
|
||||||
("Enable blocking user input", "Povolit blokování uživatelského vstupu"),
|
("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."),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Modpart-Afslut"),
|
("Peer exit", "Modpart-Afslut"),
|
||||||
("Failed to turn off", "Mislykkedes i at lukke ned"),
|
("Failed to turn off", "Mislykkedes i at lukke ned"),
|
||||||
("Turned off", "Slukket"),
|
("Turned off", "Slukket"),
|
||||||
("In privacy mode", "I privatlivstilstand"),
|
|
||||||
("Out privacy mode", "Privatlivstilstand fra"),
|
|
||||||
("Language", "Sprog"),
|
("Language", "Sprog"),
|
||||||
("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"),
|
("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"),
|
||||||
("Ignore Battery Optimizations", "Ignorér betteri optimeringer"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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."),
|
("Peer exit", "Die Gegenstelle hat die Verbindung getrennt."),
|
||||||
("Failed to turn off", "Ausschalten fehlgeschlagen"),
|
("Failed to turn off", "Ausschalten fehlgeschlagen"),
|
||||||
("Turned off", "Ausgeschaltet"),
|
("Turned off", "Ausgeschaltet"),
|
||||||
("In privacy mode", "Datenschutzmodus aktivieren"),
|
|
||||||
("Out privacy mode", "Datenschutzmodus deaktivieren"),
|
|
||||||
("Language", "Sprache"),
|
("Language", "Sprache"),
|
||||||
("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
|
("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
|
||||||
("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"),
|
("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)"),
|
("True color (4:4:4)", "True Color (4:4:4)"),
|
||||||
("Enable blocking user input", "Blockieren von Benutzereingaben aktivieren"),
|
("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."),
|
("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();
|
].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", "Αποτυχία απενεργοποίησης"),
|
("Failed to turn off", "Αποτυχία απενεργοποίησης"),
|
||||||
("Turned off", "Απενεργοποιημένο"),
|
("Turned off", "Απενεργοποιημένο"),
|
||||||
("In privacy mode", "Σε λειτουργία απορρήτου"),
|
|
||||||
("Out privacy mode", "Εκτός λειτουργίας απορρήτου"),
|
|
||||||
("Language", "Γλώσσα"),
|
("Language", "Γλώσσα"),
|
||||||
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
|
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
|
||||||
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
|
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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."),
|
("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."),
|
("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"),
|
("id_input_tip", "You can input an ID, a direct IP, or a domain with a port (<domain>:<port>).\nIf you want to access a device on another server, please append the server address (<id>@<server_address>?key=<key_value>), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"<id>@public\", the key is not needed for public server"),
|
||||||
|
("privacy_mode_impl_mag_tip", "Old Windows magnifier API"),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", "Disable physical displays"),
|
||||||
].iter().cloned().collect();
|
].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", ""),
|
("Failed to turn off", ""),
|
||||||
("Turned off", ""),
|
("Turned off", ""),
|
||||||
("In privacy mode", ""),
|
|
||||||
("Out privacy mode", ""),
|
|
||||||
("Language", ""),
|
("Language", ""),
|
||||||
("Keep RustDesk background service", ""),
|
("Keep RustDesk background service", ""),
|
||||||
("Ignore Battery Optimizations", ""),
|
("Ignore Battery Optimizations", ""),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Par salio"),
|
("Peer exit", "Par salio"),
|
||||||
("Failed to turn off", "Error al apagar"),
|
("Failed to turn off", "Error al apagar"),
|
||||||
("Turned off", "Apagado"),
|
("Turned off", "Apagado"),
|
||||||
("In privacy mode", "En modo de privacidad"),
|
|
||||||
("Out privacy mode", "Fuera del modo de privacidad"),
|
|
||||||
("Language", "Idioma"),
|
("Language", "Idioma"),
|
||||||
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
|
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
|
||||||
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
|
("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)"),
|
("True color (4:4:4)", "Color real (4:4:4)"),
|
||||||
("Enable blocking user input", "Habilitar el bloqueo de la entrada del usuario"),
|
("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."),
|
("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();
|
].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", "خاموش کردن انجام نشد"),
|
("Failed to turn off", "خاموش کردن انجام نشد"),
|
||||||
("Turned off", "خاموش شد"),
|
("Turned off", "خاموش شد"),
|
||||||
("In privacy mode", "در حالت حریم خصوصی"),
|
|
||||||
("Out privacy mode", "خارج از حالت حریم خصوصی"),
|
|
||||||
("Language", "زبان"),
|
("Language", "زبان"),
|
||||||
("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"),
|
("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"),
|
||||||
("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"),
|
("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Appareil distant déconnecté"),
|
("Peer exit", "Appareil distant déconnecté"),
|
||||||
("Failed to turn off", "Échec de la désactivation"),
|
("Failed to turn off", "Échec de la désactivation"),
|
||||||
("Turned off", "Désactivé"),
|
("Turned off", "Désactivé"),
|
||||||
("In privacy mode", "en mode privé"),
|
|
||||||
("Out privacy mode", "hors mode de confidentialité"),
|
|
||||||
("Language", "Langue"),
|
("Language", "Langue"),
|
||||||
("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
|
("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
|
||||||
("Ignore Battery Optimizations", "Ignorer les optimisations batterie"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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"),
|
("Peer exit", "A távoli fél kilépett"),
|
||||||
("Failed to turn off", "Nem sikerült kikapcsolni"),
|
("Failed to turn off", "Nem sikerült kikapcsolni"),
|
||||||
("Turned off", "Kikapcsolva"),
|
("Turned off", "Kikapcsolva"),
|
||||||
("In privacy mode", "Belépés inkognitó módba"),
|
|
||||||
("Out privacy mode", "Kilépés inkognitó módból"),
|
|
||||||
("Language", "Nyelv"),
|
("Language", "Nyelv"),
|
||||||
("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
|
("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
|
||||||
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Rekan keluar"),
|
("Peer exit", "Rekan keluar"),
|
||||||
("Failed to turn off", "Gagal mematikan"),
|
("Failed to turn off", "Gagal mematikan"),
|
||||||
("Turned off", "Dimatikan"),
|
("Turned off", "Dimatikan"),
|
||||||
("In privacy mode", "Dalam mode privasi"),
|
|
||||||
("Out privacy mode", "Keluar dari mode privasi"),
|
|
||||||
("Language", "Bahasa"),
|
("Language", "Bahasa"),
|
||||||
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada service background"),
|
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada service background"),
|
||||||
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", "Aktifkan pemblokiran input pengguna"),
|
("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"),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Uscita dal dispostivo remoto"),
|
("Peer exit", "Uscita dal dispostivo remoto"),
|
||||||
("Failed to turn off", "Impossibile spegnere"),
|
("Failed to turn off", "Impossibile spegnere"),
|
||||||
("Turned off", "Spegni"),
|
("Turned off", "Spegni"),
|
||||||
("In privacy mode", "In modalità privacy"),
|
|
||||||
("Out privacy mode", "Uscita dalla modalità privacy"),
|
|
||||||
("Language", "Lingua"),
|
("Language", "Lingua"),
|
||||||
("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
|
("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
|
||||||
("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"),
|
("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)"),
|
("True color (4:4:4)", "Colore reale (4:4:4)"),
|
||||||
("Enable blocking user input", "Abilita blocco input utente"),
|
("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"),
|
("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();
|
].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", "オフにできませんでした"),
|
("Failed to turn off", "オフにできませんでした"),
|
||||||
("Turned off", "オフになりました"),
|
("Turned off", "オフになりました"),
|
||||||
("In privacy mode", "プライバシーモード開始"),
|
|
||||||
("Out privacy mode", "プライバシーモード終了"),
|
|
||||||
("Language", "言語"),
|
("Language", "言語"),
|
||||||
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
|
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
|
||||||
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
|
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", "종료에 실패함"),
|
("Failed to turn off", "종료에 실패함"),
|
||||||
("Turned off", "종료됨"),
|
("Turned off", "종료됨"),
|
||||||
("In privacy mode", "개인정보 보호 모드 진입"),
|
|
||||||
("Out privacy mode", "개인정보 보호 모드 나감"),
|
|
||||||
("Language", "언어"),
|
("Language", "언어"),
|
||||||
("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"),
|
("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"),
|
||||||
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
|
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
|
||||||
@ -569,8 +567,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Plug out all", "모두 플러그 아웃"),
|
("Plug out all", "모두 플러그 아웃"),
|
||||||
("True color (4:4:4)", "트루컬러(4:4:4)"),
|
("True color (4:4:4)", "트루컬러(4:4:4)"),
|
||||||
("Enable blocking user input", "사용자 입력 차단 허용"),
|
("Enable blocking user input", "사용자 입력 차단 허용"),
|
||||||
("id_input_tip", "입력된 ID, IP, 도메인과 포트(<domain>:<port>)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(<id>@<server_address>?key=<key_value>)를 추가하세요.
|
("id_input_tip", "입력된 ID, IP, 도메인과 포트(<domain>:<port>)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(<id>@<server_address>?key=<key_value>)를 추가하세요"),
|
||||||
예:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\n
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
공개 서버의 장치에 액세스하려면 \"<id>@public\",을 입력하세요. 공개 서버에는 키가 필요하지 않습니다"),
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", "Сөндіру сәтсіз болды"),
|
("Failed to turn off", "Сөндіру сәтсіз болды"),
|
||||||
("Turned off", "Өшірілген"),
|
("Turned off", "Өшірілген"),
|
||||||
("In privacy mode", "Құпиялылық модасында"),
|
|
||||||
("Out privacy mode", "Құпиялылық модасынан Шығу"),
|
|
||||||
("Language", "Тіл"),
|
("Language", "Тіл"),
|
||||||
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
|
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
|
||||||
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
|
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Nuotolinis mazgas neveikia"),
|
("Peer exit", "Nuotolinis mazgas neveikia"),
|
||||||
("Failed to turn off", "Nepavyko išjungti"),
|
("Failed to turn off", "Nepavyko išjungti"),
|
||||||
("Turned off", "Išjungti"),
|
("Turned off", "Išjungti"),
|
||||||
("In privacy mode", "Privatumo režimas"),
|
|
||||||
("Out privacy mode", "Išėjimas iš privatumo režimo"),
|
|
||||||
("Language", "Kalba"),
|
("Language", "Kalba"),
|
||||||
("Keep RustDesk background service", "Palikti RustDesk fonine paslauga"),
|
("Keep RustDesk background service", "Palikti RustDesk fonine paslauga"),
|
||||||
("Ignore Battery Optimizations", "Ignoruoti akumuliatoriaus optimizavimą"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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"),
|
("Peer exit", "Iziet no attālās ierīces"),
|
||||||
("Failed to turn off", "Neizdevās izslēgt"),
|
("Failed to turn off", "Neizdevās izslēgt"),
|
||||||
("Turned off", "Izslēgts"),
|
("Turned off", "Izslēgts"),
|
||||||
("In privacy mode", "Privātuma režīmā"),
|
|
||||||
("Out privacy mode", "Izslēgts privātuma režīms"),
|
|
||||||
("Language", "Valoda"),
|
("Language", "Valoda"),
|
||||||
("Keep RustDesk background service", "Saglabāt RustDesk fona servisu"),
|
("Keep RustDesk background service", "Saglabāt RustDesk fona servisu"),
|
||||||
("Ignore Battery Optimizations", "Ignorēt akumulatora optimizāciju"),
|
("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)"),
|
("True color (4:4:4)", "Īstā krāsa (4:4:4)"),
|
||||||
("Enable blocking user input", "Iespējot lietotāja ievades bloķēšanu"),
|
("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"),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Peer afgesloten"),
|
("Peer exit", "Peer afgesloten"),
|
||||||
("Failed to turn off", "Uitschakelen mislukt"),
|
("Failed to turn off", "Uitschakelen mislukt"),
|
||||||
("Turned off", "Uitgeschakeld"),
|
("Turned off", "Uitgeschakeld"),
|
||||||
("In privacy mode", "In privacymodus"),
|
|
||||||
("Out privacy mode", "Uit privacymodus"),
|
|
||||||
("Language", "Taal"),
|
("Language", "Taal"),
|
||||||
("Keep RustDesk background service", "RustDesk achtergronddienst behouden"),
|
("Keep RustDesk background service", "RustDesk achtergronddienst behouden"),
|
||||||
("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"),
|
("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)"),
|
("True color (4:4:4)", "Ware kleur (4:4:4)"),
|
||||||
("Enable blocking user input", "Blokkeren van gebruikersinvoer inschakelen"),
|
("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."),
|
("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();
|
].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"),
|
("Peer exit", "Wyjście ze zdalnego urządzenia"),
|
||||||
("Failed to turn off", "Nie udało się wyłączyć"),
|
("Failed to turn off", "Nie udało się wyłączyć"),
|
||||||
("Turned off", "Wyłączony"),
|
("Turned off", "Wyłączony"),
|
||||||
("In privacy mode", "Uruchom tryb prywatności"),
|
|
||||||
("Out privacy mode", "Opuść tryb prywatności"),
|
|
||||||
("Language", "Język"),
|
("Language", "Język"),
|
||||||
("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"),
|
("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"),
|
||||||
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
|
("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)"),
|
("True color (4:4:4)", "True color (4:4:4)"),
|
||||||
("Enable blocking user input", "Zablokuj wprowadzanie danych przez użytkownika"),
|
("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."),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Saída do Remoto"),
|
("Peer exit", "Saída do Remoto"),
|
||||||
("Failed to turn off", "Falha ao desligar"),
|
("Failed to turn off", "Falha ao desligar"),
|
||||||
("Turned off", "Desligado"),
|
("Turned off", "Desligado"),
|
||||||
("In privacy mode", "Em modo de privacidade"),
|
|
||||||
("Out privacy mode", "Sair do modo de privacidade"),
|
|
||||||
("Language", "Linguagem"),
|
("Language", "Linguagem"),
|
||||||
("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"),
|
("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"),
|
||||||
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Parceiro saiu"),
|
("Peer exit", "Parceiro saiu"),
|
||||||
("Failed to turn off", "Falha ao desligar"),
|
("Failed to turn off", "Falha ao desligar"),
|
||||||
("Turned off", "Desligado"),
|
("Turned off", "Desligado"),
|
||||||
("In privacy mode", "No modo de privacidade"),
|
|
||||||
("Out privacy mode", "Fora do modo de privacidade"),
|
|
||||||
("Language", "Idioma"),
|
("Language", "Idioma"),
|
||||||
("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
|
("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
|
||||||
("Ignore Battery Optimizations", "Ignorar otimizações de bateria"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Ieșire dispozitiv pereche"),
|
("Peer exit", "Ieșire dispozitiv pereche"),
|
||||||
("Failed to turn off", "Dezactivare nereușită"),
|
("Failed to turn off", "Dezactivare nereușită"),
|
||||||
("Turned off", "Închis"),
|
("Turned off", "Închis"),
|
||||||
("In privacy mode", "În modul privat"),
|
|
||||||
("Out privacy mode", "Ieșit din modul privat"),
|
|
||||||
("Language", "Limbă"),
|
("Language", "Limbă"),
|
||||||
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
|
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
|
||||||
("Ignore Battery Optimizations", "Ignoră optimizările de baterie"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", "Невозможно отключить"),
|
("Failed to turn off", "Невозможно отключить"),
|
||||||
("Turned off", "Отключён"),
|
("Turned off", "Отключён"),
|
||||||
("In privacy mode", "В режиме конфиденциальности"),
|
|
||||||
("Out privacy mode", "Выход из режима конфиденциальности"),
|
|
||||||
("Language", "Язык"),
|
("Language", "Язык"),
|
||||||
("Keep RustDesk background service", "Держать в фоне службу RustDesk"),
|
("Keep RustDesk background service", "Держать в фоне службу RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
|
("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)"),
|
("True color (4:4:4)", "Истинный цвет (4:4:4)"),
|
||||||
("Enable blocking user input", "Блокировать ввод пользователя"),
|
("Enable blocking user input", "Блокировать ввод пользователя"),
|
||||||
("id_input_tip", "Можно ввести идентификатор, прямой IP-адрес или домен с портом (<домен>:<порт>).\nЕсли необходимо получить доступ к устройству на другом сервере, добавьте адрес сервера (<id>@<адрес_сервера>?key=<ключ_значение>), например:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли необходимо получить доступ к устройству на общедоступном сервере, введите \"<id>@public\", ключ для публичного сервера не требуется."),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Peer exit"),
|
("Peer exit", "Peer exit"),
|
||||||
("Failed to turn off", "Nepodarilo sa vypnúť"),
|
("Failed to turn off", "Nepodarilo sa vypnúť"),
|
||||||
("Turned off", "Vypnutý"),
|
("Turned off", "Vypnutý"),
|
||||||
("In privacy mode", "V režime súkromia"),
|
|
||||||
("Out privacy mode", "Mimo režimu súkromia"),
|
|
||||||
("Language", "Jazyk"),
|
("Language", "Jazyk"),
|
||||||
("Keep RustDesk background service", "Ponechať službu RustDesk na pozadí"),
|
("Keep RustDesk background service", "Ponechať službu RustDesk na pozadí"),
|
||||||
("Ignore Battery Optimizations", "Ignorovať optimalizácie batérie"),
|
("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)"),
|
("True color (4:4:4)", "Skutočná farba (4:4:4)"),
|
||||||
("Enable blocking user input", "Povoliť blokovanie vstupu od používateľa"),
|
("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."),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Odjemalec se je zaprl"),
|
("Peer exit", "Odjemalec se je zaprl"),
|
||||||
("Failed to turn off", "Ni bilo mogoče izklopiti"),
|
("Failed to turn off", "Ni bilo mogoče izklopiti"),
|
||||||
("Turned off", "Izklopljeno"),
|
("Turned off", "Izklopljeno"),
|
||||||
("In privacy mode", "V zasebnem načinu"),
|
|
||||||
("Out privacy mode", "Iz zasebnega načina"),
|
|
||||||
("Language", "Jezik"),
|
("Language", "Jezik"),
|
||||||
("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"),
|
("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"),
|
||||||
("Ignore Battery Optimizations", "Prezri optimizacije baterije"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Dalje peer"),
|
("Peer exit", "Dalje peer"),
|
||||||
("Failed to turn off", "Dështoi të fiket"),
|
("Failed to turn off", "Dështoi të fiket"),
|
||||||
("Turned off", "I fikur"),
|
("Turned off", "I fikur"),
|
||||||
("In privacy mode", "Në modalitetin e privatësisë"),
|
|
||||||
("Out privacy mode", "Jashtë modaliteti i privatësisë"),
|
|
||||||
("Language", "Gjuha"),
|
("Language", "Gjuha"),
|
||||||
("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"),
|
("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Klijent izašao"),
|
("Peer exit", "Klijent izašao"),
|
||||||
("Failed to turn off", "Greška kod isključenja"),
|
("Failed to turn off", "Greška kod isključenja"),
|
||||||
("Turned off", "Isključeno"),
|
("Turned off", "Isključeno"),
|
||||||
("In privacy mode", "U modu privatnosti"),
|
|
||||||
("Out privacy mode", "Van moda privatnosti"),
|
|
||||||
("Language", "Jezik"),
|
("Language", "Jezik"),
|
||||||
("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"),
|
("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"),
|
||||||
("Ignore Battery Optimizations", "Zanemari optimizacije baterije"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "Avsluta klient"),
|
("Peer exit", "Avsluta klient"),
|
||||||
("Failed to turn off", "Misslyckades med avstängning"),
|
("Failed to turn off", "Misslyckades med avstängning"),
|
||||||
("Turned off", "Avstängd"),
|
("Turned off", "Avstängd"),
|
||||||
("In privacy mode", "I säkerhetsläge"),
|
|
||||||
("Out privacy mode", "Ur säkerhetsläge"),
|
|
||||||
("Language", "Språk"),
|
("Language", "Språk"),
|
||||||
("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"),
|
("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"),
|
||||||
("Ignore Battery Optimizations", "Ignorera batterioptimering"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", ""),
|
("Failed to turn off", ""),
|
||||||
("Turned off", ""),
|
("Turned off", ""),
|
||||||
("In privacy mode", ""),
|
|
||||||
("Out privacy mode", ""),
|
|
||||||
("Language", ""),
|
("Language", ""),
|
||||||
("Keep RustDesk background service", ""),
|
("Keep RustDesk background service", ""),
|
||||||
("Ignore Battery Optimizations", ""),
|
("Ignore Battery Optimizations", ""),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", "การปิดล้มเหลว"),
|
("Failed to turn off", "การปิดล้มเหลว"),
|
||||||
("Turned off", "ปิด"),
|
("Turned off", "ปิด"),
|
||||||
("In privacy mode", "อยู่ในโหมดความเป็นส่วนตัว"),
|
|
||||||
("Out privacy mode", "อยู่นอกโหมดความเป็นส่วนตัว"),
|
|
||||||
("Language", "ภาษา"),
|
("Language", "ภาษา"),
|
||||||
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
|
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
|
||||||
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,8 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Peer exit", "eş çıkışı"),
|
("Peer exit", "eş çıkışı"),
|
||||||
("Failed to turn off", "kapatılamadı"),
|
("Failed to turn off", "kapatılamadı"),
|
||||||
("Turned off", "Kapatıldı"),
|
("Turned off", "Kapatıldı"),
|
||||||
("In privacy mode", "Gizlilik modunda"),
|
|
||||||
("Out privacy mode", "Gizlilik modu dışında"),
|
|
||||||
("Language", "Dil"),
|
("Language", "Dil"),
|
||||||
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
|
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
|
||||||
("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", "關閉失敗"),
|
("Failed to turn off", "關閉失敗"),
|
||||||
("Turned off", "已關閉"),
|
("Turned off", "已關閉"),
|
||||||
("In privacy mode", "開啟隱私模式"),
|
|
||||||
("Out privacy mode", "退出隱私模式"),
|
|
||||||
("Language", "語言"),
|
("Language", "語言"),
|
||||||
("Keep RustDesk background service", "保持 RustDesk 後台服務"),
|
("Keep RustDesk background service", "保持 RustDesk 後台服務"),
|
||||||
("Ignore Battery Optimizations", "忽略電池最佳化"),
|
("Ignore Battery Optimizations", "忽略電池最佳化"),
|
||||||
@ -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", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].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", "Не вдалося вимкнути"),
|
("Failed to turn off", "Не вдалося вимкнути"),
|
||||||
("Turned off", "Вимкнений"),
|
("Turned off", "Вимкнений"),
|
||||||
("In privacy mode", "У режимі конфіденційності"),
|
|
||||||
("Out privacy mode", "Вихід із режиму конфіденційності"),
|
|
||||||
("Language", "Мова"),
|
("Language", "Мова"),
|
||||||
("Keep RustDesk background service", "Зберегти фонову службу RustDesk"),
|
("Keep RustDesk background service", "Зберегти фонову службу RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Ігнорувати оптимізації батареї"),
|
("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)"),
|
("True color (4:4:4)", "Спражній колір (4:4:4)"),
|
||||||
("Enable blocking user input", "Увімкнути блокування введення користувача"),
|
("Enable blocking user input", "Увімкнути блокування введення користувача"),
|
||||||
("id_input_tip", "Ви можете ввести ID, безпосередню IP, або ж домен з портом (<домен>:<порт>).\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (<id>@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"<id>@public\", ключ для публічного сервера не потрібен."),
|
("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();
|
].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"),
|
("Peer exit", "Người dùng từ xa đã thoát"),
|
||||||
("Failed to turn off", "Không thể tắt"),
|
("Failed to turn off", "Không thể tắt"),
|
||||||
("Turned off", "Đã 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ữ"),
|
("Language", "Ngôn ngữ"),
|
||||||
("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"),
|
("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"),
|
("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)", ""),
|
("True color (4:4:4)", ""),
|
||||||
("Enable blocking user input", ""),
|
("Enable blocking user input", ""),
|
||||||
("id_input_tip", ""),
|
("id_input_tip", ""),
|
||||||
|
("privacy_mode_impl_mag_tip", ""),
|
||||||
|
("privacy_mode_impl_virtual_display_tip", ""),
|
||||||
|
("Enter privacy mode", ""),
|
||||||
|
("Exit privacy mode", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,7 @@ mod hbbs_http;
|
|||||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
pub mod clipboard_file;
|
pub mod clipboard_file;
|
||||||
|
|
||||||
#[cfg(windows)]
|
pub mod privacy_mode;
|
||||||
pub mod privacy_win_mag;
|
|
||||||
|
|
||||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
pub mod virtual_display_manager;
|
pub mod virtual_display_manager;
|
||||||
|
@ -3,7 +3,7 @@ use crate::common::PORTABLE_APPNAME_RUNTIME_ENV_KEY;
|
|||||||
use crate::{
|
use crate::{
|
||||||
ipc,
|
ipc,
|
||||||
license::*,
|
license::*,
|
||||||
privacy_win_mag::{self, WIN_MAG_INJECTED_PROCESS_EXE},
|
privacy_mode::win_mag::{self, WIN_MAG_INJECTED_PROCESS_EXE},
|
||||||
};
|
};
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
@ -472,7 +472,7 @@ async fn run_service(_arguments: Vec<OsString>) -> ResultType<()> {
|
|||||||
log::info!("Got service control event: {:?}", control_event);
|
log::info!("Got service control event: {:?}", control_event);
|
||||||
match control_event {
|
match control_event {
|
||||||
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||||
ServiceControl::Stop => {
|
ServiceControl::Stop | ServiceControl::Preshutdown | ServiceControl::Shutdown => {
|
||||||
send_close(crate::POSTFIX_SERVICE).ok();
|
send_close(crate::POSTFIX_SERVICE).ok();
|
||||||
ServiceControlHandlerResult::NoError
|
ServiceControlHandlerResult::NoError
|
||||||
}
|
}
|
||||||
@ -848,8 +848,8 @@ fn get_default_install_path() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_update_broker_process() -> ResultType<()> {
|
pub fn check_update_broker_process() -> ResultType<()> {
|
||||||
let process_exe = privacy_win_mag::INJECTED_PROCESS_EXE;
|
let process_exe = win_mag::INJECTED_PROCESS_EXE;
|
||||||
let origin_process_exe = privacy_win_mag::ORIGIN_PROCESS_EXE;
|
let origin_process_exe = win_mag::ORIGIN_PROCESS_EXE;
|
||||||
|
|
||||||
let exe_file = std::env::current_exe()?;
|
let exe_file = std::env::current_exe()?;
|
||||||
let Some(cur_dir) = exe_file.parent() else {
|
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}
|
{main_exe}
|
||||||
copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\"
|
copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\"
|
||||||
",
|
",
|
||||||
ORIGIN_PROCESS_EXE = privacy_win_mag::ORIGIN_PROCESS_EXE,
|
ORIGIN_PROCESS_EXE = win_mag::ORIGIN_PROCESS_EXE,
|
||||||
broker_exe = privacy_win_mag::INJECTED_PROCESS_EXE,
|
broker_exe = win_mag::INJECTED_PROCESS_EXE,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
297
src/privacy_mode.rs
Normal file
297
src/privacy_mode.rs
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
#[cfg(windows)]
|
||||||
|
use crate::ipc::{connect, Data};
|
||||||
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
|
use crate::platform::is_installed;
|
||||||
|
use crate::{display_service, ipc::PrivacyModeState, ui_interface::get_option};
|
||||||
|
#[cfg(windows)]
|
||||||
|
use hbb_common::tokio;
|
||||||
|
use hbb_common::{anyhow::anyhow, bail, lazy_static, ResultType};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod win_input;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub mod win_mag;
|
||||||
|
|
||||||
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
|
mod win_virtual_display;
|
||||||
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
|
pub use win_virtual_display::restore_reg_connectivity;
|
||||||
|
|
||||||
|
pub const INVALID_PRIVACY_MODE_CONN_ID: i32 = 0;
|
||||||
|
pub const OCCUPIED: &'static str = "Privacy occupied by another one";
|
||||||
|
pub const TURN_OFF_OTHER_ID: &'static str =
|
||||||
|
"Failed to turn off privacy mode that belongs to someone else";
|
||||||
|
pub const NO_DISPLAYS: &'static str = "No displays";
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub const PRIVACY_MODE_IMPL_WIN_MAG: &str = win_mag::PRIVACY_MODE_IMPL;
|
||||||
|
|
||||||
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
|
pub const PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY: &str = win_virtual_display::PRIVACY_MODE_IMPL;
|
||||||
|
|
||||||
|
pub trait PrivacyMode: Sync + Send {
|
||||||
|
fn init(&self) -> ResultType<()>;
|
||||||
|
fn clear(&mut self);
|
||||||
|
fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType<bool>;
|
||||||
|
fn turn_off_privacy(&mut self, conn_id: i32, state: Option<PrivacyModeState>)
|
||||||
|
-> ResultType<()>;
|
||||||
|
|
||||||
|
fn pre_conn_id(&self) -> i32;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn check_on_conn_id(&self, conn_id: i32) -> ResultType<bool> {
|
||||||
|
let pre_conn_id = self.pre_conn_id();
|
||||||
|
if pre_conn_id == conn_id {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if pre_conn_id != INVALID_PRIVACY_MODE_CONN_ID {
|
||||||
|
bail!(OCCUPIED);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn check_off_conn_id(&self, conn_id: i32) -> ResultType<()> {
|
||||||
|
let pre_conn_id = self.pre_conn_id();
|
||||||
|
if pre_conn_id != INVALID_PRIVACY_MODE_CONN_ID
|
||||||
|
&& conn_id != INVALID_PRIVACY_MODE_CONN_ID
|
||||||
|
&& pre_conn_id != conn_id
|
||||||
|
{
|
||||||
|
bail!(TURN_OFF_OTHER_ID)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref DEFAULT_PRIVACY_MODE_IMPL: String = {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
if display_service::is_privacy_mode_mag_supported() {
|
||||||
|
PRIVACY_MODE_IMPL_WIN_MAG
|
||||||
|
} else {
|
||||||
|
#[cfg(feature = "virtual_display_driver")]
|
||||||
|
{
|
||||||
|
if is_installed() {
|
||||||
|
PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "virtual_display_driver"))]
|
||||||
|
{
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}.to_owned()
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static ref CUR_PRIVACY_MODE_IMPL: Arc<Mutex<String>> = {
|
||||||
|
let mut cur_impl = get_option("privacy-mode-impl-key".to_owned());
|
||||||
|
if !get_supported_privacy_mode_impl().iter().any(|(k, _)| k == &cur_impl) {
|
||||||
|
cur_impl = DEFAULT_PRIVACY_MODE_IMPL.to_owned();
|
||||||
|
}
|
||||||
|
Arc::new(Mutex::new(cur_impl))
|
||||||
|
};
|
||||||
|
static ref PRIVACY_MODE: Arc<Mutex<Option<Box<dyn PrivacyMode>>>> = {
|
||||||
|
let cur_impl = (*CUR_PRIVACY_MODE_IMPL.lock().unwrap()).clone();
|
||||||
|
let privacy_mode = match PRIVACY_MODE_CREATOR.lock().unwrap().get(&(&cur_impl as &str)) {
|
||||||
|
Some(creator) => Some(creator()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Arc::new(Mutex::new(privacy_mode))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PrivacyModeCreator = fn() -> Box<dyn PrivacyMode>;
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref PRIVACY_MODE_CREATOR: Arc<Mutex<HashMap<&'static str, PrivacyModeCreator>>> = {
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new();
|
||||||
|
#[cfg(windows)]
|
||||||
|
let mut map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new();
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
map.insert(win_mag::PRIVACY_MODE_IMPL, || {
|
||||||
|
Box::new(win_mag::PrivacyModeImpl::default())
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "virtual_display_driver")]
|
||||||
|
map.insert(win_virtual_display::PRIVACY_MODE_IMPL, || {
|
||||||
|
Box::new(win_virtual_display::PrivacyModeImpl::default())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Arc::new(Mutex::new(map))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn init() -> Option<ResultType<()>> {
|
||||||
|
Some(PRIVACY_MODE.lock().unwrap().as_ref()?.init())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear() -> Option<()> {
|
||||||
|
Some(PRIVACY_MODE.lock().unwrap().as_mut()?.clear())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn switch(impl_key: &str) {
|
||||||
|
let mut cur_impl_lock = CUR_PRIVACY_MODE_IMPL.lock().unwrap();
|
||||||
|
if *cur_impl_lock == impl_key {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(creator) = PRIVACY_MODE_CREATOR.lock().unwrap().get(impl_key) {
|
||||||
|
*PRIVACY_MODE.lock().unwrap() = Some(creator());
|
||||||
|
*cur_impl_lock = impl_key.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_supported_impl(impl_key: &str) -> String {
|
||||||
|
let supported_impls = get_supported_privacy_mode_impl();
|
||||||
|
if supported_impls.iter().any(|(k, _)| k == &impl_key) {
|
||||||
|
return impl_key.to_owned();
|
||||||
|
};
|
||||||
|
// fallback
|
||||||
|
let mut cur_impl = get_option("privacy-mode-impl-key".to_owned());
|
||||||
|
if !get_supported_privacy_mode_impl()
|
||||||
|
.iter()
|
||||||
|
.any(|(k, _)| k == &cur_impl)
|
||||||
|
{
|
||||||
|
// fallback
|
||||||
|
cur_impl = DEFAULT_PRIVACY_MODE_IMPL.to_owned();
|
||||||
|
}
|
||||||
|
cur_impl
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option<ResultType<bool>> {
|
||||||
|
// Check if privacy mode is already on or occupied by another one
|
||||||
|
let mut privacy_mode_lock = PRIVACY_MODE.lock().unwrap();
|
||||||
|
if let Some(privacy_mode) = privacy_mode_lock.as_ref() {
|
||||||
|
let check_on_conn_id = privacy_mode.check_on_conn_id(conn_id);
|
||||||
|
match check_on_conn_id.as_ref() {
|
||||||
|
Ok(true) | Err(_) => return Some(check_on_conn_id),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check or switch privacy mode implementation
|
||||||
|
let impl_key = get_supported_impl(impl_key);
|
||||||
|
let mut cur_impl_lock = CUR_PRIVACY_MODE_IMPL.lock().unwrap();
|
||||||
|
if *cur_impl_lock != impl_key {
|
||||||
|
if let Some(creator) = PRIVACY_MODE_CREATOR
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get(&(&impl_key as &str))
|
||||||
|
{
|
||||||
|
*privacy_mode_lock = Some(creator());
|
||||||
|
*cur_impl_lock = impl_key.to_owned();
|
||||||
|
} else {
|
||||||
|
return Some(Err(anyhow!("Unsupported privacy mode: {}", impl_key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn on privacy mode
|
||||||
|
Some(privacy_mode_lock.as_mut()?.turn_on_privacy(conn_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn turn_off_privacy(conn_id: i32, state: Option<PrivacyModeState>) -> Option<ResultType<()>> {
|
||||||
|
Some(
|
||||||
|
PRIVACY_MODE
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_mut()?
|
||||||
|
.turn_off_privacy(conn_id, state),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn check_on_conn_id(conn_id: i32) -> Option<ResultType<bool>> {
|
||||||
|
Some(
|
||||||
|
PRIVACY_MODE
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()?
|
||||||
|
.check_on_conn_id(conn_id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn set_privacy_mode_state(
|
||||||
|
conn_id: i32,
|
||||||
|
state: PrivacyModeState,
|
||||||
|
impl_key: String,
|
||||||
|
ms_timeout: u64,
|
||||||
|
) -> ResultType<()> {
|
||||||
|
let mut c = connect(ms_timeout, "_cm").await?;
|
||||||
|
c.send(&Data::PrivacyModeState((conn_id, state, impl_key)))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_supported_privacy_mode_impl() -> Vec<(&'static str, &'static str)> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
let mut vec_impls = Vec::new();
|
||||||
|
if display_service::is_privacy_mode_mag_supported() {
|
||||||
|
vec_impls.push((PRIVACY_MODE_IMPL_WIN_MAG, "privacy_mode_impl_mag_tip"));
|
||||||
|
}
|
||||||
|
#[cfg(feature = "virtual_display_driver")]
|
||||||
|
if is_installed() {
|
||||||
|
vec_impls.push((
|
||||||
|
PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY,
|
||||||
|
"privacy_mode_impl_virtual_display_tip",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
vec_impls
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_current_privacy_mode_impl(impl_key: &str) -> bool {
|
||||||
|
*CUR_PRIVACY_MODE_IMPL.lock().unwrap() == impl_key
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn check_privacy_mode_err(
|
||||||
|
_privacy_mode_id: i32,
|
||||||
|
_display_idx: usize,
|
||||||
|
_timeout_millis: u64,
|
||||||
|
) -> String {
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn check_privacy_mode_err(
|
||||||
|
privacy_mode_id: i32,
|
||||||
|
display_idx: usize,
|
||||||
|
timeout_millis: u64,
|
||||||
|
) -> String {
|
||||||
|
if is_current_privacy_mode_impl(PRIVACY_MODE_IMPL_WIN_MAG) {
|
||||||
|
crate::video_service::test_create_capturer(privacy_mode_id, display_idx, timeout_millis)
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_privacy_mode_supported() -> bool {
|
||||||
|
!DEFAULT_PRIVACY_MODE_IMPL.is_empty()
|
||||||
|
}
|
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,
|
new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender,
|
||||||
},
|
},
|
||||||
common::{get_default_sound_input, set_sound_input},
|
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"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||||
use crate::{ipc, VERSION};
|
|
||||||
use cidr_utils::cidr::IpCidr;
|
use cidr_utils::cidr::IpCidr;
|
||||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||||
@ -506,24 +505,27 @@ impl Connection {
|
|||||||
ipc::Data::ClipboardFile(clip) => {
|
ipc::Data::ClipboardFile(clip) => {
|
||||||
allow_err!(conn.stream.send(&clip_2_msg(clip)).await);
|
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 {
|
let msg_out = match state {
|
||||||
ipc::PrivacyModeState::OffSucceeded => {
|
ipc::PrivacyModeState::OffSucceeded => {
|
||||||
video_service::set_privacy_mode_conn_id(0);
|
video_service::set_privacy_mode_conn_id(0);
|
||||||
crate::common::make_privacy_mode_msg(
|
crate::common::make_privacy_mode_msg(
|
||||||
back_notification::PrivacyModeState::PrvOffSucceeded,
|
back_notification::PrivacyModeState::PrvOffSucceeded,
|
||||||
|
impl_key,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ipc::PrivacyModeState::OffByPeer => {
|
ipc::PrivacyModeState::OffByPeer => {
|
||||||
video_service::set_privacy_mode_conn_id(0);
|
video_service::set_privacy_mode_conn_id(0);
|
||||||
crate::common::make_privacy_mode_msg(
|
crate::common::make_privacy_mode_msg(
|
||||||
back_notification::PrivacyModeState::PrvOffByPeer,
|
back_notification::PrivacyModeState::PrvOffByPeer,
|
||||||
|
impl_key,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ipc::PrivacyModeState::OffUnknown => {
|
ipc::PrivacyModeState::OffUnknown => {
|
||||||
video_service::set_privacy_mode_conn_id(0);
|
video_service::set_privacy_mode_conn_id(0);
|
||||||
crate::common::make_privacy_mode_msg(
|
crate::common::make_privacy_mode_msg(
|
||||||
back_notification::PrivacyModeState::PrvOffUnknown,
|
back_notification::PrivacyModeState::PrvOffUnknown,
|
||||||
|
impl_key,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -683,9 +685,7 @@ impl Connection {
|
|||||||
let video_privacy_conn_id = video_service::get_privacy_mode_conn_id();
|
let video_privacy_conn_id = video_service::get_privacy_mode_conn_id();
|
||||||
if video_privacy_conn_id == id {
|
if video_privacy_conn_id == id {
|
||||||
video_service::set_privacy_mode_conn_id(0);
|
video_service::set_privacy_mode_conn_id(0);
|
||||||
let _ = privacy_mode::turn_off_privacy(id);
|
let _ = Self::turn_off_privacy_to_msg(id);
|
||||||
} else if video_privacy_conn_id == 0 {
|
|
||||||
let _ = privacy_mode::turn_off_privacy(0);
|
|
||||||
}
|
}
|
||||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
@ -1059,7 +1059,13 @@ impl Connection {
|
|||||||
pi.hostname = DEVICE_NAME.lock().unwrap().clone();
|
pi.hostname = DEVICE_NAME.lock().unwrap().clone();
|
||||||
pi.platform = "Android".into();
|
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();
|
let mut platform_additions = serde_json::Map::new();
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
@ -1087,6 +1093,10 @@ impl Connection {
|
|||||||
platform_additions.insert("virtual_displays".into(), json!(&virtual_displays));
|
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(
|
#[cfg(any(
|
||||||
@ -1158,7 +1168,7 @@ impl Connection {
|
|||||||
pi.username = username;
|
pi.username = username;
|
||||||
pi.sas_enabled = sas_enabled;
|
pi.sas_enabled = sas_enabled;
|
||||||
pi.features = Some(Features {
|
pi.features = Some(Features {
|
||||||
privacy_mode: display_service::is_privacy_mode_supported(),
|
privacy_mode: privacy_mode::is_privacy_mode_supported(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
@ -1474,6 +1484,7 @@ impl Connection {
|
|||||||
fn try_start_cm_ipc(&mut self) {
|
fn try_start_cm_ipc(&mut self) {
|
||||||
if let Some(p) = self.start_cm_ipc_para.take() {
|
if let Some(p) = self.start_cm_ipc_para.take() {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
#[cfg(windows)]
|
||||||
let tx_from_cm_clone = p.tx_from_cm.clone();
|
let tx_from_cm_clone = p.tx_from_cm.clone();
|
||||||
if let Err(err) = start_ipc(
|
if let Err(err) = start_ipc(
|
||||||
p.rx_to_cm,
|
p.rx_to_cm,
|
||||||
@ -2090,6 +2101,9 @@ impl Connection {
|
|||||||
Some(misc::Union::ToggleVirtualDisplay(t)) => {
|
Some(misc::Union::ToggleVirtualDisplay(t)) => {
|
||||||
self.toggle_virtual_display(t).await;
|
self.toggle_virtual_display(t).await;
|
||||||
}
|
}
|
||||||
|
Some(misc::Union::TogglePrivacyMode(t)) => {
|
||||||
|
self.toggle_privacy_mode(t).await;
|
||||||
|
}
|
||||||
Some(misc::Union::ChatMessage(c)) => {
|
Some(misc::Union::ChatMessage(c)) => {
|
||||||
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
|
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
|
||||||
self.chat_unanswered = true;
|
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")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
fn change_resolution(&mut self, r: &Resolution) {
|
fn change_resolution(&mut self, r: &Resolution) {
|
||||||
if self.keyboard {
|
if self.keyboard {
|
||||||
@ -2535,73 +2557,24 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 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 let Ok(q) = o.privacy_mode.enum_value() {
|
||||||
if self.keyboard {
|
if self.keyboard {
|
||||||
match q {
|
match q {
|
||||||
BoolOption::Yes => {
|
BoolOption::Yes => {
|
||||||
let msg_out = if !display_service::is_privacy_mode_supported() {
|
self.turn_on_privacy("".to_owned()).await;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
BoolOption::No => {
|
BoolOption::No => {
|
||||||
let msg_out = if !display_service::is_privacy_mode_supported() {
|
self.turn_off_privacy("".to_owned()).await;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if let Ok(q) = o.block_input.enum_value() {
|
if let Ok(q) = o.block_input.enum_value() {
|
||||||
if self.keyboard && self.block_input {
|
if self.keyboard && self.block_input {
|
||||||
match q {
|
match q {
|
||||||
@ -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) {
|
async fn on_close(&mut self, reason: &str, lock: bool) {
|
||||||
if self.closed {
|
if self.closed {
|
||||||
return;
|
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 {
|
pub enum AlarmAuditType {
|
||||||
IpWhitelist = 0,
|
IpWhitelist = 0,
|
||||||
ExceedThirtyAttempts = 1,
|
ExceedThirtyAttempts = 1,
|
||||||
@ -3208,8 +3241,6 @@ mod raii {
|
|||||||
display_service::reset_resolutions();
|
display_service::reset_resolutions();
|
||||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
let _ = virtual_display_manager::reset_all();
|
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]
|
#[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
|
return *IS_CAPTURER_MAGNIFIER_SUPPORTED
|
||||||
&& get_version_number(&crate::VERSION) > get_version_number("1.1.9");
|
&& get_version_number(&crate::VERSION) > get_version_number("1.1.9");
|
||||||
#[cfg(not(windows))]
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> GenericService {
|
pub fn new() -> GenericService {
|
||||||
|
@ -29,7 +29,11 @@ use crate::common::SimpleCallOnReturn;
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use crate::platform::linux::is_x11;
|
use crate::platform::linux::is_x11;
|
||||||
#[cfg(windows)]
|
#[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::{
|
use hbb_common::{
|
||||||
anyhow::anyhow,
|
anyhow::anyhow,
|
||||||
tokio::sync::{
|
tokio::sync::{
|
||||||
@ -182,44 +186,14 @@ fn create_capturer(
|
|||||||
if privacy_mode_id > 0 {
|
if privacy_mode_id > 0 {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
match scrap::CapturerMag::new(display.origin(), display.width(), display.height()) {
|
if let Some(c1) = crate::privacy_mode::win_mag::create_capturer(
|
||||||
Ok(mut c1) => {
|
privacy_mode_id,
|
||||||
let mut ok = false;
|
display.origin(),
|
||||||
let check_begin = Instant::now();
|
display.width(),
|
||||||
while check_begin.elapsed().as_secs() < 5 {
|
display.height(),
|
||||||
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));
|
c = Some(Box::new(c1));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
bail!(format!("Failed to create magnifier capture {}", e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,18 +248,21 @@ pub fn test_create_capturer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: This function is extremely expensive, do not call it frequently.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> ResultType<()> {
|
fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> ResultType<()> {
|
||||||
if 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 privacy_mode_id != capturer_privacy_mode_id {
|
if privacy_mode_id != capturer_privacy_mode_id {
|
||||||
if !is_process_consent_running()? {
|
if !is_process_consent_running()? {
|
||||||
bail!("consent.exe is running");
|
bail!("consent.exe is not running");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_process_consent_running()? {
|
if is_process_consent_running()? {
|
||||||
bail!("consent.exe is running");
|
bail!("consent.exe is running");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,11 +329,16 @@ fn get_capturer(current: usize, portable_service_running: bool) -> ResultType<Ca
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let mut capturer_privacy_mode_id = privacy_mode_id;
|
let mut capturer_privacy_mode_id = privacy_mode_id;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if 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()? {
|
if is_process_consent_running()? {
|
||||||
capturer_privacy_mode_id = 0;
|
capturer_privacy_mode_id = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Try create capturer with capturer privacy mode id {}",
|
"Try create capturer with capturer privacy mode id {}",
|
||||||
capturer_privacy_mode_id,
|
capturer_privacy_mode_id,
|
||||||
@ -586,8 +568,6 @@ fn run(vs: VideoService) -> ResultType<()> {
|
|||||||
let wait_begin = Instant::now();
|
let wait_begin = Instant::now();
|
||||||
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
|
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
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);
|
frame_controller.try_wait_next(&mut fetched_conn_ids, 300);
|
||||||
// break if all connections have received current frame
|
// break if all connections have received current frame
|
||||||
if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() {
|
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 {
|
if privacy_mode_id_2 != 0 {
|
||||||
let msg_out = crate::common::make_privacy_mode_msg(
|
let msg_out = crate::common::make_privacy_mode_msg(
|
||||||
back_notification::PrivacyModeState::PrvOnByOther,
|
back_notification::PrivacyModeState::PrvOnByOther,
|
||||||
|
"".to_owned(),
|
||||||
);
|
);
|
||||||
sp.send_to_others(msg_out, privacy_mode_id_2);
|
sp.send_to_others(msg_out, privacy_mode_id_2);
|
||||||
}
|
}
|
||||||
|
@ -401,7 +401,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
|||||||
log::info!("cm ipc connection disconnect");
|
log::info!("cm ipc connection disconnect");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Data::PrivacyModeState((_id, _)) => {
|
Data::PrivacyModeState((_id, _, _)) => {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
cm_inner_send(_id, data);
|
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;
|
let mut mouse_time = 0;
|
||||||
#[cfg(not(feature = "flutter"))]
|
#[cfg(not(feature = "flutter"))]
|
||||||
let mut id = "".to_owned();
|
let mut id = "".to_owned();
|
||||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
#[cfg(any(
|
||||||
#[allow(unused_mut, dead_code)]
|
target_os = "windows",
|
||||||
|
all(
|
||||||
|
any(target_os = "linux", target_os = "macos"),
|
||||||
|
feature = "unix-file-copy-paste"
|
||||||
|
)
|
||||||
|
))]
|
||||||
let mut enable_file_transfer = "".to_owned();
|
let mut enable_file_transfer = "".to_owned();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -317,6 +317,18 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_privacy_mode(&self, impl_key: String) {
|
||||||
|
let mut misc = Misc::new();
|
||||||
|
misc.set_toggle_privacy_mode(TogglePrivacyMode {
|
||||||
|
impl_key,
|
||||||
|
on: !self.lc.read().unwrap().get_toggle_option("privacy-mode"),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
msg_out.set_misc(misc);
|
||||||
|
self.send(Data::Message(msg_out));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_toggle_option(&self, name: String) -> bool {
|
pub fn get_toggle_option(&self, name: String) -> bool {
|
||||||
self.lc.read().unwrap().get_toggle_option(&name)
|
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.
|
// 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_INDEX_FOR_HEADLESS: u32 = 0;
|
||||||
const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 3;
|
const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 1;
|
||||||
const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 10;
|
const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 5;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref VIRTUAL_DISPLAY_MANAGER: Arc<Mutex<VirtualDisplayManager>> =
|
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);
|
log::error!("Plug in monitor failed {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user