mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-10-11 22:49:54 +00:00
Fix/preserve deleted printer (#16500)
* fix(windows): preserve manually deleted printers Signed-off-by: fufesou <[email protected]> * fix(windows): distinguish printer detection failures Signed-off-by: fufesou <[email protected]> * fix(windows): skip printer detection when disabled Signed-off-by: fufesou <[email protected]> * fix(flutter): load printer status asynchronously Signed-off-by: fufesou <[email protected]> * refactor(flutter): remove printer status mounted checks Signed-off-by: fufesou <[email protected]> * fix(msi): preserve deleted printers in direct upgrades Signed-off-by: fufesou <[email protected]> * fix(windows): preserve deleted printers in silent EXE installs Signed-off-by: fufesou <[email protected]> * fix(windows): skip printer installation when detection fails Signed-off-by: fufesou <[email protected]> * fix(installer): default printer off when detection fails Signed-off-by: fufesou <[email protected]> * fix(printer): limit checked enumeration to presence queries Signed-off-by: fufesou <[email protected]> * fix(installer): allow cancellation during printer detection Inline MSI update command construction without changing its arguments. Signed-off-by: fufesou <[email protected]> * fix(flutter): time out installer printer detection Signed-off-by: fufesou <[email protected]> * chore Signed-off-by: fufesou <[email protected]> * test(windows): cover multilingual install app names Signed-off-by: fufesou <[email protected]> --------- Signed-off-by: fufesou <[email protected]>
This commit is contained in:
@@ -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<void> _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';
|
||||
|
||||
@@ -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<bool> {
|
||||
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
|
||||
|
||||
@@ -14,3 +14,4 @@ EXPORTS
|
||||
RemoveAmyuniIdd
|
||||
InstallPrinter
|
||||
UninstallPrinter
|
||||
SetPrinterInstallDefault
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <Windows.h>
|
||||
#include <winspool.h>
|
||||
#include <setupapi.h>
|
||||
#include <strutil.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
@@ -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<BYTE> 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<const PRINTER_INFO_1W *>(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<UINT>(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);
|
||||
}
|
||||
|
||||
@@ -74,13 +74,22 @@
|
||||
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
|
||||
<SetProperty Action="RestoreSavedPrinterValue" Id="INSTALLPRINTER" Value="[SavedPrinterCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedPrinterCmdLineValue" />
|
||||
|
||||
<!-- Check before the old product removes its printer. firstSequence preserves later UI choices. -->
|
||||
<CustomAction Id="SetPrinterInstallDefault" DllEntry="SetPrinterInstallDefault" Execute="firstSequence" Return="check" BinaryRef="Custom_Actions_Dll" />
|
||||
<InstallUISequence>
|
||||
<Custom Action="SetPrinterInstallDefault" After="RestoreSavedPrinterValue" Condition="WIX_UPGRADE_DETECTED AND NOT SavedPrinterCmdLineValue AND (INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y")" />
|
||||
</InstallUISequence>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="SetPrinterInstallDefault" After="RestoreSavedPrinterValue" Condition="WIX_UPGRADE_DETECTED AND NOT SavedPrinterCmdLineValue AND (INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y")" />
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!-- If a command line value or registry value was set, update the main properties with the value -->
|
||||
<SetProperty Id="STARTMENUSHORTCUTS" Value="" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS AND NOT (CREATESTARTMENUSHORTCUTS = 1 OR CREATESTARTMENUSHORTCUTS = "Y" OR CREATESTARTMENUSHORTCUTS = "y")" />
|
||||
<SetProperty Id="DESKTOPSHORTCUTS" Value="" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS AND NOT (CREATEDESKTOPSHORTCUTS = 1 OR CREATEDESKTOPSHORTCUTS = "Y" OR CREATEDESKTOPSHORTCUTS = "y")" />
|
||||
<!-- PRINTER defaults to empty now, so a saved or command-line INSTALLPRINTER=1
|
||||
must explicitly enable the main PRINTER property. Non-truthy INSTALLPRINTER
|
||||
values still clear PRINTER so upgrades preserve an explicit disabled choice. -->
|
||||
<SetProperty Action="SetPrinterValueEnabled" Id="PRINTER" Value="1" After="RestoreSavedPrinterValue" Sequence="first" Condition="INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y"" />
|
||||
<SetProperty Action="SetPrinterValueEnabled" Id="PRINTER" Value="1" After="SetPrinterInstallDefault" Sequence="first" Condition="INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y"" />
|
||||
<SetProperty Action="SetPrinterValueDisabled" Id="PRINTER" Value="" After="SetPrinterValueEnabled" Sequence="first" Condition="INSTALLPRINTER AND NOT (INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y")" />
|
||||
|
||||
</Fragment>
|
||||
|
||||
+35
-4
@@ -1354,6 +1354,10 @@ pub fn get_install_options() -> String {
|
||||
}
|
||||
|
||||
pub fn get_silent_install_options(printer_override: Option<bool>) -> &'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<bool>) -> &'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}"
|
||||
|
||||
Reference in New Issue
Block a user