diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index bb8c81a0f..bb5399221 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -113,7 +113,18 @@ jobs: shell: bash - name: Build rustdesk - run: python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack + run: | + Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip + $SHA256_SUM = '629b51e9944762bae73948171c65d09a79595cf4c771a82ebc003fbba5b24f51' + if ((Get-FileHash -Path .\usbmmidd_v2.zip -Algorithm SHA256).Hash -ne $SHA256_SUM) { + Write-Error "SHA256 sum mismatch, falling back to the non-virtual-display version" + python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack + } else { + Write-Host "SHA256 sum matched, using the virtual-display version" + Expand-Archive usbmmidd_v2.zip -DestinationPath . + python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack --virtual-display + mv -Force .\usbmmidd_v2 ./flutter/build/windows/x64/runner/Release/ + } - name: find Runner.res # Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res diff --git a/Cargo.toml b/Cargo.toml index 0f59d49cc..10a0ecf8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,7 @@ system_shutdown = "4.0" qrcode-generator = "4.1" [target.'cfg(target_os = "windows")'.dependencies] -winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi", "pdh", "synchapi", "memoryapi", "shellapi"] } +winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi", "pdh", "synchapi", "memoryapi", "shellapi", "devguid", "setupapi", "cguid", "cfgmgr32"] } winreg = "0.11" windows-service = "0.6" virtual_display = { path = "libs/virtual_display", optional = true } diff --git a/build.py b/build.py index ebea1f34d..1779d7d46 100755 --- a/build.py +++ b/build.py @@ -153,6 +153,12 @@ def make_parser(): action='store_true', help='Skip packing, only flutter version + Windows supported' ) + parser.add_argument( + '--virtual-display', + action='store_true', + default=False, + help='Build rustdesk libs with the virtual display feature enabled' + ) parser.add_argument( "--package", type=str @@ -293,6 +299,9 @@ def get_features(args): features.append('appimage') if args.unix_file_copy_paste: features.append('unix-file-copy-paste') + if windows: + if args.virtual_display: + features.append('virtual_display_driver') print("features:", features) return features diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 0e4a39d0a..090ca62a4 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -19,7 +19,9 @@ const kKeyTranslateMode = 'translate'; const String kPlatformAdditionsIsWayland = "is_wayland"; const String kPlatformAdditionsHeadless = "headless"; const String kPlatformAdditionsIsInstalled = "is_installed"; -const String kPlatformAdditionsVirtualDisplays = "virtual_displays"; +const String kPlatformAdditionsIddImpl = "idd_impl"; +const String kPlatformAdditionsRustDeskVirtualDisplays = "rustdesk_virtual_displays"; +const String kPlatformAdditionsAmyuniVirtualDisplays = "amyuni_virtual_displays"; const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard"; const String kPlatformAdditionsSupportedPrivacyModeImpl = "supported_privacy_mode_impl"; @@ -121,12 +123,11 @@ double kNewWindowOffset = isWindows ? 30.0 : 50.0; -EdgeInsets get kDragToResizeAreaPadding => - !kUseCompatibleUiMode && isLinux - ? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value - ? EdgeInsets.zero - : EdgeInsets.all(5.0) - : EdgeInsets.zero; +EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && isLinux + ? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value + ? EdgeInsets.zero + : EdgeInsets.all(5.0) + : EdgeInsets.zero; // https://en.wikipedia.org/wiki/Non-breaking_space const int $nbsp = 0x00A0; diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 70a5b13a8..fd048337c 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1058,11 +1058,16 @@ class _DisplayMenuState extends State<_DisplayMenu> { ffi: widget.ffi, screenAdjustor: _screenAdjustor, ), - // We may add this feature if it is needed and we have an EV certificate. - // _VirtualDisplayMenu( - // id: widget.id, - // ffi: widget.ffi, - // ), + if (pi.isRustDeskIdd) + _RustDeskVirtualDisplayMenu( + id: widget.id, + ffi: widget.ffi, + ), + if (pi.isAmyuniIdd) + _AmyuniVirtualDisplayMenu( + id: widget.id, + ffi: widget.ffi, + ), Divider(), toggles(), ]; @@ -1540,21 +1545,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } } -class _VirtualDisplayMenu extends StatefulWidget { +class _RustDeskVirtualDisplayMenu extends StatefulWidget { final String id; final FFI ffi; - _VirtualDisplayMenu({ + _RustDeskVirtualDisplayMenu({ Key? key, required this.id, required this.ffi, }) : super(key: key); @override - State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState(); + State<_RustDeskVirtualDisplayMenu> createState() => + _RustDeskVirtualDisplayMenuState(); } -class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> { +class _RustDeskVirtualDisplayMenuState + extends State<_RustDeskVirtualDisplayMenu> { @override void initState() { super.initState(); @@ -1569,7 +1576,7 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> { return Offstage(); } - final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays; + final virtualDisplays = widget.ffi.ffiModel.pi.RustDeskVirtualDisplays; final privacyModeState = PrivacyModeState.find(widget.id); final children = []; @@ -1611,6 +1618,82 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> { } } +class _AmyuniVirtualDisplayMenu extends StatefulWidget { + final String id; + final FFI ffi; + + _AmyuniVirtualDisplayMenu({ + Key? key, + required this.id, + required this.ffi, + }) : super(key: key); + + @override + State<_AmyuniVirtualDisplayMenu> createState() => + _AmiyuniVirtualDisplayMenuState(); +} + +class _AmiyuniVirtualDisplayMenuState extends State<_AmyuniVirtualDisplayMenu> { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) { + return Offstage(); + } + if (!widget.ffi.ffiModel.pi.isInstalled) { + return Offstage(); + } + + final count = widget.ffi.ffiModel.pi.amyuniVirtualDisplayCount; + final privacyModeState = PrivacyModeState.find(widget.id); + + final children = [ + Obx(() => Row( + children: [ + TextButton( + onPressed: privacyModeState.isNotEmpty || count == 0 + ? null + : () => bind.sessionToggleVirtualDisplay( + sessionId: widget.ffi.sessionId, index: 0, on: false), + child: Icon(Icons.remove), + ), + Text(count.toString()), + TextButton( + onPressed: privacyModeState.isNotEmpty || count == 4 + ? null + : () => bind.sessionToggleVirtualDisplay( + sessionId: widget.ffi.sessionId, index: 0, on: true), + child: Icon(Icons.add), + ), + ], + )), + Divider(), + Obx(() => MenuButton( + onPressed: privacyModeState.isNotEmpty || count == 0 + ? null + : () { + bind.sessionToggleVirtualDisplay( + sessionId: widget.ffi.sessionId, + index: kAllVirtualDisplay, + on: false); + }, + ffi: widget.ffi, + child: Text(translate('Plug out all')), + )), + ]; + + return _SubmenuButton( + ffi: widget.ffi, + menuChildren: children, + child: Text(translate("Virtual display")), + ); + } +} + class _KeyboardMenu extends StatelessWidget { final String id; final FFI ffi; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 0a58aa023..046767be6 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -561,8 +561,12 @@ class FfiModel with ChangeNotifier { showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId); } else if (text == 'Connected, waiting for image...') { showConnectedWaitingForImage(dialogManager, sessionId, type, title, text); + } else if (title == 'Privacy mode') { + final hasRetry = evt['hasRetry'] == 'true'; + showPrivacyFailedDialog( + sessionId, type, title, text, link, hasRetry, dialogManager); } else { - var hasRetry = evt['hasRetry'] == 'true'; + final hasRetry = evt['hasRetry'] == 'true'; showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager); } } @@ -657,6 +661,27 @@ class FfiModel with ChangeNotifier { bind.sessionOnWaitingForImageDialogShow(sessionId: sessionId); } + void showPrivacyFailedDialog( + SessionID sessionId, + String type, + String title, + String text, + String link, + bool hasRetry, + OverlayDialogManager dialogManager) { + if (text == 'no_need_privacy_mode_no_physical_displays_tip' || + text == 'Enter privacy mode') { + // There are display changes on the remote side, + // which will cause some messages to refresh the canvas and dismiss dialogs. + // So we add a delay here to ensure the dialog is displayed. + Future.delayed(Duration(milliseconds: 3000), () { + showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager); + }); + } else { + showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager); + } + } + _updateSessionWidthHeight(SessionID sessionId) { if (_rect == null) return; if (_rect!.width <= 0 || _rect!.height <= 0) { @@ -986,15 +1011,21 @@ class FfiModel with ChangeNotifier { } if (updateData.isEmpty) { - _pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays); + _pi.platformAdditions.remove(kPlatformAdditionsRustDeskVirtualDisplays); + _pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays); } else { try { final updateJson = json.decode(updateData) as Map; for (final key in updateJson.keys) { _pi.platformAdditions[key] = updateJson[key]; } - if (!updateJson.containsKey(kPlatformAdditionsVirtualDisplays)) { - _pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays); + if (!updateJson + .containsKey(kPlatformAdditionsRustDeskVirtualDisplays)) { + _pi.platformAdditions + .remove(kPlatformAdditionsRustDeskVirtualDisplays); + } + if (!updateJson.containsKey(kPlatformAdditionsAmyuniVirtualDisplays)) { + _pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays); } } catch (e) { debugPrint('Failed to decode platformAdditions $e'); @@ -2490,14 +2521,21 @@ class PeerInfo with ChangeNotifier { bool get isInstalled => platform != kPeerPlatformWindows || platformAdditions[kPlatformAdditionsIsInstalled] == true; - List get virtualDisplays => List.from( - platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []); + List get RustDeskVirtualDisplays => List.from( + platformAdditions[kPlatformAdditionsRustDeskVirtualDisplays] ?? []); + int get amyuniVirtualDisplayCount => + platformAdditions[kPlatformAdditionsAmyuniVirtualDisplays] ?? 0; bool get isSupportMultiDisplay => (isDesktop || isWebDesktop) && isSupportMultiUiSession; bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false; + bool get isRustDeskIdd => + platformAdditions[kPlatformAdditionsIddImpl] == 'rustdesk_idd'; + bool get isAmyuniIdd => + platformAdditions[kPlatformAdditionsIddImpl] == 'amyuni_idd'; + Display? tryGetDisplay() { if (displays.isEmpty) { return null; diff --git a/res/msi/CustomActions/CustomActions.cpp b/res/msi/CustomActions/CustomActions.cpp index 1543e8f65..3643ea9ce 100644 --- a/res/msi/CustomActions/CustomActions.cpp +++ b/res/msi/CustomActions/CustomActions.cpp @@ -591,3 +591,68 @@ LExit: er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; return WcaFinalize(er); } + +UINT __stdcall RemoveAmyuniIdd( + __in MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + int nResult = 0; + LPWSTR installFolder = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzData = NULL; + + WCHAR workDir[1024] = L""; + DWORD fileAttributes = 0; + HINSTANCE hi = 0; + + USHORT processMachine = 0; + USHORT nativeMachine = 0; + BOOL isWow64Res = FALSE; + LPCWSTR exe = NULL; + + hr = WcaInitialize(hInstall, "RemoveAmyuniIdd"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &installFolder); + ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz); + + hr = StringCchPrintfW(workDir, 1024, L"%lsusbmmidd_v2", installFolder); + ExitOnFailure(hr, "Failed to compose a resource identifier string"); + fileAttributes = GetFileAttributesW(workDir); + if (fileAttributes == INVALID_FILE_ATTRIBUTES) { + WcaLog(LOGMSG_STANDARD, "Amyuni idd dir \"%ls\" is out found, %d", workDir, fileAttributes); + goto LExit; + } + + isWow64Res = IsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine); + if (isWow64Res == TRUE) { + if (nativeMachine == IMAGE_FILE_MACHINE_AMD64) { + exe = L"deviceinstaller64.exe"; + } else { + exe = L"deviceinstaller.exe"; + } + WcaLog(LOGMSG_STANDARD, "Remove amyuni idd %ls in %ls", exe, workDir); + hi = ShellExecuteW(NULL, L"open", exe, L"remove usbmmidd", workDir, SW_HIDE); + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew + if ((int)hi <= 32) { + WcaLog(LOGMSG_STANDARD, "Failed to remove amyuni idd : %d, last error: %d", (int)hi, GetLastError()); + } + else { + WcaLog(LOGMSG_STANDARD, "Amyuni idd is removed"); + } + } else { + WcaLog(LOGMSG_STANDARD, "Failed to call IsWow64Process2(): %d", GetLastError()); + } + +LExit: + ReleaseStr(installFolder); + + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} diff --git a/res/msi/CustomActions/CustomActions.def b/res/msi/CustomActions/CustomActions.def index a73ecda26..557bfaf18 100644 --- a/res/msi/CustomActions/CustomActions.def +++ b/res/msi/CustomActions/CustomActions.def @@ -11,3 +11,4 @@ EXPORTS TryDeleteStartupShortcut SetPropertyFromConfig AddRegSoftwareSASGeneration + RemoveAmyuniIdd diff --git a/res/msi/Package/Components/RustDesk.wxs b/res/msi/Package/Components/RustDesk.wxs index ab1d404ec..31dbbc66d 100644 --- a/res/msi/Package/Components/RustDesk.wxs +++ b/res/msi/Package/Components/RustDesk.wxs @@ -29,6 +29,7 @@ + @@ -68,10 +69,12 @@ - + - + + + diff --git a/res/msi/Package/Fragments/CustomActions.wxs b/res/msi/Package/Fragments/CustomActions.wxs index e064b188e..b443eff52 100644 --- a/res/msi/Package/Fragments/CustomActions.wxs +++ b/res/msi/Package/Fragments/CustomActions.wxs @@ -16,5 +16,6 @@ + diff --git a/src/core_main.rs b/src/core_main.rs index cd27ec0ca..25e291295 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -215,7 +215,9 @@ pub fn core_main() -> Option> { } else if args[0] == "--install-idd" { #[cfg(all(windows, feature = "virtual_display_driver"))] if crate::virtual_display_manager::is_virtual_display_supported() { - hbb_common::allow_err!(crate::virtual_display_manager::install_update_driver()); + hbb_common::allow_err!( + crate::virtual_display_manager::rustdesk_idd::install_update_driver() + ); } return None; } else if args[0] == "--portable-service" { @@ -254,7 +256,7 @@ pub fn core_main() -> Option> { } else if args[0] == "--server" { log::info!("start --server with user {}", crate::username()); #[cfg(all(windows, feature = "virtual_display_driver"))] - crate::privacy_mode::restore_reg_connectivity(); + crate::privacy_mode::restore_reg_connectivity(true); #[cfg(any(target_os = "linux", target_os = "windows"))] { crate::start_server(true); diff --git a/src/lang/ar.rs b/src/lang/ar.rs index be4f6bc5b..bdfd83cc1 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 4ed1cc0c3..3a3a40ee9 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 42d714da7..610282b1a 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index b350420b8..0309cf562 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "所有人"), ("ab_web_console_tip", "打开 Web 控制台以执行更多操作"), ("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"), + ("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index b6bb80b59..4c3a4b3cc 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Každý"), ("ab_web_console_tip", "Více na webové konzoli"), ("allow-only-conn-window-open-tip", "Povolit připojení pouze v případě, že je otevřené okno RustDesk"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 8ec86dace..c20f15128 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index e8d63e790..1c88bdde5 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Jeder"), ("ab_web_console_tip", "Mehr über Webkonsole"), ("allow-only-conn-window-open-tip", "Verbindung nur zulassen, wenn das RustDesk-Fenster geöffnet ist"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 369ec466d..7d4966120 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 5782a8597..ac6cd2cd3 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -219,5 +219,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("share_warning_tip", "The fields above are shared and visible to others."), ("ab_web_console_tip", "More on web console"), ("allow-only-conn-window-open-tip", "Only allow connection if RustDesk window is open"), + ("no_need_privacy_mode_no_physical_displays_tip", "No physical displays, no need to use the privacy mode."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index fc17f0af9..e1bab260d 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index e9ac3a3c5..1e7c40b26 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Todos"), ("ab_web_console_tip", "Más en consola web"), ("allow-only-conn-window-open-tip", "Permitir la conexión solo si la ventana RusDesk está abierta"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 5449dc863..2e49ddee7 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 4cf7b889f..6eb59377d 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "هر کس"), ("ab_web_console_tip", "اطلاعات بیشتر در کنسول وب"), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 7f6d75b08..ff644806e 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 3cec23385..2da9339a2 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index 91913676b..def491c52 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Svatko"), ("ab_web_console_tip", "Više na web konzoli"), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 1a63bdcf8..bdae7d9bb 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index b56293785..9a0fc988f 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 493519fbf..670853e4a 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Everyone"), ("ab_web_console_tip", "Altre info sulla console web"), ("allow-only-conn-window-open-tip", "Consenti la connessione solo se la finestra RustDesk è aperta"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 184461198..400ae1329 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 116cd5d02..e0fc91a06 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index eb599cd98..7bffe6a2e 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 6c7b84804..06d3ce2bd 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 9fbc7d10a..cc0cf98ff 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Visi"), ("ab_web_console_tip", "Vairāk par tīmekļa konsoli"), ("allow-only-conn-window-open-tip", "Atļaut savienojumu tikai tad, ja ir atvērts RustDesk logs"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index 5a0a83f44..d4251a554 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index a4c074912..705b34f16 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Iedereen"), ("ab_web_console_tip", "Meer over de webconsole"), ("allow-only-conn-window-open-tip", "Alleen verbindingen toestaan als het RustDesk-venster geopend is"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index c32931661..06adebc09 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Wszyscy"), ("ab_web_console_tip", "Więcej w konsoli web"), ("allow-only-conn-window-open-tip", "Zezwalaj na połączenie tylko wtedy, gdy okno RustDesk jest otwarte"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index c1f40cbae..76fa2c214 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 01795ebf7..2958878a5 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Todos"), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 28aa015c9..c7780c371 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 00c4ad0de..0cc7acdb0 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Все"), ("ab_web_console_tip", "Больше в веб-консоли"), ("allow-only-conn-window-open-tip", "Разрешать подключение только при открытом окне RustDesk"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 289a75410..7492b59f2 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Každý"), ("ab_web_console_tip", "Viac na webovej konzole"), ("allow-only-conn-window-open-tip", "Povoliť pripojenie iba vtedy, ak je otvorené okno aplikácie RustDesk"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index cbc15f35e..cdc37594d 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index f6369c9da..6a7ee5d63 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index b38b2713d..e03383415 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index e40ddc824..b78677fe6 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 2c608cf1c..02aa92450 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 20abcd639..ce262d19a 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index cacaa1586..27fca3807 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index fbd36cb12..4e86fe760 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "所有人"), ("ab_web_console_tip", "打開 Web 控制台以進行更多操作"), ("allow-only-conn-window-open-tip", "只在 RustDesk 視窗開啟時允許連接"), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 0f4fc652e..63a8800f5 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", "Всі"), ("ab_web_console_tip", "Детальніше про веб-консоль"), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index bd671bd8f..96cbbee7e 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Everyone", ""), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), + ("no_need_privacy_mode_no_physical_displays_tip", ""), ].iter().cloned().collect(); } diff --git a/src/platform/windows.cc b/src/platform/windows.cc index 381f4dc63..0d328ac13 100644 --- a/src/platform/windows.cc +++ b/src/platform/windows.cc @@ -669,4 +669,48 @@ extern "C" AllocConsole(); freopen("CONOUT$", "w", stdout); } + + bool is_service_running_w(LPCWSTR serviceName) + { + SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); + if (hSCManager == NULL) { + return false; + } + + SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_QUERY_STATUS); + if (hService == NULL) { + CloseServiceHandle(hSCManager); + return false; + } + + SERVICE_STATUS_PROCESS serviceStatus; + DWORD bytesNeeded; + if (!QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, reinterpret_cast(&serviceStatus), sizeof(serviceStatus), &bytesNeeded)) { + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return false; + } + + bool isRunning = (serviceStatus.dwCurrentState == SERVICE_RUNNING); + + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + + return isRunning; + } } // end of extern "C" + +extern "C" +{ + int get_native_machine() + { + USHORT processMachine = 0; + USHORT nativeMachine = 0; + BOOL res = IsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine); + if (res == TRUE) { + return (int)nativeMachine; + } else { + return -1; + } + } +} diff --git a/src/platform/windows.rs b/src/platform/windows.rs index c28570219..a6dfd444f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -64,8 +64,6 @@ use windows_service::{ use winreg::enums::*; use winreg::RegKey; -pub const DRIVER_CERT_FILE: &str = "RustDeskIddDriver.cer"; - pub fn get_cursor_pos() -> Option<(i32, i32)> { unsafe { #[allow(invalid_value)] @@ -462,6 +460,8 @@ extern "C" { fn is_win_down() -> BOOL; fn is_local_system() -> BOOL; fn alloc_console_and_redirect(); + fn get_native_machine() -> i32; + fn is_service_running_w(svc_name: *const u16) -> bool; } extern "system" { @@ -1296,12 +1296,14 @@ fn get_uninstall(kill_self: bool) -> String { {before_uninstall} {uninstall_cert_cmd} reg delete {subkey} /f + {uninstall_amyuni_idd} if exist \"{path}\" rd /s /q \"{path}\" if exist \"{start_menu}\" rd /s /q \"{start_menu}\" if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\" if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" ", before_uninstall=get_before_uninstall(kill_self), + uninstall_amyuni_idd=get_uninstall_amyuni_idd(&path), app_name = crate::get_app_name(), ) } @@ -2367,3 +2369,81 @@ impl Drop for WallPaperRemover { allow_err!(Self::set_wallpaper(Some(self.old_path.clone()))); } } + +// See winnt.h for more information. +#[derive(Clone, Copy)] +pub enum MachineArch { + Unknown = 0, + I386 = 0x014c, + ARM = 0x01c0, + AMD64 = 0x8664, + ARM64 = 0xAA64, +} + +pub fn get_machine_arch() -> Result { + let native_machine = unsafe { get_native_machine() }; + if native_machine != -1 { + let native_machine = native_machine as u16; + let check_types = [ + MachineArch::I386, + MachineArch::AMD64, + MachineArch::ARM, + MachineArch::ARM64, + ]; + for check_type in check_types.iter() { + if *check_type as u16 == native_machine { + return Ok(*check_type); + } + } + Ok(MachineArch::Unknown) + } else { + Err(io::Error::last_os_error()) + } +} + +pub fn get_amyuni_exe_name() -> Option { + match get_machine_arch() { + Ok(arch) => { + let exe = match arch { + MachineArch::I386 => "deviceinstaller.exe", + MachineArch::AMD64 => "deviceinstaller64.exe", + _ => { + log::error!("Unsupported machine architecture"); + return None; + } + }; + Some(exe.to_string()) + } + Err(e) => { + log::warn!("Failed to get machine architecture: {}", e); + None + } + } +} + +fn get_uninstall_amyuni_idd(path: &str) -> String { + let Some(exe) = get_amyuni_exe_name() else { + return "".to_string(); + }; + let work_dir = PathBuf::from(path).join("usbmmidd_v2"); + if work_dir.join(&exe).exists() { + format!( + "pushd {} && .\\{exe} remove usbmmidd && popd", + work_dir.to_string_lossy() + ) + } else { + "".to_string() + } +} + +#[inline] +pub fn is_self_service_running() -> bool { + is_service_running(&crate::get_app_name()) +} + +pub fn is_service_running(service_name: &str) -> bool { + unsafe { + let service_name = wide_string(service_name); + is_service_running_w(service_name.as_ptr() as _) + } +} diff --git a/src/privacy_mode.rs b/src/privacy_mode.rs index d0781d993..f6eafcf41 100644 --- a/src/privacy_mode.rs +++ b/src/privacy_mode.rs @@ -6,9 +6,12 @@ use crate::{ display_service, ipc::{connect, Data}, }; -#[cfg(windows)] -use hbb_common::tokio; -use hbb_common::{anyhow::anyhow, bail, lazy_static, ResultType}; +use hbb_common::{ + anyhow::anyhow, + bail, lazy_static, + tokio::{self, sync::oneshot}, + ResultType, +}; use serde_derive::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -30,10 +33,10 @@ mod win_virtual_display; 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 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"; + "Failed to turn off privacy mode that belongs to someone else."; +pub const NO_PHYSICAL_DISPLAYS: &'static str = "no_need_privacy_mode_no_physical_displays_tip"; #[cfg(windows)] pub const PRIVACY_MODE_IMPL_WIN_MAG: &str = win_mag::PRIVACY_MODE_IMPL; @@ -53,6 +56,8 @@ pub enum PrivacyModeState { } pub trait PrivacyMode: Sync + Send { + fn is_async_privacy_mode(&self) -> bool; + fn init(&self) -> ResultType<()>; fn clear(&mut self); fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType; @@ -203,7 +208,40 @@ fn get_supported_impl(impl_key: &str) -> String { } #[inline] -pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> { +pub async fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> { + if is_async_privacy_mode() { + turn_on_privacy_async(impl_key.to_string(), conn_id).await + } else { + turn_on_privacy_sync(impl_key, conn_id) + } +} + +#[inline] +fn is_async_privacy_mode() -> bool { + PRIVACY_MODE + .lock() + .unwrap() + .as_ref() + .map_or(false, |m| m.is_async_privacy_mode()) +} + +#[inline] +async fn turn_on_privacy_async(impl_key: String, conn_id: i32) -> Option> { + let (tx, rx) = oneshot::channel(); + std::thread::spawn(move || { + let res = turn_on_privacy_sync(&impl_key, conn_id); + let _ = tx.send(res); + }); + match hbb_common::timeout(5000, rx).await { + Ok(res) => match res { + Ok(res) => res, + Err(e) => Some(Err(anyhow!(e.to_string()))), + }, + Err(e) => Some(Err(anyhow!(e.to_string()))), + } +} + +fn turn_on_privacy_sync(impl_key: &str, conn_id: i32) -> Option> { // Check if privacy mode is already on or occupied by another one let mut privacy_mode_lock = PRIVACY_MODE.lock().unwrap(); @@ -300,7 +338,7 @@ pub fn get_supported_privacy_mode_impl() -> Vec<(&'static str, &'static str)> { } #[cfg(feature = "virtual_display_driver")] - if is_installed() { + if is_installed() && crate::platform::windows::is_self_service_running() { vec_impls.push(( PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY, "privacy_mode_impl_virtual_display_tip", diff --git a/src/privacy_mode/win_topmost_window.rs b/src/privacy_mode/win_topmost_window.rs index 7fd27b60b..a7f80a02d 100644 --- a/src/privacy_mode/win_topmost_window.rs +++ b/src/privacy_mode/win_topmost_window.rs @@ -72,6 +72,10 @@ pub struct PrivacyModeImpl { } impl PrivacyMode for PrivacyModeImpl { + fn is_async_privacy_mode(&self) -> bool { + false + } + fn init(&self) -> ResultType<()> { Ok(()) } diff --git a/src/privacy_mode/win_virtual_display.rs b/src/privacy_mode/win_virtual_display.rs index 287bfd51e..04a9d776c 100644 --- a/src/privacy_mode/win_virtual_display.rs +++ b/src/privacy_mode/win_virtual_display.rs @@ -1,9 +1,11 @@ -use super::{PrivacyMode, PrivacyModeState, INVALID_PRIVACY_MODE_CONN_ID, NO_DISPLAYS}; +use super::{PrivacyMode, PrivacyModeState, INVALID_PRIVACY_MODE_CONN_ID, NO_PHYSICAL_DISPLAYS}; use crate::virtual_display_manager; use hbb_common::{allow_err, bail, config::Config, log, ResultType}; use std::{ io::Error, ops::{Deref, DerefMut}, + thread, + time::Duration, }; use virtual_display::MonitorMode; use winapi::{ @@ -27,7 +29,6 @@ use winapi::{ 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 { @@ -137,8 +138,9 @@ impl PrivacyModeImpl { primary, }; - if let Ok(s) = std::string::String::from_utf16(&dd.DeviceString) { - if &s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING { + let ds = virtual_display_manager::get_cur_device_string(); + if let Ok(s) = String::from_utf16(&dd.DeviceString) { + if s.len() >= ds.len() && &s[..ds.len()] == ds { self.virtual_displays.push(display); continue; } @@ -155,7 +157,7 @@ impl PrivacyModeImpl { } fn restore_plug_out_monitor(&mut self) { - let _ = virtual_display_manager::plug_out_peer_request(&self.virtual_displays_added); + let _ = virtual_display_manager::plug_out_monitor_indices(&self.virtual_displays_added); self.virtual_displays_added.clear(); } @@ -304,8 +306,18 @@ impl PrivacyModeImpl { 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); + if virtual_display_manager::is_amyuni_idd() { + thread::sleep(Duration::from_secs(3)); + } self.set_displays(); + + // No physical displays, no need to use the privacy mode. + if self.displays.is_empty() { + virtual_display_manager::plug_out_monitor_indices(&displays)?; + bail!(NO_PHYSICAL_DISPLAYS); + } + + self.virtual_displays_added.extend(displays); } Ok(()) @@ -348,6 +360,10 @@ impl PrivacyModeImpl { } impl PrivacyMode for PrivacyModeImpl { + fn is_async_privacy_mode(&self) -> bool { + virtual_display_manager::is_amyuni_idd() + } + fn init(&self) -> ResultType<()> { Ok(()) } @@ -367,8 +383,8 @@ impl PrivacyMode for PrivacyModeImpl { } self.set_displays(); if self.displays.is_empty() { - log::debug!("No displays"); - bail!(NO_DISPLAYS); + log::debug!("{}", NO_PHYSICAL_DISPLAYS); + bail!(NO_PHYSICAL_DISPLAYS); } let mut guard = TurnOnGuard { @@ -379,7 +395,7 @@ impl PrivacyMode for PrivacyModeImpl { guard.ensure_virtual_display()?; if guard.virtual_displays.is_empty() { log::debug!("No virtual displays"); - bail!("No virtual displays"); + bail!("No virtual displays."); } let reg_connectivity_1 = reg_display_settings::read_reg_connectivity()?; @@ -416,7 +432,7 @@ impl PrivacyMode for PrivacyModeImpl { self.check_off_conn_id(conn_id)?; super::win_input::unhook()?; self.restore(); - restore_reg_connectivity(); + restore_reg_connectivity(false); if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { if let Some(state) = state { @@ -457,11 +473,14 @@ fn reset_config_reg_connectivity() { Config::set_option(CONFIG_KEY_REG_RECOVERY.to_owned(), "".to_owned()); } -pub fn restore_reg_connectivity() { +pub fn restore_reg_connectivity(plug_out_monitors: bool) { let config_recovery_value = Config::get_option(CONFIG_KEY_REG_RECOVERY); if config_recovery_value.is_empty() { return; } + if plug_out_monitors { + let _ = virtual_display_manager::plug_out_monitor(-1); + } if let Ok(reg_recovery) = serde_json::from_str::(&config_recovery_value) { diff --git a/src/server/connection.rs b/src/server/connection.rs index 1a30730af..8526a998d 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1133,10 +1133,7 @@ impl Connection { ); #[cfg(feature = "virtual_display_driver")] if crate::platform::is_installed() { - let virtual_displays = virtual_display_manager::get_virtual_displays(); - if !virtual_displays.is_empty() { - platform_additions.insert("virtual_displays".into(), json!(&virtual_displays)); - } + platform_additions.extend(virtual_display_manager::get_platform_additions()); } platform_additions.insert( "supported_privacy_mode_impl".into(), @@ -2595,8 +2592,7 @@ impl Connection { self.send(make_msg("idd_not_support_under_win10_2004_tip".to_string())) .await; } else { - if let Err(e) = - virtual_display_manager::plug_in_index_modes(t.display as _, Vec::new()) + if let Err(e) = virtual_display_manager::plug_in_monitor(t.display as _, Vec::new()) { log::error!("Failed to plug in virtual display: {}", e); self.send(make_msg(format!( @@ -2607,13 +2603,8 @@ impl Connection { } } } else { - let indices = if t.display == -1 { - virtual_display_manager::get_virtual_displays() - } else { - vec![t.display as _] - }; - if let Err(e) = virtual_display_manager::plug_out_peer_request(&indices) { - log::error!("Failed to plug out virtual display {:?}: {}", &indices, e); + if let Err(e) = virtual_display_manager::plug_out_monitor(t.display) { + log::error!("Failed to plug out virtual display {}: {}", t.display, e); self.send(make_msg(format!( "Failed to plug out virtual displays: {}", e @@ -2639,7 +2630,7 @@ impl Connection { let name = display.name(); #[cfg(all(windows, feature = "virtual_display_driver"))] if let Some(_ok) = - virtual_display_manager::change_resolution_if_is_virtual_display( + virtual_display_manager::rustdesk_idd::change_resolution_if_is_virtual_display( &name, r.width as _, r.height as _, @@ -2858,7 +2849,6 @@ impl Connection { } else { let is_pre_privacy_on = privacy_mode::is_in_privacy_mode(); let pre_impl_key = privacy_mode::get_cur_impl_key(); - let turn_on_res = privacy_mode::turn_on_privacy(&impl_key, self.inner.id); if is_pre_privacy_on { if let Some(pre_impl_key) = pre_impl_key { @@ -2872,6 +2862,7 @@ impl Connection { } } + let turn_on_res = privacy_mode::turn_on_privacy(&impl_key, self.inner.id).await; match turn_on_res { Some(Ok(res)) => { if res { @@ -2906,7 +2897,7 @@ impl Connection { } Some(Err(e)) => { log::error!("Failed to turn on privacy mode. {}", e); - if !privacy_mode::is_in_privacy_mode() { + if privacy_mode::is_in_privacy_mode() { let _ = Self::turn_off_privacy_to_msg( privacy_mode::INVALID_PRIVACY_MODE_CONN_ID, ); diff --git a/src/server/display_service.rs b/src/server/display_service.rs index 838937057..0c8263cbd 100644 --- a/src/server/display_service.rs +++ b/src/server/display_service.rs @@ -160,15 +160,8 @@ fn displays_to_msg(displays: Vec) -> Message { #[cfg(all(windows, feature = "virtual_display_driver"))] if crate::platform::is_installed() { - let virtual_displays = crate::virtual_display_manager::get_virtual_displays(); - if !virtual_displays.is_empty() { - let mut platform_additions = serde_json::Map::new(); - platform_additions.insert( - "virtual_displays".into(), - serde_json::json!(&virtual_displays), - ); - pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into()); - } + let m = crate::virtual_display_manager::get_platform_additions(); + pi.platform_additions = serde_json::to_string(&m).unwrap_or_default(); } // current_display should not be used in server. @@ -227,10 +220,11 @@ pub(super) fn get_original_resolution( h: usize, ) -> MessageField { #[cfg(all(windows, feature = "virtual_display_driver"))] - let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name); + let is_rustdesk_virtual_display = + crate::virtual_display_manager::rustdesk_idd::is_virtual_display(&display_name); #[cfg(not(all(windows, feature = "virtual_display_driver")))] - let is_virtual_display = false; - Some(if is_virtual_display { + let is_rustdesk_virtual_display = false; + Some(if is_rustdesk_virtual_display { Resolution { width: 0, height: 0, @@ -382,8 +376,10 @@ pub fn try_get_displays() -> ResultType> { #[cfg(all(windows, feature = "virtual_display_driver"))] pub fn try_get_displays() -> ResultType> { let mut displays = Display::all()?; + let no_displays_v = no_displays(&displays); + virtual_display_manager::set_can_plug_out_all(!no_displays_v); if crate::platform::is_installed() - && no_displays(&displays) + && no_displays_v && virtual_display_manager::is_virtual_display_supported() { log::debug!("no displays, create virtual display"); diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs index da8780c7f..3188fc33e 100644 --- a/src/virtual_display_manager.rs +++ b/src/virtual_display_manager.rs @@ -1,57 +1,47 @@ -#[cfg(target_os = "windows")] -use hbb_common::platform::windows::is_windows_version_or_greater; -use hbb_common::{allow_err, bail, lazy_static, log, ResultType}; -use std::{ - collections::{HashMap, HashSet}, - sync::{Arc, Mutex}, -}; +use hbb_common::{bail, platform::windows::is_windows_version_or_greater, ResultType}; +use std::sync::atomic; -// virtual display index range: 0 - 2 are reserved for headless and other special uses. -const VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS: u32 = 0; -const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 1; -const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 5; +// This string is defined here. +// https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 +pub const RUSTDESK_IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; +pub const AMYUNI_IDD_DEVICE_STRING: &'static str = "USB Mobile Monitor Virtual Display\0"; -lazy_static::lazy_static! { - static ref VIRTUAL_DISPLAY_MANAGER: Arc> = - Arc::new(Mutex::new(VirtualDisplayManager::default())); +const IDD_IMPL: &str = IDD_IMPL_AMYUNI; +const IDD_IMPL_RUSTDESK: &str = "rustdesk_idd"; +const IDD_IMPL_AMYUNI: &str = "amyuni_idd"; + +const IS_CAN_PLUG_OUT_ALL_NOT_SET: i8 = 0; +const IS_CAN_PLUG_OUT_ALL_YES: i8 = 1; +const IS_CAN_PLUG_OUT_ALL_NO: i8 = 2; +static IS_CAN_PLUG_OUT_ALL: atomic::AtomicI8 = atomic::AtomicI8::new(IS_CAN_PLUG_OUT_ALL_NOT_SET); + +pub fn is_can_plug_out_all() -> bool { + IS_CAN_PLUG_OUT_ALL.load(atomic::Ordering::Relaxed) != IS_CAN_PLUG_OUT_ALL_NO } -#[derive(Default)] -struct VirtualDisplayManager { - headless_index_name: Option<(u32, String)>, - peer_index_name: HashMap, - is_driver_installed: bool, +// No need to consider concurrency here. +pub fn set_can_plug_out_all(v: bool) { + if IS_CAN_PLUG_OUT_ALL.load(atomic::Ordering::Relaxed) == IS_CAN_PLUG_OUT_ALL_NOT_SET { + IS_CAN_PLUG_OUT_ALL.store( + if v { + IS_CAN_PLUG_OUT_ALL_YES + } else { + IS_CAN_PLUG_OUT_ALL_NO + }, + atomic::Ordering::Relaxed, + ); + } } -impl VirtualDisplayManager { - fn prepare_driver(&mut self) -> ResultType<()> { - if !self.is_driver_installed { - self.install_update_driver()?; - } - Ok(()) - } +pub fn is_amyuni_idd() -> bool { + IDD_IMPL == IDD_IMPL_AMYUNI +} - fn install_update_driver(&mut self) -> ResultType<()> { - if let Err(e) = virtual_display::create_device() { - if !e.to_string().contains("Device is already created") { - bail!("Create device failed {}", e); - } - } - // Reboot is not required for this case. - let mut _reboot_required = false; - virtual_display::install_update_driver(&mut _reboot_required)?; - self.is_driver_installed = true; - Ok(()) - } - - fn plug_in_monitor(index: u32, modes: &[virtual_display::MonitorMode]) -> ResultType<()> { - if let Err(e) = virtual_display::plug_in_monitor(index) { - bail!("Plug in monitor failed {}", e); - } - if let Err(e) = virtual_display::update_monitor_modes(index, &modes) { - log::error!("Update monitor modes failed {}", e); - } - Ok(()) +pub fn get_cur_device_string() -> &'static str { + match IDD_IMPL { + IDD_IMPL_RUSTDESK => RUSTDESK_IDD_DEVICE_STRING, + IDD_IMPL_AMYUNI => AMYUNI_IDD_DEVICE_STRING, + _ => "", } } @@ -66,209 +56,514 @@ pub fn is_virtual_display_supported() -> bool { } } -pub fn install_update_driver() -> ResultType<()> { - VIRTUAL_DISPLAY_MANAGER - .lock() - .unwrap() - .install_update_driver() -} - pub fn plug_in_headless() -> ResultType<()> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - manager.prepare_driver()?; - let modes = [virtual_display::MonitorMode { - width: 1920, - height: 1080, - sync: 60, - }]; - let device_names = windows::get_device_names(); - VirtualDisplayManager::plug_in_monitor(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, &modes)?; - let device_name = get_new_device_name(&device_names); - manager.headless_index_name = Some((VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, device_name)); - Ok(()) -} - -pub fn plug_out_headless() -> bool { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - if let Some((index, _)) = manager.headless_index_name.take() { - if let Err(e) = virtual_display::plug_out_monitor(index) { - log::error!("Plug out monitor failed {}", e); - } - true - } else { - false + match IDD_IMPL { + IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_headless(), + IDD_IMPL_AMYUNI => amyuni_idd::plug_in_headless(), + _ => bail!("Unsupported virtual display implementation."), } } -fn get_new_device_name(device_names: &HashSet) -> String { - for _ in 0..3 { - let device_names_af = windows::get_device_names(); - let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); - if diff_names.len() == 1 { - return diff_names[0].clone(); - } else if diff_names.len() > 1 { - log::error!( - "Failed to get diff device names after plugin virtual display, more than one diff names: {:?}", - &diff_names - ); - return "".to_string(); - } - // Sleep is needed here to wait for the virtual display to be ready. - std::thread::sleep(std::time::Duration::from_millis(50)); +pub fn get_platform_additions() -> serde_json::Map { + let mut map = serde_json::Map::new(); + if !crate::platform::windows::is_self_service_running() { + return map; } - log::error!("Failed to get diff device names after plugin virtual display",); - "".to_string() -} - -pub fn get_virtual_displays() -> Vec { - VIRTUAL_DISPLAY_MANAGER - .lock() - .unwrap() - .peer_index_name - .keys() - .cloned() - .collect() -} - -pub fn plug_in_index_modes( - idx: u32, - mut modes: Vec, -) -> ResultType<()> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - manager.prepare_driver()?; - if !manager.peer_index_name.contains_key(&idx) { - let device_names = windows::get_device_names(); - if modes.is_empty() { - modes.push(virtual_display::MonitorMode { - width: 1920, - height: 1080, - sync: 60, - }); - } - match VirtualDisplayManager::plug_in_monitor(idx, modes.as_slice()) { - Ok(_) => { - let device_name = get_new_device_name(&device_names); - manager.peer_index_name.insert(idx, device_name); - } - Err(e) => { - log::error!("Plug in monitor failed {}", e); + map.insert("idd_impl".into(), serde_json::json!(IDD_IMPL)); + match IDD_IMPL { + IDD_IMPL_RUSTDESK => { + let virtual_displays = rustdesk_idd::get_virtual_displays(); + if !virtual_displays.is_empty() { + map.insert( + "rustdesk_virtual_displays".into(), + serde_json::json!(virtual_displays), + ); } } + IDD_IMPL_AMYUNI => { + let c = amyuni_idd::get_monitor_count(); + if c > 0 { + map.insert("amyuni_virtual_displays".into(), serde_json::json!(c)); + } + } + _ => {} } - Ok(()) + map } -pub fn reset_all() -> ResultType<()> { - if is_virtual_display_supported() { - return Ok(()); +#[inline] +pub fn plug_in_monitor(idx: u32, modes: Vec) -> ResultType<()> { + match IDD_IMPL { + IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_index_modes(idx, modes), + IDD_IMPL_AMYUNI => amyuni_idd::plug_in_monitor(), + _ => bail!("Unsupported virtual display implementation."), } +} - if let Err(e) = plug_out_peer_request(&get_virtual_displays()) { - log::error!("Failed to plug out virtual displays: {}", e); +pub fn plug_out_monitor(index: i32) -> ResultType<()> { + match IDD_IMPL { + IDD_IMPL_RUSTDESK => { + let indices = if index == -1 { + rustdesk_idd::get_virtual_displays() + } else { + vec![index as _] + }; + rustdesk_idd::plug_out_peer_request(&indices) + } + IDD_IMPL_AMYUNI => amyuni_idd::plug_out_monitor(index), + _ => bail!("Unsupported virtual display implementation."), } - let _ = plug_out_headless(); - Ok(()) } pub fn plug_in_peer_request(modes: Vec>) -> ResultType> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - manager.prepare_driver()?; + match IDD_IMPL { + IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_peer_request(modes), + IDD_IMPL_AMYUNI => { + amyuni_idd::plug_in_monitor()?; + Ok(vec![0]) + } + _ => bail!("Unsupported virtual display implementation."), + } +} - let mut indices: Vec = Vec::new(); - for m in modes.iter() { - for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT { - if !manager.peer_index_name.contains_key(&idx) { - let device_names = windows::get_device_names(); - match VirtualDisplayManager::plug_in_monitor(idx, m) { - Ok(_) => { - let device_name = get_new_device_name(&device_names); - manager.peer_index_name.insert(idx, device_name); - indices.push(idx); - } - Err(e) => { - log::error!("Plug in monitor failed {}", e); - } +pub fn plug_out_monitor_indices(indices: &[u32]) -> ResultType<()> { + match IDD_IMPL { + IDD_IMPL_RUSTDESK => rustdesk_idd::plug_out_peer_request(indices), + IDD_IMPL_AMYUNI => { + for _idx in indices.iter() { + amyuni_idd::plug_out_monitor(0)?; + } + Ok(()) + } + _ => bail!("Unsupported virtual display implementation."), + } +} + +pub fn reset_all() -> ResultType<()> { + match IDD_IMPL { + IDD_IMPL_RUSTDESK => rustdesk_idd::reset_all(), + IDD_IMPL_AMYUNI => crate::privacy_mode::turn_off_privacy(0, None).unwrap_or(Ok(())), + _ => bail!("Unsupported virtual display implementation."), + } +} + +pub mod rustdesk_idd { + use super::windows; + use hbb_common::{allow_err, bail, lazy_static, log, ResultType}; + use std::{ + collections::{HashMap, HashSet}, + sync::{Arc, Mutex}, + }; + + // virtual display index range: 0 - 2 are reserved for headless and other special uses. + const VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS: u32 = 0; + const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 1; + const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 5; + + lazy_static::lazy_static! { + static ref VIRTUAL_DISPLAY_MANAGER: Arc> = + Arc::new(Mutex::new(VirtualDisplayManager::default())); + } + + #[derive(Default)] + struct VirtualDisplayManager { + headless_index_name: Option<(u32, String)>, + peer_index_name: HashMap, + is_driver_installed: bool, + } + + impl VirtualDisplayManager { + fn prepare_driver(&mut self) -> ResultType<()> { + if !self.is_driver_installed { + self.install_update_driver()?; + } + Ok(()) + } + + fn install_update_driver(&mut self) -> ResultType<()> { + if let Err(e) = virtual_display::create_device() { + if !e.to_string().contains("Device is already created") { + bail!("Create device failed {}", e); } - break; + } + // Reboot is not required for this case. + let mut _reboot_required = false; + virtual_display::install_update_driver(&mut _reboot_required)?; + self.is_driver_installed = true; + Ok(()) + } + + fn plug_in_monitor(index: u32, modes: &[virtual_display::MonitorMode]) -> ResultType<()> { + if let Err(e) = virtual_display::plug_in_monitor(index) { + bail!("Plug in monitor failed {}", e); + } + if let Err(e) = virtual_display::update_monitor_modes(index, &modes) { + log::error!("Update monitor modes failed {}", e); + } + Ok(()) + } + } + + pub fn install_update_driver() -> ResultType<()> { + VIRTUAL_DISPLAY_MANAGER + .lock() + .unwrap() + .install_update_driver() + } + + #[inline] + fn get_device_names() -> Vec { + windows::get_device_names(Some(super::RUSTDESK_IDD_DEVICE_STRING)) + } + + pub fn plug_in_headless() -> ResultType<()> { + let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + manager.prepare_driver()?; + let modes = [virtual_display::MonitorMode { + width: 1920, + height: 1080, + sync: 60, + }]; + let device_names = get_device_names().into_iter().collect(); + VirtualDisplayManager::plug_in_monitor(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, &modes)?; + let device_name = get_new_device_name(&device_names); + manager.headless_index_name = Some((VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, device_name)); + Ok(()) + } + + pub fn plug_out_headless() -> bool { + let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + if let Some((index, _)) = manager.headless_index_name.take() { + if let Err(e) = virtual_display::plug_out_monitor(index) { + log::error!("Plug out monitor failed {}", e); + } + true + } else { + false + } + } + + fn get_new_device_name(device_names: &HashSet) -> String { + for _ in 0..3 { + let device_names_af: HashSet = get_device_names().into_iter().collect(); + let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); + if diff_names.len() == 1 { + return diff_names[0].clone(); + } else if diff_names.len() > 1 { + log::error!( + "Failed to get diff device names after plugin virtual display, more than one diff names: {:?}", + &diff_names + ); + return "".to_string(); + } + // Sleep is needed here to wait for the virtual display to be ready. + std::thread::sleep(std::time::Duration::from_millis(50)); + } + log::error!("Failed to get diff device names after plugin virtual display",); + "".to_string() + } + + pub fn get_virtual_displays() -> Vec { + VIRTUAL_DISPLAY_MANAGER + .lock() + .unwrap() + .peer_index_name + .keys() + .cloned() + .collect() + } + + pub fn plug_in_index_modes( + idx: u32, + mut modes: Vec, + ) -> ResultType<()> { + let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + manager.prepare_driver()?; + if !manager.peer_index_name.contains_key(&idx) { + let device_names = get_device_names().into_iter().collect(); + if modes.is_empty() { + modes.push(virtual_display::MonitorMode { + width: 1920, + height: 1080, + sync: 60, + }); + } + match VirtualDisplayManager::plug_in_monitor(idx, modes.as_slice()) { + Ok(_) => { + let device_name = get_new_device_name(&device_names); + manager.peer_index_name.insert(idx, device_name); + } + Err(e) => { + log::error!("Plug in monitor failed {}", e); + } + } + } + Ok(()) + } + + pub fn reset_all() -> ResultType<()> { + if super::is_virtual_display_supported() { + return Ok(()); + } + + if let Err(e) = plug_out_peer_request(&get_virtual_displays()) { + log::error!("Failed to plug out virtual displays: {}", e); + } + let _ = plug_out_headless(); + Ok(()) + } + + pub fn plug_in_peer_request( + modes: Vec>, + ) -> ResultType> { + let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + manager.prepare_driver()?; + + let mut indices: Vec = Vec::new(); + for m in modes.iter() { + for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT { + if !manager.peer_index_name.contains_key(&idx) { + let device_names = get_device_names().into_iter().collect(); + match VirtualDisplayManager::plug_in_monitor(idx, m) { + Ok(_) => { + let device_name = get_new_device_name(&device_names); + manager.peer_index_name.insert(idx, device_name); + indices.push(idx); + } + Err(e) => { + log::error!("Plug in monitor failed {}", e); + } + } + break; + } + } + } + + Ok(indices) + } + + pub fn plug_out_peer_request(indices: &[u32]) -> ResultType<()> { + let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + for idx in indices.iter() { + if manager.peer_index_name.contains_key(idx) { + allow_err!(virtual_display::plug_out_monitor(*idx)); + manager.peer_index_name.remove(idx); + } + } + Ok(()) + } + + pub fn is_virtual_display(name: &str) -> bool { + let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + if let Some((_, device_name)) = &lock.headless_index_name { + if windows::is_device_name(device_name, name) { + return true; + } + } + for (_, v) in lock.peer_index_name.iter() { + if windows::is_device_name(v, name) { + return true; + } + } + false + } + + fn change_resolution(index: u32, w: u32, h: u32) -> bool { + let modes = [virtual_display::MonitorMode { + width: w, + height: h, + sync: 60, + }]; + match virtual_display::update_monitor_modes(index, &modes) { + Ok(_) => true, + Err(e) => { + log::error!("Update monitor {} modes {:?} failed: {}", index, &modes, e); + false } } } - Ok(indices) -} + pub fn change_resolution_if_is_virtual_display(name: &str, w: u32, h: u32) -> Option { + let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + if let Some((index, device_name)) = &lock.headless_index_name { + if windows::is_device_name(device_name, name) { + return Some(change_resolution(*index, w, h)); + } + } -pub fn plug_out_peer_request(indices: &[u32]) -> ResultType<()> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - for idx in indices.iter() { - if manager.peer_index_name.contains_key(idx) { - allow_err!(virtual_display::plug_out_monitor(*idx)); - manager.peer_index_name.remove(idx); - } - } - Ok(()) -} - -pub fn is_virtual_display(name: &str) -> bool { - let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - if let Some((_, device_name)) = &lock.headless_index_name { - if windows::is_device_name(device_name, name) { - return true; - } - } - for (_, v) in lock.peer_index_name.iter() { - if windows::is_device_name(v, name) { - return true; - } - } - false -} - -fn change_resolution(index: u32, w: u32, h: u32) -> bool { - let modes = [virtual_display::MonitorMode { - width: w, - height: h, - sync: 60, - }]; - match virtual_display::update_monitor_modes(index, &modes) { - Ok(_) => true, - Err(e) => { - log::error!("Update monitor {} modes {:?} failed: {}", index, &modes, e); - false + for (k, v) in lock.peer_index_name.iter() { + if windows::is_device_name(v, name) { + return Some(change_resolution(*k, w, h)); + } } + None } } -pub fn change_resolution_if_is_virtual_display(name: &str, w: u32, h: u32) -> Option { - let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - if let Some((index, device_name)) = &lock.headless_index_name { - if windows::is_device_name(device_name, name) { - return Some(change_resolution(*index, w, h)); +pub mod amyuni_idd { + use super::windows; + use crate::platform::windows::get_amyuni_exe_name; + use hbb_common::{bail, lazy_static, log, ResultType}; + use std::{ + ptr::null_mut, + sync::{Arc, Mutex}, + }; + use winapi::um::shellapi::ShellExecuteA; + + lazy_static::lazy_static! { + static ref LOCK: Arc> = Default::default(); + } + + fn run_deviceinstaller(args: &str) -> ResultType<()> { + let Some(exe_name) = get_amyuni_exe_name() else { + bail!("Cannot get amyuni exe name.") + }; + + let cur_exe = std::env::current_exe()?; + let Some(cur_dir) = cur_exe.parent() else { + bail!("Cannot get parent of current exe file."); + }; + + let work_dir = cur_dir.join("usbmmidd_v2"); + if !work_dir.exists() { + bail!("usbmmidd_v2 does not exist.",); + } + let Some(work_dir) = work_dir.to_str() else { + bail!("Cannot convert work_dir to string."); + }; + let mut work_dir2 = work_dir.as_bytes().to_vec(); + work_dir2.push(0); + + unsafe { + const SW_HIDE: i32 = 0; + let mut args = args.bytes().collect::>(); + args.push(0); + let mut exe_name = exe_name.bytes().collect::>(); + exe_name.push(0); + let hi = ShellExecuteA( + null_mut(), + "open\0".as_ptr() as _, + exe_name.as_ptr() as _, + args.as_ptr() as _, + work_dir2.as_ptr() as _, + SW_HIDE, + ) as i32; + if hi <= 32 { + log::error!("Failed to run deviceinstaller: {}", hi); + bail!("Failed to run deviceinstaller.") + } + Ok(()) } } - for (k, v) in lock.peer_index_name.iter() { - if windows::is_device_name(v, name) { - return Some(change_resolution(*k, w, h)); + fn check_install_driver() -> ResultType<()> { + let _l = LOCK.lock().unwrap(); + let drivers = windows::get_display_drivers(); + if drivers + .iter() + .any(|(s, c)| s == super::AMYUNI_IDD_DEVICE_STRING && *c == 0) + { + return Ok(()); } + + run_deviceinstaller("install usbmmidd.inf usbmmidd") + } + + pub fn plug_in_headless() -> ResultType<()> { + if get_monitor_count() > 0 { + return Ok(()); + } + + if let Err(e) = check_install_driver() { + log::error!("Failed to install driver: {}", e); + bail!("Failed to install driver."); + } + + run_deviceinstaller("enableidd 1") + } + + pub fn plug_in_monitor() -> ResultType<()> { + if let Err(e) = check_install_driver() { + log::error!("Failed to install driver: {}", e); + bail!("Failed to install driver."); + } + + if get_monitor_count() == 4 { + bail!("There are already 4 monitors plugged in."); + } + + run_deviceinstaller("enableidd 1") + } + + pub fn plug_out_monitor(index: i32) -> ResultType<()> { + let all_count = windows::get_device_names(None).len(); + let amyuni_count = get_monitor_count(); + let mut to_plug_out_count = match all_count { + 0 => return Ok(()), + 1 => { + if amyuni_count == 0 { + bail!("No virtual displays to plug out.") + } else { + if super::is_can_plug_out_all() { + 1 + } else { + bail!("This only virtual display cannot be pulled out.") + } + } + } + _ => { + if all_count == amyuni_count { + if super::is_can_plug_out_all() { + all_count + } else { + all_count - 1 + } + } else { + amyuni_count + } + } + }; + if to_plug_out_count != 0 && index != -1 { + to_plug_out_count = 1; + } + for _i in 0..to_plug_out_count { + let _ = run_deviceinstaller(&format!("enableidd 0")); + } + Ok(()) + } + + #[inline] + pub fn get_monitor_count() -> usize { + windows::get_device_names(Some(super::AMYUNI_IDD_DEVICE_STRING)).len() } - None } mod windows { - use std::{collections::HashSet, ptr::null_mut}; + use std::ptr::null_mut; use winapi::{ - shared::minwindef::{DWORD, FALSE}, + shared::{ + devguid::GUID_DEVCLASS_DISPLAY, + minwindef::{DWORD, FALSE}, + ntdef::ULONG, + }, um::{ + cfgmgr32::{CM_Get_DevNode_Status, CR_SUCCESS}, + cguid::GUID_NULL, + setupapi::{ + SetupDiEnumDeviceInfo, SetupDiGetClassDevsW, SetupDiGetDeviceRegistryPropertyW, + SP_DEVINFO_DATA, + }, wingdi::{ DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_MIRRORING_DRIVER, }, + winnt::HANDLE, winuser::{EnumDisplayDevicesW, EnumDisplaySettingsExW, ENUM_CURRENT_SETTINGS}, }, }; - // This string is defined here. - // https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 - const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; + const DIGCF_PRESENT: DWORD = 0x00000002; + const SPDRP_DEVICEDESC: DWORD = 0x00000000; + const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE; #[inline] pub(super) fn is_device_name(device_name: &str, name: &str) -> bool { @@ -281,8 +576,8 @@ mod windows { } } - pub(super) fn get_device_names() -> HashSet { - let mut device_names = HashSet::new(); + pub(super) fn get_device_names(device_string: Option<&str>) -> Vec { + let mut device_names = Vec::new(); let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; dd.cb = std::mem::size_of::() as DWORD; let mut i_dev_num = 0; @@ -317,15 +612,115 @@ mod windows { continue; } - if let (Ok(device_name), Ok(device_string)) = ( + if let (Ok(device_name), Ok(ds)) = ( String::from_utf16(&dd.DeviceName), String::from_utf16(&dd.DeviceString), ) { - if &device_string[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING { - device_names.insert(device_name); + if let Some(s) = device_string { + if ds.len() >= s.len() && &ds[..s.len()] == s { + device_names.push(device_name); + } + } else { + device_names.push(device_name); } } } device_names } + + pub(super) fn get_display_drivers() -> Vec<(String, u32)> { + let mut display_drivers: Vec<(String, u32)> = Vec::new(); + + let device_info_set = unsafe { + SetupDiGetClassDevsW( + &GUID_DEVCLASS_DISPLAY, + null_mut(), + null_mut(), + DIGCF_PRESENT, + ) + }; + + if device_info_set == INVALID_HANDLE_VALUE { + println!( + "Failed to get device information set. Error: {}", + std::io::Error::last_os_error() + ); + return display_drivers; + } + + let mut device_info_data = SP_DEVINFO_DATA { + cbSize: std::mem::size_of::() as u32, + ClassGuid: GUID_NULL, + DevInst: 0, + Reserved: 0, + }; + + let mut device_index = 0; + loop { + let result = unsafe { + SetupDiEnumDeviceInfo(device_info_set, device_index, &mut device_info_data) + }; + if result == 0 { + break; + } + + let mut data_type: DWORD = 0; + let mut required_size: DWORD = 0; + + // Get the required buffer size for the driver description + let mut buffer; + unsafe { + SetupDiGetDeviceRegistryPropertyW( + device_info_set, + &mut device_info_data, + SPDRP_DEVICEDESC, + &mut data_type, + null_mut(), + 0, + &mut required_size, + ); + + buffer = vec![0; required_size as usize / 2]; + SetupDiGetDeviceRegistryPropertyW( + device_info_set, + &mut device_info_data, + SPDRP_DEVICEDESC, + &mut data_type, + buffer.as_mut_ptr() as *mut u8, + required_size, + null_mut(), + ); + } + + let Ok(driver_description) = String::from_utf16(&buffer) else { + println!("Failed to convert driver description to string"); + device_index += 1; + continue; + }; + + let mut status: ULONG = 0; + let mut problem_number: ULONG = 0; + // Get the device status and problem number + let config_ret = unsafe { + CM_Get_DevNode_Status( + &mut status, + &mut problem_number, + device_info_data.DevInst, + 0, + ) + }; + if config_ret != CR_SUCCESS { + println!( + "Failed to get device status. Error: {}", + std::io::Error::last_os_error() + ); + device_index += 1; + continue; + } + display_drivers.push((driver_description, problem_number)); + device_index += 1; + } + + display_drivers + } }