From 001b0e96173cea4e49f9f7a67bea95a177dd1de3 Mon Sep 17 00:00:00 2001 From: fufesou Date: Sat, 10 Oct 2026 00:21:17 +0800 Subject: [PATCH] Fix/preserve deleted printer (#16500) * fix(windows): preserve manually deleted printers Signed-off-by: fufesou * fix(windows): distinguish printer detection failures Signed-off-by: fufesou * fix(windows): skip printer detection when disabled Signed-off-by: fufesou * fix(flutter): load printer status asynchronously Signed-off-by: fufesou * refactor(flutter): remove printer status mounted checks Signed-off-by: fufesou * fix(msi): preserve deleted printers in direct upgrades Signed-off-by: fufesou * fix(windows): preserve deleted printers in silent EXE installs Signed-off-by: fufesou * fix(windows): skip printer installation when detection fails Signed-off-by: fufesou * fix(installer): default printer off when detection fails Signed-off-by: fufesou * fix(printer): limit checked enumeration to presence queries Signed-off-by: fufesou * fix(installer): allow cancellation during printer detection Inline MSI update command construction without changing its arguments. Signed-off-by: fufesou * fix(flutter): time out installer printer detection Signed-off-by: fufesou * chore Signed-off-by: fufesou * test(windows): cover multilingual install app names Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/desktop/pages/install_page.dart | 32 ++++++++- libs/remote_printer/src/setup/printer.rs | 59 ++++++++++++----- res/msi/CustomActions/CustomActions.def | 1 + res/msi/CustomActions/RemotePrinter.cpp | 66 +++++++++++++++++++ .../Package/Fragments/ShortcutProperties.wxs | 11 +++- src/platform/windows.rs | 39 +++++++++-- 6 files changed, 186 insertions(+), 22 deletions(-) diff --git a/flutter/lib/desktop/pages/install_page.dart b/flutter/lib/desktop/pages/install_page.dart index fe94b388b..8e72f8d31 100644 --- a/flutter/lib/desktop/pages/install_page.dart +++ b/flutter/lib/desktop/pages/install_page.dart @@ -62,12 +62,15 @@ class _InstallPageBody extends StatefulWidget { class _InstallPageBodyState extends State<_InstallPageBody> with WindowListener { + static const _printerStatusTimeout = Duration(seconds: 5); + late final TextEditingController controller; final RxBool startmenu = true.obs; final RxBool desktopicon = true.obs; final RxBool printer = false.obs; final RxBool showProgress = false.obs; final RxBool btnEnabled = true.obs; + final RxBool cancelEnabled = true.obs; // todo move to theme. final buttonStyle = OutlinedButton.styleFrom( @@ -83,10 +86,33 @@ class _InstallPageBodyState extends State<_InstallPageBody> printer.value = installOptions['PRINTER'] == '1'; } + Future _loadPrinterStatus() async { + btnEnabled.value = false; + showProgress.value = true; + try { + final status = await bind + .mainGetCommon(key: 'is-printer-installed') + .timeout(_printerStatusTimeout); + printer.value = status == 'true'; + if (status != 'true' && status != 'false') { + debugPrint('Failed to check printer installation status: $status'); + } + } catch (e) { + printer.value = false; + debugPrint('Failed to check printer installation status: $e'); + } finally { + showProgress.value = false; + btnEnabled.value = true; + } + } + @override void initState() { windowManager.addListener(this); super.initState(); + if (printer.value) { + _loadPrinterStatus(); + } } @override @@ -217,8 +243,9 @@ class _InstallPageBodyState extends State<_InstallPageBody> () => OutlinedButton.icon( icon: Icon(Icons.close_rounded, size: 16), label: Text(translate('Cancel')), - onPressed: - btnEnabled.value ? () => windowManager.close() : null, + onPressed: cancelEnabled.value + ? () => windowManager.close() + : null, style: buttonStyle, ).marginOnly(right: 10), ), @@ -253,6 +280,7 @@ class _InstallPageBodyState extends State<_InstallPageBody> void install() { do_install() { btnEnabled.value = false; + cancelEnabled.value = false; showProgress.value = true; String args = ''; if (startmenu.value) args += ' startmenu'; diff --git a/libs/remote_printer/src/setup/printer.rs b/libs/remote_printer/src/setup/printer.rs index 9882b8f38..80ad44d3a 100644 --- a/libs/remote_printer/src/setup/printer.rs +++ b/libs/remote_printer/src/setup/printer.rs @@ -5,7 +5,7 @@ use winapi::{ shared::{ minwindef::{BOOL, DWORD, FALSE, LPBYTE, LPDWORD}, ntdef::HANDLE, - winerror::ERROR_INVALID_PRINTER_NAME, + winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_PRINTER_NAME}, }, um::winspool::{ AddPrinterW, ClosePrinter, DeletePrinter, EnumPrintersW, OpenPrinterW, SetPrinterW, @@ -42,20 +42,49 @@ fn enum_local_printer( #[inline] pub fn is_printer_added(name: &PCWSTR) -> ResultType { - let r = common_enum( - "EnumPrintersW", - enum_local_printer, - 1, - |info: &PRINTER_INFO_1W| { - if is_name_equal(name, info.pName) { - Some(true) - } else { - None - } - }, - || None, - )?; - Ok(r.unwrap_or(false)) + const PRINTER_INFO_LEVEL: DWORD = 1; + let mut needed = 0; + let mut returned = 0; + if enum_local_printer( + PRINTER_INFO_LEVEL, + null_mut(), + 0, + &mut needed, + &mut returned, + ) == FALSE + { + let error = io::Error::last_os_error(); + if error.raw_os_error() != Some(ERROR_INSUFFICIENT_BUFFER as i32) || needed == 0 { + bail!("Failed to query EnumPrintersW buffer size: {error}"); + } + } + if needed == 0 { + return Ok(false); + } + + let mut buffer = vec![0u8; needed as usize]; + if enum_local_printer( + PRINTER_INFO_LEVEL, + buffer.as_mut_ptr(), + needed, + &mut needed, + &mut returned, + ) == FALSE + { + bail!( + "Failed to call EnumPrintersW, error: {}", + io::Error::last_os_error() + ); + } + + let printers = buffer.as_ptr() as *const PRINTER_INFO_1W; + for index in 0..returned { + let printer = unsafe { &*printers.add(index as usize) }; + if is_name_equal(name, printer.pName) { + return Ok(true); + } + } + Ok(false) } // Only return the first matched printer diff --git a/res/msi/CustomActions/CustomActions.def b/res/msi/CustomActions/CustomActions.def index d50fbf59b..65f2087a2 100644 --- a/res/msi/CustomActions/CustomActions.def +++ b/res/msi/CustomActions/CustomActions.def @@ -14,3 +14,4 @@ EXPORTS RemoveAmyuniIdd InstallPrinter UninstallPrinter + SetPrinterInstallDefault diff --git a/res/msi/CustomActions/RemotePrinter.cpp b/res/msi/CustomActions/RemotePrinter.cpp index 532a3524b..f949f8e13 100644 --- a/res/msi/CustomActions/RemotePrinter.cpp +++ b/res/msi/CustomActions/RemotePrinter.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -356,6 +357,42 @@ namespace RemotePrinter return EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, level, pPrinterInfo, cbBuf, pcbNeeded, pcReturned); } + static HRESULT queryPrinterPresence(LPCWSTR name) + { + constexpr DWORD printerInfoLevel = 1; + DWORD needed = 0; + DWORD returned = 0; + if (!enumLocalPrinter(printerInfoLevel, NULL, 0, &needed, &returned)) + { + const DWORD error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER || needed == 0) + { + return error == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(error); + } + } + if (needed == 0) + { + return S_FALSE; + } + + std::vector buffer(needed); + if (!enumLocalPrinter(printerInfoLevel, buffer.data(), needed, &needed, &returned)) + { + const DWORD error = GetLastError(); + return error == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(error); + } + + const auto printers = reinterpret_cast(buffer.data()); + for (DWORD i = 0; i < returned; i++) + { + if (isNameEqual(name, printers[i].pName)) + { + return S_OK; + } + } + return S_FALSE; + } + BOOL isPrinterAdded(LPCWSTR name) { auto onData = [name](const PRINTER_INFO_1W &info) @@ -528,3 +565,32 @@ namespace RemotePrinter WcaLog(LOGMSG_STANDARD, "Deleted the local port\n"); } } + +extern "C" UINT __stdcall SetPrinterInstallDefault(__in MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + LPWSTR appName = NULL; + + hr = WcaInitialize(hInstall, "SetPrinterInstallDefault"); + ExitOnFailure(hr, "Failed to initialize printer detection"); + + hr = WcaGetProperty(L"ProductName", &appName); + ExitOnFailure(hr, "Failed to get the printer's application name"); + + hr = RemotePrinter::queryPrinterPresence(RemotePrinter::printerNameOf(appName).c_str()); + if (FAILED(hr)) + { + WcaLog(LOGMSG_STANDARD, "Failed to detect the existing printer (0x%08X); skipping printer installation.", static_cast(hr)); + } + + if (hr != S_OK) + { + hr = WcaSetProperty(L"INSTALLPRINTER", L"0"); + ExitOnFailure(hr, "Failed to disable printer installation"); + WcaLog(LOGMSG_STANDARD, "The application's printer was not confirmed present; leaving printer installation disabled by default."); + } + +LExit: + ReleaseStr(appName); + return WcaFinalize(SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE); +} diff --git a/res/msi/Package/Fragments/ShortcutProperties.wxs b/res/msi/Package/Fragments/ShortcutProperties.wxs index ba23ab006..52513844f 100644 --- a/res/msi/Package/Fragments/ShortcutProperties.wxs +++ b/res/msi/Package/Fragments/ShortcutProperties.wxs @@ -74,13 +74,22 @@ + + + + + + + + + - + diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a3c3d68f0..438baf729 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1354,6 +1354,10 @@ pub fn get_install_options() -> String { } pub fn get_silent_install_options(printer_override: Option) -> &'static str { + if !is_win_10_or_greater() { + return "desktopicon startmenu"; + } + let install_printer = match printer_override { Some(override_value) => override_value, None => { @@ -1361,9 +1365,13 @@ pub fn get_silent_install_options(printer_override: Option) -> &'static st let subkey = format!(".{}", app_name.to_lowercase()); let printer = get_reg_of_hkcr(&subkey, REG_NAME_INSTALL_PRINTER); printer.as_deref() == Some("1") + && remote_printer::is_rd_printer_installed(&app_name).unwrap_or_else(|err| { + log::warn!("Failed to check printer installation status: {err}; skipping printer installation"); + false + }) } }; - if install_printer && is_win_10_or_greater() { + if install_printer { "desktopicon startmenu printer" } else { "desktopicon startmenu" @@ -3911,8 +3919,22 @@ pub fn update_to(file: &str) -> ResultType<()> { // We need also to handle the command line parsing to find the tray processes. pub fn update_me_msi(msi: &str, quiet: bool) -> ResultType<()> { let quiet_args = if quiet { " /qn LAUNCH_TRAY_APP=N" } else { "" }; - let cmds = - format!("chcp 65001 && msiexec /i \"{msi}\"{quiet_args} REBOOT=ReallySuppress /norestart"); + let app_name = crate::get_app_name(); + let subkey = format!(".{}", app_name.to_lowercase()); + let printer_installed = get_reg_of_hkcr(&subkey, REG_NAME_INSTALL_PRINTER).as_deref() + != Some("0") + && remote_printer::is_rd_printer_installed(&app_name).unwrap_or_else(|err| { + log::warn!( + "Failed to check printer installation status: {err}; skipping printer installation" + ); + false + }); + let printer_args = if printer_installed { + "" + } else { + " INSTALLPRINTER=0" + }; + let cmds = format!("chcp 65001 && msiexec /i \"{msi}\"{quiet_args}{printer_args} REBOOT=ReallySuppress /norestart"); run_cmds(cmds, false, "update-msi")?; Ok(()) } @@ -4846,7 +4868,16 @@ mod tests { #[test] fn install_app_names_enforce_ascii_command_safety() { assert!(validate_install_app_name("RustDesk-Admin1").is_ok()); - for app_name in ["", "RustDesk_Admin", "RustDesk&whoami", "RustDesk应用"] { + for app_name in [ + "", + "RustDesk_Admin", + "RustDesk&whoami", + "RustDesk应用", + "RustDesk앱", + "RustDeskBüro", + "RustDeskFrançais", + "RustDeskアプリ", + ] { assert!( validate_install_app_name(app_name).is_err(), "unsafe application name was accepted: {app_name}"