refact: win, virtual display (#7767)

* refact: win, virtual display

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Update flutter-build.yml

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
fufesou 2024-04-19 11:31:52 +08:00 committed by GitHub
parent a3c0911529
commit e83c28bf54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1113 additions and 290 deletions

View File

@ -113,7 +113,18 @@ jobs:
shell: bash shell: bash
- name: Build rustdesk - 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 - name: find Runner.res
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res # Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res

View File

@ -100,7 +100,7 @@ system_shutdown = "4.0"
qrcode-generator = "4.1" qrcode-generator = "4.1"
[target.'cfg(target_os = "windows")'.dependencies] [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" winreg = "0.11"
windows-service = "0.6" windows-service = "0.6"
virtual_display = { path = "libs/virtual_display", optional = true } virtual_display = { path = "libs/virtual_display", optional = true }

View File

@ -153,6 +153,12 @@ def make_parser():
action='store_true', action='store_true',
help='Skip packing, only flutter version + Windows supported' 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( parser.add_argument(
"--package", "--package",
type=str type=str
@ -293,6 +299,9 @@ def get_features(args):
features.append('appimage') features.append('appimage')
if args.unix_file_copy_paste: if args.unix_file_copy_paste:
features.append('unix-file-copy-paste') features.append('unix-file-copy-paste')
if windows:
if args.virtual_display:
features.append('virtual_display_driver')
print("features:", features) print("features:", features)
return features return features

View File

@ -19,7 +19,9 @@ const kKeyTranslateMode = 'translate';
const String kPlatformAdditionsIsWayland = "is_wayland"; const String kPlatformAdditionsIsWayland = "is_wayland";
const String kPlatformAdditionsHeadless = "headless"; const String kPlatformAdditionsHeadless = "headless";
const String kPlatformAdditionsIsInstalled = "is_installed"; 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 kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
const String kPlatformAdditionsSupportedPrivacyModeImpl = const String kPlatformAdditionsSupportedPrivacyModeImpl =
"supported_privacy_mode_impl"; "supported_privacy_mode_impl";
@ -121,12 +123,11 @@ double kNewWindowOffset = isWindows
? 30.0 ? 30.0
: 50.0; : 50.0;
EdgeInsets get kDragToResizeAreaPadding => EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && isLinux
!kUseCompatibleUiMode && isLinux ? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value ? EdgeInsets.zero
? EdgeInsets.zero : EdgeInsets.all(5.0)
: EdgeInsets.all(5.0) : EdgeInsets.zero;
: EdgeInsets.zero;
// https://en.wikipedia.org/wiki/Non-breaking_space // https://en.wikipedia.org/wiki/Non-breaking_space
const int $nbsp = 0x00A0; const int $nbsp = 0x00A0;

View File

@ -1058,11 +1058,16 @@ class _DisplayMenuState extends State<_DisplayMenu> {
ffi: widget.ffi, ffi: widget.ffi,
screenAdjustor: _screenAdjustor, screenAdjustor: _screenAdjustor,
), ),
// We may add this feature if it is needed and we have an EV certificate. if (pi.isRustDeskIdd)
// _VirtualDisplayMenu( _RustDeskVirtualDisplayMenu(
// id: widget.id, id: widget.id,
// ffi: widget.ffi, ffi: widget.ffi,
// ), ),
if (pi.isAmyuniIdd)
_AmyuniVirtualDisplayMenu(
id: widget.id,
ffi: widget.ffi,
),
Divider(), Divider(),
toggles(), toggles(),
]; ];
@ -1540,21 +1545,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
} }
} }
class _VirtualDisplayMenu extends StatefulWidget { class _RustDeskVirtualDisplayMenu extends StatefulWidget {
final String id; final String id;
final FFI ffi; final FFI ffi;
_VirtualDisplayMenu({ _RustDeskVirtualDisplayMenu({
Key? key, Key? key,
required this.id, required this.id,
required this.ffi, required this.ffi,
}) : super(key: key); }) : super(key: key);
@override @override
State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState(); State<_RustDeskVirtualDisplayMenu> createState() =>
_RustDeskVirtualDisplayMenuState();
} }
class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> { class _RustDeskVirtualDisplayMenuState
extends State<_RustDeskVirtualDisplayMenu> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -1569,7 +1576,7 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
return Offstage(); return Offstage();
} }
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays; final virtualDisplays = widget.ffi.ffiModel.pi.RustDeskVirtualDisplays;
final privacyModeState = PrivacyModeState.find(widget.id); final privacyModeState = PrivacyModeState.find(widget.id);
final children = <Widget>[]; final children = <Widget>[];
@ -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 = <Widget>[
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 { class _KeyboardMenu extends StatelessWidget {
final String id; final String id;
final FFI ffi; final FFI ffi;

View File

@ -561,8 +561,12 @@ class FfiModel with ChangeNotifier {
showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId); showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId);
} else if (text == 'Connected, waiting for image...') { } else if (text == 'Connected, waiting for image...') {
showConnectedWaitingForImage(dialogManager, sessionId, type, title, text); 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 { } else {
var hasRetry = evt['hasRetry'] == 'true'; final hasRetry = evt['hasRetry'] == 'true';
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager); showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
} }
} }
@ -657,6 +661,27 @@ class FfiModel with ChangeNotifier {
bind.sessionOnWaitingForImageDialogShow(sessionId: sessionId); 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) { _updateSessionWidthHeight(SessionID sessionId) {
if (_rect == null) return; if (_rect == null) return;
if (_rect!.width <= 0 || _rect!.height <= 0) { if (_rect!.width <= 0 || _rect!.height <= 0) {
@ -986,15 +1011,21 @@ class FfiModel with ChangeNotifier {
} }
if (updateData.isEmpty) { if (updateData.isEmpty) {
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays); _pi.platformAdditions.remove(kPlatformAdditionsRustDeskVirtualDisplays);
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
} else { } else {
try { try {
final updateJson = json.decode(updateData) as Map<String, dynamic>; final updateJson = json.decode(updateData) as Map<String, dynamic>;
for (final key in updateJson.keys) { for (final key in updateJson.keys) {
_pi.platformAdditions[key] = updateJson[key]; _pi.platformAdditions[key] = updateJson[key];
} }
if (!updateJson.containsKey(kPlatformAdditionsVirtualDisplays)) { if (!updateJson
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays); .containsKey(kPlatformAdditionsRustDeskVirtualDisplays)) {
_pi.platformAdditions
.remove(kPlatformAdditionsRustDeskVirtualDisplays);
}
if (!updateJson.containsKey(kPlatformAdditionsAmyuniVirtualDisplays)) {
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
} }
} catch (e) { } catch (e) {
debugPrint('Failed to decode platformAdditions $e'); debugPrint('Failed to decode platformAdditions $e');
@ -2490,14 +2521,21 @@ class PeerInfo with ChangeNotifier {
bool get isInstalled => bool get isInstalled =>
platform != kPeerPlatformWindows || platform != kPeerPlatformWindows ||
platformAdditions[kPlatformAdditionsIsInstalled] == true; platformAdditions[kPlatformAdditionsIsInstalled] == true;
List<int> get virtualDisplays => List<int>.from( List<int> get RustDeskVirtualDisplays => List<int>.from(
platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []); platformAdditions[kPlatformAdditionsRustDeskVirtualDisplays] ?? []);
int get amyuniVirtualDisplayCount =>
platformAdditions[kPlatformAdditionsAmyuniVirtualDisplays] ?? 0;
bool get isSupportMultiDisplay => bool get isSupportMultiDisplay =>
(isDesktop || isWebDesktop) && isSupportMultiUiSession; (isDesktop || isWebDesktop) && isSupportMultiUiSession;
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false; bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
bool get isRustDeskIdd =>
platformAdditions[kPlatformAdditionsIddImpl] == 'rustdesk_idd';
bool get isAmyuniIdd =>
platformAdditions[kPlatformAdditionsIddImpl] == 'amyuni_idd';
Display? tryGetDisplay() { Display? tryGetDisplay() {
if (displays.isEmpty) { if (displays.isEmpty) {
return null; return null;

View File

@ -591,3 +591,68 @@ LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er); 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);
}

View File

@ -11,3 +11,4 @@ EXPORTS
TryDeleteStartupShortcut TryDeleteStartupShortcut
SetPropertyFromConfig SetPropertyFromConfig
AddRegSoftwareSASGeneration AddRegSoftwareSASGeneration
RemoveAmyuniIdd

View File

@ -29,6 +29,7 @@
<CustomAction Id="SetPropertyServiceStop.SetParam.ConfigKey" Return="check" Property="ConfigKey" Value="stop-service" /> <CustomAction Id="SetPropertyServiceStop.SetParam.ConfigKey" Return="check" Property="ConfigKey" Value="stop-service" />
<CustomAction Id="SetPropertyServiceStop.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" /> <CustomAction Id="SetPropertyServiceStop.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" />
<CustomAction Id="TryDeleteStartupShortcut.SetParam" Return="check" Property="ShortcutName" Value="$(var.Product) Tray" /> <CustomAction Id="TryDeleteStartupShortcut.SetParam" Return="check" Property="ShortcutName" Value="$(var.Product) Tray" />
<CustomAction Id="RemoveAmyuniIdd.SetParam" Return="check" Property="RemoveAmyuniIdd" Value="[INSTALLFOLDER]" />
<InstallExecuteSequence> <InstallExecuteSequence>
<Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" /> <Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" />
@ -68,10 +69,12 @@
<Custom Action="RemoveFirewallRules" Before="RemoveFiles"/> <Custom Action="RemoveFirewallRules" Before="RemoveFiles"/>
<Custom Action="RemoveFirewallRules.SetParam" Before="RemoveFirewallRules"/> <Custom Action="RemoveFirewallRules.SetParam" Before="RemoveFirewallRules"/>
<Custom Action="TerminateProcesses" Before="RemoveFiles"/> <Custom Action="TerminateProcesses" Before="RemoveInstallFolder"/>
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/> <Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
<Custom Action="TerminateBrokers" Before="RemoveFiles"/> <Custom Action="TerminateBrokers" Before="RemoveInstallFolder"/>
<Custom Action="TerminateBrokers.SetParam" Before="TerminateBrokers"/> <Custom Action="TerminateBrokers.SetParam" Before="TerminateBrokers"/>
<Custom Action="RemoveAmyuniIdd" Before="RemoveInstallFolder"/>
<Custom Action="RemoveAmyuniIdd.SetParam" Before="RemoveAmyuniIdd"/>
</InstallExecuteSequence> </InstallExecuteSequence>
<!-- Shortcuts --> <!-- Shortcuts -->

View File

@ -16,5 +16,6 @@
<CustomAction Id="TryDeleteStartupShortcut" DllEntry="TryDeleteStartupShortcut" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/> <CustomAction Id="TryDeleteStartupShortcut" DllEntry="TryDeleteStartupShortcut" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="SetPropertyServiceStop" DllEntry="SetPropertyFromConfig" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/> <CustomAction Id="SetPropertyServiceStop" DllEntry="SetPropertyFromConfig" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="AddRegSoftwareSASGeneration" DllEntry="AddRegSoftwareSASGeneration" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/> <CustomAction Id="AddRegSoftwareSASGeneration" DllEntry="AddRegSoftwareSASGeneration" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="RemoveAmyuniIdd" DllEntry="RemoveAmyuniIdd" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
</Fragment> </Fragment>
</Wix> </Wix>

View File

@ -215,7 +215,9 @@ pub fn core_main() -> Option<Vec<String>> {
} else if args[0] == "--install-idd" { } else if args[0] == "--install-idd" {
#[cfg(all(windows, feature = "virtual_display_driver"))] #[cfg(all(windows, feature = "virtual_display_driver"))]
if crate::virtual_display_manager::is_virtual_display_supported() { 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; return None;
} else if args[0] == "--portable-service" { } else if args[0] == "--portable-service" {
@ -254,7 +256,7 @@ pub fn core_main() -> Option<Vec<String>> {
} 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"))] #[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"))] #[cfg(any(target_os = "linux", target_os = "windows"))]
{ {
crate::start_server(true); crate::start_server(true);

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "所有人"), ("Everyone", "所有人"),
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"), ("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"), ("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"),
("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Každý"), ("Everyone", "Každý"),
("ab_web_console_tip", "Více na webové konzoli"), ("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"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Jeder"), ("Everyone", "Jeder"),
("ab_web_console_tip", "Mehr über Webkonsole"), ("ab_web_console_tip", "Mehr über Webkonsole"),
("allow-only-conn-window-open-tip", "Verbindung nur zulassen, wenn das RustDesk-Fenster geöffnet ist"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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."), ("share_warning_tip", "The fields above are shared and visible to others."),
("ab_web_console_tip", "More on web console"), ("ab_web_console_tip", "More on web console"),
("allow-only-conn-window-open-tip", "Only allow connection if RustDesk window is open"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Todos"), ("Everyone", "Todos"),
("ab_web_console_tip", "Más en consola web"), ("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"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "هر کس"), ("Everyone", "هر کس"),
("ab_web_console_tip", "اطلاعات بیشتر در کنسول وب"), ("ab_web_console_tip", "اطلاعات بیشتر در کنسول وب"),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Svatko"), ("Everyone", "Svatko"),
("ab_web_console_tip", "Više na web konzoli"), ("ab_web_console_tip", "Više na web konzoli"),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Everyone"), ("Everyone", "Everyone"),
("ab_web_console_tip", "Altre info sulla console web"), ("ab_web_console_tip", "Altre info sulla console web"),
("allow-only-conn-window-open-tip", "Consenti la connessione solo se la finestra RustDesk è aperta"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Visi"), ("Everyone", "Visi"),
("ab_web_console_tip", "Vairāk par tīmekļa konsoli"), ("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"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Iedereen"), ("Everyone", "Iedereen"),
("ab_web_console_tip", "Meer over de webconsole"), ("ab_web_console_tip", "Meer over de webconsole"),
("allow-only-conn-window-open-tip", "Alleen verbindingen toestaan als het RustDesk-venster geopend is"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Wszyscy"), ("Everyone", "Wszyscy"),
("ab_web_console_tip", "Więcej w konsoli web"), ("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"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Todos"), ("Everyone", "Todos"),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Все"), ("Everyone", "Все"),
("ab_web_console_tip", "Больше в веб-консоли"), ("ab_web_console_tip", "Больше в веб-консоли"),
("allow-only-conn-window-open-tip", "Разрешать подключение только при открытом окне RustDesk"), ("allow-only-conn-window-open-tip", "Разрешать подключение только при открытом окне RustDesk"),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Každý"), ("Everyone", "Každý"),
("ab_web_console_tip", "Viac na webovej konzole"), ("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"), ("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(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "所有人"), ("Everyone", "所有人"),
("ab_web_console_tip", "打開 Web 控制台以進行更多操作"), ("ab_web_console_tip", "打開 Web 控制台以進行更多操作"),
("allow-only-conn-window-open-tip", "只在 RustDesk 視窗開啟時允許連接"), ("allow-only-conn-window-open-tip", "只在 RustDesk 視窗開啟時允許連接"),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", "Всі"), ("Everyone", "Всі"),
("ab_web_console_tip", "Детальніше про веб-консоль"), ("ab_web_console_tip", "Детальніше про веб-консоль"),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -601,5 +601,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Everyone", ""), ("Everyone", ""),
("ab_web_console_tip", ""), ("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""), ("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -669,4 +669,48 @@ extern "C"
AllocConsole(); AllocConsole();
freopen("CONOUT$", "w", stdout); 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<LPBYTE>(&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" } // 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;
}
}
}

View File

@ -64,8 +64,6 @@ use windows_service::{
use winreg::enums::*; use winreg::enums::*;
use winreg::RegKey; use winreg::RegKey;
pub const DRIVER_CERT_FILE: &str = "RustDeskIddDriver.cer";
pub fn get_cursor_pos() -> Option<(i32, i32)> { pub fn get_cursor_pos() -> Option<(i32, i32)> {
unsafe { unsafe {
#[allow(invalid_value)] #[allow(invalid_value)]
@ -462,6 +460,8 @@ extern "C" {
fn is_win_down() -> BOOL; fn is_win_down() -> BOOL;
fn is_local_system() -> BOOL; fn is_local_system() -> BOOL;
fn alloc_console_and_redirect(); fn alloc_console_and_redirect();
fn get_native_machine() -> i32;
fn is_service_running_w(svc_name: *const u16) -> bool;
} }
extern "system" { extern "system" {
@ -1296,12 +1296,14 @@ fn get_uninstall(kill_self: bool) -> String {
{before_uninstall} {before_uninstall}
{uninstall_cert_cmd} {uninstall_cert_cmd}
reg delete {subkey} /f reg delete {subkey} /f
{uninstall_amyuni_idd}
if exist \"{path}\" rd /s /q \"{path}\" if exist \"{path}\" rd /s /q \"{path}\"
if exist \"{start_menu}\" rd /s /q \"{start_menu}\" 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 \"%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\" 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), before_uninstall=get_before_uninstall(kill_self),
uninstall_amyuni_idd=get_uninstall_amyuni_idd(&path),
app_name = crate::get_app_name(), app_name = crate::get_app_name(),
) )
} }
@ -2367,3 +2369,81 @@ impl Drop for WallPaperRemover {
allow_err!(Self::set_wallpaper(Some(self.old_path.clone()))); 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<MachineArch, io::Error> {
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<String> {
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 _)
}
}

View File

@ -6,9 +6,12 @@ use crate::{
display_service, display_service,
ipc::{connect, Data}, ipc::{connect, Data},
}; };
#[cfg(windows)] use hbb_common::{
use hbb_common::tokio; anyhow::anyhow,
use hbb_common::{anyhow::anyhow, bail, lazy_static, ResultType}; bail, lazy_static,
tokio::{self, sync::oneshot},
ResultType,
};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -30,10 +33,10 @@ mod win_virtual_display;
pub use win_virtual_display::restore_reg_connectivity; pub use win_virtual_display::restore_reg_connectivity;
pub const INVALID_PRIVACY_MODE_CONN_ID: i32 = 0; 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 = pub const TURN_OFF_OTHER_ID: &'static str =
"Failed to turn off privacy mode that belongs to someone else"; "Failed to turn off privacy mode that belongs to someone else.";
pub const NO_DISPLAYS: &'static str = "No displays"; pub const NO_PHYSICAL_DISPLAYS: &'static str = "no_need_privacy_mode_no_physical_displays_tip";
#[cfg(windows)] #[cfg(windows)]
pub const PRIVACY_MODE_IMPL_WIN_MAG: &str = win_mag::PRIVACY_MODE_IMPL; 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 { pub trait PrivacyMode: Sync + Send {
fn is_async_privacy_mode(&self) -> bool;
fn init(&self) -> ResultType<()>; fn init(&self) -> ResultType<()>;
fn clear(&mut self); fn clear(&mut self);
fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType<bool>; fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType<bool>;
@ -203,7 +208,40 @@ fn get_supported_impl(impl_key: &str) -> String {
} }
#[inline] #[inline]
pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option<ResultType<bool>> { pub async fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option<ResultType<bool>> {
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<ResultType<bool>> {
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<ResultType<bool>> {
// Check if privacy mode is already on or occupied by another one // Check if privacy mode is already on or occupied by another one
let mut privacy_mode_lock = PRIVACY_MODE.lock().unwrap(); 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")] #[cfg(feature = "virtual_display_driver")]
if is_installed() { if is_installed() && crate::platform::windows::is_self_service_running() {
vec_impls.push(( vec_impls.push((
PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY, PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY,
"privacy_mode_impl_virtual_display_tip", "privacy_mode_impl_virtual_display_tip",

View File

@ -72,6 +72,10 @@ pub struct PrivacyModeImpl {
} }
impl PrivacyMode for PrivacyModeImpl { impl PrivacyMode for PrivacyModeImpl {
fn is_async_privacy_mode(&self) -> bool {
false
}
fn init(&self) -> ResultType<()> { fn init(&self) -> ResultType<()> {
Ok(()) Ok(())
} }

View File

@ -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 crate::virtual_display_manager;
use hbb_common::{allow_err, bail, config::Config, log, ResultType}; use hbb_common::{allow_err, bail, config::Config, log, ResultType};
use std::{ use std::{
io::Error, io::Error,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
thread,
time::Duration,
}; };
use virtual_display::MonitorMode; use virtual_display::MonitorMode;
use winapi::{ use winapi::{
@ -27,7 +29,6 @@ use winapi::{
pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_virtual_display"; 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"; const CONFIG_KEY_REG_RECOVERY: &str = "reg_recovery";
struct Display { struct Display {
@ -137,8 +138,9 @@ impl PrivacyModeImpl {
primary, primary,
}; };
if let Ok(s) = std::string::String::from_utf16(&dd.DeviceString) { let ds = virtual_display_manager::get_cur_device_string();
if &s[..IDD_DEVICE_STRING.len()] == IDD_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); self.virtual_displays.push(display);
continue; continue;
} }
@ -155,7 +157,7 @@ impl PrivacyModeImpl {
} }
fn restore_plug_out_monitor(&mut self) { 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(); self.virtual_displays_added.clear();
} }
@ -304,8 +306,18 @@ impl PrivacyModeImpl {
if self.virtual_displays.is_empty() { if self.virtual_displays.is_empty() {
let displays = let displays =
virtual_display_manager::plug_in_peer_request(vec![Self::default_display_modes()])?; 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(); 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(()) Ok(())
@ -348,6 +360,10 @@ impl PrivacyModeImpl {
} }
impl PrivacyMode for PrivacyModeImpl { impl PrivacyMode for PrivacyModeImpl {
fn is_async_privacy_mode(&self) -> bool {
virtual_display_manager::is_amyuni_idd()
}
fn init(&self) -> ResultType<()> { fn init(&self) -> ResultType<()> {
Ok(()) Ok(())
} }
@ -367,8 +383,8 @@ impl PrivacyMode for PrivacyModeImpl {
} }
self.set_displays(); self.set_displays();
if self.displays.is_empty() { if self.displays.is_empty() {
log::debug!("No displays"); log::debug!("{}", NO_PHYSICAL_DISPLAYS);
bail!(NO_DISPLAYS); bail!(NO_PHYSICAL_DISPLAYS);
} }
let mut guard = TurnOnGuard { let mut guard = TurnOnGuard {
@ -379,7 +395,7 @@ impl PrivacyMode for PrivacyModeImpl {
guard.ensure_virtual_display()?; guard.ensure_virtual_display()?;
if guard.virtual_displays.is_empty() { if guard.virtual_displays.is_empty() {
log::debug!("No virtual displays"); log::debug!("No virtual displays");
bail!("No virtual displays"); bail!("No virtual displays.");
} }
let reg_connectivity_1 = reg_display_settings::read_reg_connectivity()?; 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)?; self.check_off_conn_id(conn_id)?;
super::win_input::unhook()?; super::win_input::unhook()?;
self.restore(); self.restore();
restore_reg_connectivity(); restore_reg_connectivity(false);
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
if let Some(state) = state { 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()); 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); let config_recovery_value = Config::get_option(CONFIG_KEY_REG_RECOVERY);
if config_recovery_value.is_empty() { if config_recovery_value.is_empty() {
return; return;
} }
if plug_out_monitors {
let _ = virtual_display_manager::plug_out_monitor(-1);
}
if let Ok(reg_recovery) = if let Ok(reg_recovery) =
serde_json::from_str::<reg_display_settings::RegRecovery>(&config_recovery_value) serde_json::from_str::<reg_display_settings::RegRecovery>(&config_recovery_value)
{ {

View File

@ -1133,10 +1133,7 @@ impl Connection {
); );
#[cfg(feature = "virtual_display_driver")] #[cfg(feature = "virtual_display_driver")]
if crate::platform::is_installed() { if crate::platform::is_installed() {
let virtual_displays = virtual_display_manager::get_virtual_displays(); platform_additions.extend(virtual_display_manager::get_platform_additions());
if !virtual_displays.is_empty() {
platform_additions.insert("virtual_displays".into(), json!(&virtual_displays));
}
} }
platform_additions.insert( platform_additions.insert(
"supported_privacy_mode_impl".into(), "supported_privacy_mode_impl".into(),
@ -2595,8 +2592,7 @@ impl Connection {
self.send(make_msg("idd_not_support_under_win10_2004_tip".to_string())) self.send(make_msg("idd_not_support_under_win10_2004_tip".to_string()))
.await; .await;
} else { } else {
if let Err(e) = if let Err(e) = virtual_display_manager::plug_in_monitor(t.display as _, Vec::new())
virtual_display_manager::plug_in_index_modes(t.display as _, Vec::new())
{ {
log::error!("Failed to plug in virtual display: {}", e); log::error!("Failed to plug in virtual display: {}", e);
self.send(make_msg(format!( self.send(make_msg(format!(
@ -2607,13 +2603,8 @@ impl Connection {
} }
} }
} else { } else {
let indices = if t.display == -1 { if let Err(e) = virtual_display_manager::plug_out_monitor(t.display) {
virtual_display_manager::get_virtual_displays() log::error!("Failed to plug out virtual display {}: {}", t.display, e);
} 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);
self.send(make_msg(format!( self.send(make_msg(format!(
"Failed to plug out virtual displays: {}", "Failed to plug out virtual displays: {}",
e e
@ -2639,7 +2630,7 @@ impl Connection {
let name = display.name(); let name = display.name();
#[cfg(all(windows, feature = "virtual_display_driver"))] #[cfg(all(windows, feature = "virtual_display_driver"))]
if let Some(_ok) = 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, &name,
r.width as _, r.width as _,
r.height as _, r.height as _,
@ -2858,7 +2849,6 @@ impl Connection {
} else { } else {
let is_pre_privacy_on = privacy_mode::is_in_privacy_mode(); let is_pre_privacy_on = privacy_mode::is_in_privacy_mode();
let pre_impl_key = privacy_mode::get_cur_impl_key(); 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 is_pre_privacy_on {
if let Some(pre_impl_key) = pre_impl_key { 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 { match turn_on_res {
Some(Ok(res)) => { Some(Ok(res)) => {
if res { if res {
@ -2906,7 +2897,7 @@ impl Connection {
} }
Some(Err(e)) => { Some(Err(e)) => {
log::error!("Failed to turn on privacy mode. {}", 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( let _ = Self::turn_off_privacy_to_msg(
privacy_mode::INVALID_PRIVACY_MODE_CONN_ID, privacy_mode::INVALID_PRIVACY_MODE_CONN_ID,
); );

View File

@ -160,15 +160,8 @@ fn displays_to_msg(displays: Vec<DisplayInfo>) -> Message {
#[cfg(all(windows, feature = "virtual_display_driver"))] #[cfg(all(windows, feature = "virtual_display_driver"))]
if crate::platform::is_installed() { if crate::platform::is_installed() {
let virtual_displays = crate::virtual_display_manager::get_virtual_displays(); let m = crate::virtual_display_manager::get_platform_additions();
if !virtual_displays.is_empty() { pi.platform_additions = serde_json::to_string(&m).unwrap_or_default();
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());
}
} }
// current_display should not be used in server. // current_display should not be used in server.
@ -227,10 +220,11 @@ pub(super) fn get_original_resolution(
h: usize, h: usize,
) -> MessageField<Resolution> { ) -> MessageField<Resolution> {
#[cfg(all(windows, feature = "virtual_display_driver"))] #[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")))] #[cfg(not(all(windows, feature = "virtual_display_driver")))]
let is_virtual_display = false; let is_rustdesk_virtual_display = false;
Some(if is_virtual_display { Some(if is_rustdesk_virtual_display {
Resolution { Resolution {
width: 0, width: 0,
height: 0, height: 0,
@ -382,8 +376,10 @@ pub fn try_get_displays() -> ResultType<Vec<Display>> {
#[cfg(all(windows, feature = "virtual_display_driver"))] #[cfg(all(windows, feature = "virtual_display_driver"))]
pub fn try_get_displays() -> ResultType<Vec<Display>> { pub fn try_get_displays() -> ResultType<Vec<Display>> {
let mut displays = Display::all()?; 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() if crate::platform::is_installed()
&& no_displays(&displays) && no_displays_v
&& virtual_display_manager::is_virtual_display_supported() && virtual_display_manager::is_virtual_display_supported()
{ {
log::debug!("no displays, create virtual display"); log::debug!("no displays, create virtual display");

View File

@ -1,57 +1,47 @@
#[cfg(target_os = "windows")] use hbb_common::{bail, platform::windows::is_windows_version_or_greater, ResultType};
use hbb_common::platform::windows::is_windows_version_or_greater; use std::sync::atomic;
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. // This string is defined here.
const VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS: u32 = 0; // https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40
const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 1; pub const RUSTDESK_IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0";
const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 5; pub const AMYUNI_IDD_DEVICE_STRING: &'static str = "USB Mobile Monitor Virtual Display\0";
lazy_static::lazy_static! { const IDD_IMPL: &str = IDD_IMPL_AMYUNI;
static ref VIRTUAL_DISPLAY_MANAGER: Arc<Mutex<VirtualDisplayManager>> = const IDD_IMPL_RUSTDESK: &str = "rustdesk_idd";
Arc::new(Mutex::new(VirtualDisplayManager::default())); 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)] // No need to consider concurrency here.
struct VirtualDisplayManager { pub fn set_can_plug_out_all(v: bool) {
headless_index_name: Option<(u32, String)>, if IS_CAN_PLUG_OUT_ALL.load(atomic::Ordering::Relaxed) == IS_CAN_PLUG_OUT_ALL_NOT_SET {
peer_index_name: HashMap<u32, String>, IS_CAN_PLUG_OUT_ALL.store(
is_driver_installed: bool, if v {
IS_CAN_PLUG_OUT_ALL_YES
} else {
IS_CAN_PLUG_OUT_ALL_NO
},
atomic::Ordering::Relaxed,
);
}
} }
impl VirtualDisplayManager { pub fn is_amyuni_idd() -> bool {
fn prepare_driver(&mut self) -> ResultType<()> { IDD_IMPL == IDD_IMPL_AMYUNI
if !self.is_driver_installed { }
self.install_update_driver()?;
}
Ok(())
}
fn install_update_driver(&mut self) -> ResultType<()> { pub fn get_cur_device_string() -> &'static str {
if let Err(e) = virtual_display::create_device() { match IDD_IMPL {
if !e.to_string().contains("Device is already created") { IDD_IMPL_RUSTDESK => RUSTDESK_IDD_DEVICE_STRING,
bail!("Create device failed {}", e); IDD_IMPL_AMYUNI => AMYUNI_IDD_DEVICE_STRING,
} _ => "",
}
// 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(())
} }
} }
@ -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<()> { pub fn plug_in_headless() -> ResultType<()> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); match IDD_IMPL {
manager.prepare_driver()?; IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_headless(),
let modes = [virtual_display::MonitorMode { IDD_IMPL_AMYUNI => amyuni_idd::plug_in_headless(),
width: 1920, _ => bail!("Unsupported virtual display implementation."),
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
} }
} }
fn get_new_device_name(device_names: &HashSet<String>) -> String { pub fn get_platform_additions() -> serde_json::Map<String, serde_json::Value> {
for _ in 0..3 { let mut map = serde_json::Map::new();
let device_names_af = windows::get_device_names(); if !crate::platform::windows::is_self_service_running() {
let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); return map;
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",); map.insert("idd_impl".into(), serde_json::json!(IDD_IMPL));
"".to_string() match IDD_IMPL {
} IDD_IMPL_RUSTDESK => {
let virtual_displays = rustdesk_idd::get_virtual_displays();
pub fn get_virtual_displays() -> Vec<u32> { if !virtual_displays.is_empty() {
VIRTUAL_DISPLAY_MANAGER map.insert(
.lock() "rustdesk_virtual_displays".into(),
.unwrap() serde_json::json!(virtual_displays),
.peer_index_name );
.keys()
.cloned()
.collect()
}
pub fn plug_in_index_modes(
idx: u32,
mut modes: Vec<virtual_display::MonitorMode>,
) -> 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);
} }
} }
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<()> { #[inline]
if is_virtual_display_supported() { pub fn plug_in_monitor(idx: u32, modes: Vec<virtual_display::MonitorMode>) -> ResultType<()> {
return Ok(()); 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()) { pub fn plug_out_monitor(index: i32) -> ResultType<()> {
log::error!("Failed to plug out virtual displays: {}", e); 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<Vec<virtual_display::MonitorMode>>) -> ResultType<Vec<u32>> { pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> ResultType<Vec<u32>> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); match IDD_IMPL {
manager.prepare_driver()?; 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<u32> = Vec::new(); pub fn plug_out_monitor_indices(indices: &[u32]) -> ResultType<()> {
for m in modes.iter() { match IDD_IMPL {
for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT { IDD_IMPL_RUSTDESK => rustdesk_idd::plug_out_peer_request(indices),
if !manager.peer_index_name.contains_key(&idx) { IDD_IMPL_AMYUNI => {
let device_names = windows::get_device_names(); for _idx in indices.iter() {
match VirtualDisplayManager::plug_in_monitor(idx, m) { amyuni_idd::plug_out_monitor(0)?;
Ok(_) => { }
let device_name = get_new_device_name(&device_names); Ok(())
manager.peer_index_name.insert(idx, device_name); }
indices.push(idx); _ => bail!("Unsupported virtual display implementation."),
} }
Err(e) => { }
log::error!("Plug in monitor failed {}", e);
} 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<Mutex<VirtualDisplayManager>> =
Arc::new(Mutex::new(VirtualDisplayManager::default()));
}
#[derive(Default)]
struct VirtualDisplayManager {
headless_index_name: Option<(u32, String)>,
peer_index_name: HashMap<u32, String>,
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<String> {
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>) -> String {
for _ in 0..3 {
let device_names_af: HashSet<String> = 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<u32> {
VIRTUAL_DISPLAY_MANAGER
.lock()
.unwrap()
.peer_index_name
.keys()
.cloned()
.collect()
}
pub fn plug_in_index_modes(
idx: u32,
mut modes: Vec<virtual_display::MonitorMode>,
) -> 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<Vec<virtual_display::MonitorMode>>,
) -> ResultType<Vec<u32>> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
manager.prepare_driver()?;
let mut indices: Vec<u32> = 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<bool> {
} 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<()> { for (k, v) in lock.peer_index_name.iter() {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); if windows::is_device_name(v, name) {
for idx in indices.iter() { return Some(change_resolution(*k, w, h));
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
} }
None
} }
} }
pub fn change_resolution_if_is_virtual_display(name: &str, w: u32, h: u32) -> Option<bool> { pub mod amyuni_idd {
let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); use super::windows;
if let Some((index, device_name)) = &lock.headless_index_name { use crate::platform::windows::get_amyuni_exe_name;
if windows::is_device_name(device_name, name) { use hbb_common::{bail, lazy_static, log, ResultType};
return Some(change_resolution(*index, w, h)); use std::{
ptr::null_mut,
sync::{Arc, Mutex},
};
use winapi::um::shellapi::ShellExecuteA;
lazy_static::lazy_static! {
static ref LOCK: Arc<Mutex<()>> = 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::<Vec<_>>();
args.push(0);
let mut exe_name = exe_name.bytes().collect::<Vec<_>>();
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() { fn check_install_driver() -> ResultType<()> {
if windows::is_device_name(v, name) { let _l = LOCK.lock().unwrap();
return Some(change_resolution(*k, w, h)); 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 { mod windows {
use std::{collections::HashSet, ptr::null_mut}; use std::ptr::null_mut;
use winapi::{ use winapi::{
shared::minwindef::{DWORD, FALSE}, shared::{
devguid::GUID_DEVCLASS_DISPLAY,
minwindef::{DWORD, FALSE},
ntdef::ULONG,
},
um::{ um::{
cfgmgr32::{CM_Get_DevNode_Status, CR_SUCCESS},
cguid::GUID_NULL,
setupapi::{
SetupDiEnumDeviceInfo, SetupDiGetClassDevsW, SetupDiGetDeviceRegistryPropertyW,
SP_DEVINFO_DATA,
},
wingdi::{ wingdi::{
DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_MIRRORING_DRIVER, DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_MIRRORING_DRIVER,
}, },
winnt::HANDLE,
winuser::{EnumDisplayDevicesW, EnumDisplaySettingsExW, ENUM_CURRENT_SETTINGS}, winuser::{EnumDisplayDevicesW, EnumDisplaySettingsExW, ENUM_CURRENT_SETTINGS},
}, },
}; };
// This string is defined here. const DIGCF_PRESENT: DWORD = 0x00000002;
// https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 const SPDRP_DEVICEDESC: DWORD = 0x00000000;
const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE;
#[inline] #[inline]
pub(super) fn is_device_name(device_name: &str, name: &str) -> bool { 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<String> { pub(super) fn get_device_names(device_string: Option<&str>) -> Vec<String> {
let mut device_names = HashSet::new(); let mut device_names = Vec::new();
let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() };
dd.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as DWORD; dd.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as DWORD;
let mut i_dev_num = 0; let mut i_dev_num = 0;
@ -317,15 +612,115 @@ mod windows {
continue; 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.DeviceName),
String::from_utf16(&dd.DeviceString), String::from_utf16(&dd.DeviceString),
) { ) {
if &device_string[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING { if let Some(s) = device_string {
device_names.insert(device_name); if ds.len() >= s.len() && &ds[..s.len()] == s {
device_names.push(device_name);
}
} else {
device_names.push(device_name);
} }
} }
} }
device_names 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::<SP_DEVINFO_DATA>() 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
}
} }