forked from 1b-agent/opencadstudio-upstream
Merge pull request #1276 from CiberNin/fix/windows-printers
This commit is contained in:
commit
279288ab71
2 files changed
+136
-4
No files matched your search
+1
-1
@@ -60,7 +60,7 @@ fluent-syntax = "0.12"
|
||||
naga = { version = "27", features = ["wgsl-in"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_System_Com", "Win32_System_Registry", "Win32_Storage_FileSystem", "Win32_Foundation"] }
|
||||
windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_System_Com", "Win32_System_Registry", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Graphics_Printing"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ashpd = { version = "0.13.13", default-features = false, features = ["async-io", "wayland"] }
|
||||
|
||||
+135
-3
@@ -89,8 +89,7 @@ pub fn print_existing_pdf(_path: &std::path::Path, _opts: &PrintOptions) -> Resu
|
||||
}
|
||||
|
||||
/// Enumerate installed printers. Linux/macOS query CUPS via `lpstat -e`;
|
||||
/// Windows returns an empty list (the "printto" dispatch targets a named
|
||||
/// printer directly and the system default is always available).
|
||||
/// Windows queries the spooler's cached local and connected printer list.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn list_printers() -> Vec<String> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@@ -111,7 +110,140 @@ pub fn list_printers() -> Vec<String> {
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Vec::new()
|
||||
windows_printers().unwrap_or_else(|error| {
|
||||
eprintln!("Could not enumerate printers: {error}");
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn windows_printers() -> std::io::Result<Vec<String>> {
|
||||
use windows_sys::Win32::Foundation::{GetLastError, ERROR_INSUFFICIENT_BUFFER};
|
||||
use windows_sys::Win32::Graphics::Printing::{
|
||||
EnumPrintersW, PRINTER_ENUM_CONNECTIONS, PRINTER_ENUM_LOCAL,
|
||||
};
|
||||
|
||||
let mut bytes_needed = 0;
|
||||
let mut printer_count = 0;
|
||||
let mut buffer = Vec::<usize>::new();
|
||||
// The printer list can grow between the size query and the data query.
|
||||
for _ in 0..4 {
|
||||
let buffer_bytes = u32::try_from(std::mem::size_of_val(buffer.as_slice()))
|
||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::OutOfMemory))?;
|
||||
let data = if buffer.is_empty() {
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
buffer.as_mut_ptr().cast::<u8>()
|
||||
};
|
||||
// SAFETY: data is null for the size query, otherwise it points to an
|
||||
// aligned, writable allocation of buffer_bytes bytes.
|
||||
let success = unsafe {
|
||||
EnumPrintersW(
|
||||
PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
|
||||
std::ptr::null(),
|
||||
4,
|
||||
data,
|
||||
buffer_bytes,
|
||||
&mut bytes_needed,
|
||||
&mut printer_count,
|
||||
)
|
||||
};
|
||||
if success != 0 {
|
||||
return windows_printer_names(&buffer, printer_count as usize);
|
||||
}
|
||||
let error = unsafe { GetLastError() };
|
||||
if error != ERROR_INSUFFICIENT_BUFFER || bytes_needed <= buffer_bytes {
|
||||
return Err(std::io::Error::from_raw_os_error(error as i32));
|
||||
}
|
||||
buffer.resize(
|
||||
(bytes_needed as usize).div_ceil(std::mem::size_of::<usize>()),
|
||||
0,
|
||||
);
|
||||
}
|
||||
Err(std::io::Error::from_raw_os_error(
|
||||
ERROR_INSUFFICIENT_BUFFER as i32,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn windows_printer_names(buffer: &[usize], printer_count: usize) -> std::io::Result<Vec<String>> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
use windows_sys::Win32::Graphics::Printing::PRINTER_INFO_4W;
|
||||
|
||||
const {
|
||||
assert!(std::mem::align_of::<usize>() >= std::mem::align_of::<PRINTER_INFO_4W>());
|
||||
}
|
||||
let buffer_bytes = std::mem::size_of_val(buffer);
|
||||
if printer_count > buffer_bytes / std::mem::size_of::<PRINTER_INFO_4W>() {
|
||||
return Err(Error::from(ErrorKind::InvalidData));
|
||||
}
|
||||
// SAFETY: the initialized buffer is aligned and large enough for these
|
||||
// records. Raw pointer fields are validated before reading any names.
|
||||
let records = unsafe {
|
||||
std::slice::from_raw_parts(buffer.as_ptr().cast::<PRINTER_INFO_4W>(), printer_count)
|
||||
};
|
||||
let mut names = Vec::with_capacity(printer_count);
|
||||
for record in records {
|
||||
if record.pPrinterName.is_null() {
|
||||
continue;
|
||||
}
|
||||
let offset = (record.pPrinterName as usize)
|
||||
.checked_sub(buffer.as_ptr() as usize)
|
||||
.filter(|offset| *offset < buffer_bytes && offset % 2 == 0)
|
||||
.ok_or(Error::from(ErrorKind::InvalidData))?;
|
||||
// SAFETY: offset is UTF-16 aligned and the slice ends within buffer.
|
||||
let utf16 = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
buffer.as_ptr().cast::<u8>().add(offset).cast::<u16>(),
|
||||
(buffer_bytes - offset) / 2,
|
||||
)
|
||||
};
|
||||
let length = utf16
|
||||
.iter()
|
||||
.position(|&unit| unit == 0)
|
||||
.ok_or(Error::from(ErrorKind::InvalidData))?;
|
||||
if length != 0 {
|
||||
names.push(String::from_utf16_lossy(&utf16[..length]));
|
||||
}
|
||||
}
|
||||
names.sort();
|
||||
names.dedup();
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_os = "windows"))]
|
||||
mod printer_buffer_tests {
|
||||
use super::windows_printer_names;
|
||||
use windows_sys::Win32::Graphics::Printing::PRINTER_INFO_4W;
|
||||
|
||||
#[test]
|
||||
fn printer_names_stay_within_the_returned_buffer() {
|
||||
assert!(windows_printer_names(&[], 0).unwrap().is_empty());
|
||||
assert!(windows_printer_names(&[], 1).is_err());
|
||||
let mut buffer = vec![0usize; 16];
|
||||
let name_offset = std::mem::size_of::<PRINTER_INFO_4W>();
|
||||
let name: Vec<u16> = "Printer \u{03b1}\0".encode_utf16().collect();
|
||||
// SAFETY: the aligned allocation holds the record and UTF-16 name.
|
||||
unsafe {
|
||||
let record = buffer.as_mut_ptr().cast::<PRINTER_INFO_4W>();
|
||||
let name_ptr = buffer
|
||||
.as_mut_ptr()
|
||||
.cast::<u8>()
|
||||
.add(name_offset)
|
||||
.cast::<u16>();
|
||||
std::ptr::copy_nonoverlapping(name.as_ptr(), name_ptr, name.len());
|
||||
(*record).pPrinterName = name_ptr;
|
||||
assert_eq!(
|
||||
windows_printer_names(&buffer, 1).unwrap(),
|
||||
["Printer \u{03b1}"]
|
||||
);
|
||||
(*record).pPrinterName = buffer.as_mut_ptr().cast::<u16>().wrapping_sub(1);
|
||||
assert!(windows_printer_names(&buffer, 1).is_err());
|
||||
(*record).pPrinterName = name_ptr;
|
||||
}
|
||||
buffer[name_offset / std::mem::size_of::<usize>()..].fill(usize::MAX);
|
||||
assert!(windows_printer_names(&buffer, 1).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user