Files
rustdesk/libs/remote_printer/src/setup/driver.rs
fufesou f4bbf82363 feat: remote printer (#11231)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-03-27 15:34:27 +08:00

203 lines
6.7 KiB
Rust

use super::{common_enum, get_wstr_bytes, is_name_equal};
use hbb_common::{bail, log, ResultType};
use std::{io, ptr::null_mut, time::Duration};
use winapi::{
shared::{
minwindef::{BOOL, DWORD, FALSE, LPBYTE, LPDWORD, MAX_PATH},
ntdef::{DWORDLONG, LPCWSTR},
winerror::{ERROR_UNKNOWN_PRINTER_DRIVER, S_OK},
},
um::{
winspool::{
DeletePrinterDriverExW, DeletePrinterDriverPackageW, EnumPrinterDriversW,
InstallPrinterDriverFromPackageW, UploadPrinterDriverPackageW, DPD_DELETE_ALL_FILES,
DRIVER_INFO_6W, DRIVER_INFO_8W, IPDFP_COPY_ALL_FILES, UPDP_SILENT_UPLOAD,
UPDP_UPLOAD_ALWAYS,
},
winuser::GetForegroundWindow,
},
};
use windows_strings::PCWSTR;
const HRESULT_ERR_ELEMENT_NOT_FOUND: u32 = 0x80070490;
fn enum_printer_driver(
level: DWORD,
p_driver_info: LPBYTE,
cb_buf: DWORD,
pcb_needed: LPDWORD,
pc_returned: LPDWORD,
) -> BOOL {
unsafe {
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinterdrivers
// This is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
EnumPrinterDriversW(
null_mut(),
null_mut(),
level,
p_driver_info,
cb_buf,
pcb_needed,
pc_returned,
)
}
}
pub fn get_installed_driver_version(name: &PCWSTR) -> ResultType<Option<DWORDLONG>> {
common_enum(
"EnumPrinterDriversW",
enum_printer_driver,
6,
|info: &DRIVER_INFO_6W| {
if is_name_equal(name, info.pName) {
Some(info.dwlDriverVersion)
} else {
None
}
},
|| None,
)
}
fn find_inf(name: &PCWSTR) -> ResultType<Vec<u16>> {
let r = common_enum(
"EnumPrinterDriversW",
enum_printer_driver,
8,
|info: &DRIVER_INFO_8W| {
if is_name_equal(name, info.pName) {
Some(get_wstr_bytes(info.pszInfPath))
} else {
None
}
},
|| None,
)?;
Ok(r.unwrap_or(vec![]))
}
fn delete_printer_driver(name: &PCWSTR) -> ResultType<()> {
unsafe {
// If the printer is used after the spooler service is started. E.g., printing a document through RustDesk Printer.
// `DeletePrinterDriverExW()` may fail with `ERROR_PRINTER_DRIVER_IN_USE`(3001, 0xBB9).
// We can only ignore this error for now.
// Though restarting the spooler service is a solution, it's not a good idea to restart the service.
//
// Deleting the printer driver after deleting the printer is a common practice.
// No idea why `DeletePrinterDriverExW()` fails with `ERROR_UNKNOWN_PRINTER_DRIVER` after using the printer once.
// https://github.com/ChromiumWebApps/chromium/blob/c7361d39be8abd1574e6ce8957c8dbddd4c6ccf7/cloud_print/virtual_driver/win/install/setup.cc#L422
// AnyDesk printer driver and the simplest printer driver also have the same issue.
if FALSE
== DeletePrinterDriverExW(
null_mut(),
null_mut(),
name.as_ptr() as _,
DPD_DELETE_ALL_FILES,
0,
)
{
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_UNKNOWN_PRINTER_DRIVER as _) {
return Ok(());
} else {
bail!("Failed to delete the printer driver, {}", err)
}
}
}
Ok(())
}
// https://github.com/dvalter/chromium-android-ext-dev/blob/dab74f7d5bc5a8adf303090ee25c611b4d54e2db/cloud_print/virtual_driver/win/install/setup.cc#L190
fn delete_printer_driver_package(inf: Vec<u16>) -> ResultType<()> {
if inf.is_empty() {
return Ok(());
}
let slen = if inf[inf.len() - 1] == 0 {
inf.len() - 1
} else {
inf.len()
};
let inf_path = String::from_utf16_lossy(&inf[..slen]);
if !std::path::Path::new(&inf_path).exists() {
return Ok(());
}
let mut retries = 3;
loop {
unsafe {
let res = DeletePrinterDriverPackageW(null_mut(), inf.as_ptr(), null_mut());
if res == S_OK || res == HRESULT_ERR_ELEMENT_NOT_FOUND as i32 {
return Ok(());
}
log::error!("Failed to delete the printer driver, result: {}", res);
}
retries -= 1;
if retries <= 0 {
bail!("Failed to delete the printer driver");
}
std::thread::sleep(Duration::from_secs(2));
}
}
pub fn uninstall_driver(name: &PCWSTR) -> ResultType<()> {
// Note: inf must be found before `delete_printer_driver()`.
let inf = find_inf(name)?;
delete_printer_driver(name)?;
delete_printer_driver_package(inf)
}
pub fn install_driver(name: &PCWSTR, inf: LPCWSTR) -> ResultType<()> {
let mut size = (MAX_PATH * 10) as u32;
let mut package_path = [0u16; MAX_PATH * 10];
unsafe {
let mut res = UploadPrinterDriverPackageW(
null_mut(),
inf,
null_mut(),
UPDP_SILENT_UPLOAD | UPDP_UPLOAD_ALWAYS,
null_mut(),
package_path.as_mut_ptr(),
&mut size as _,
);
if res != S_OK {
log::error!(
"Failed to upload the printer driver package to the driver cache silently, {}. Will try with user UI.",
res
);
res = UploadPrinterDriverPackageW(
null_mut(),
inf,
null_mut(),
UPDP_UPLOAD_ALWAYS,
GetForegroundWindow(),
package_path.as_mut_ptr(),
&mut size as _,
);
if res != S_OK {
bail!(
"Failed to upload the printer driver package to the driver cache with UI, {}",
res
);
}
}
// https://learn.microsoft.com/en-us/windows/win32/printdocs/installprinterdriverfrompackage
res = InstallPrinterDriverFromPackageW(
null_mut(),
package_path.as_ptr(),
name.as_ptr(),
null_mut(),
IPDFP_COPY_ALL_FILES,
);
if res != S_OK {
bail!("Failed to install the printer driver from package, {}", res);
}
}
Ok(())
}