Skip to main content

winprint_ext/printer/
device.rs

1use crate::utils::wchar;
2use std::alloc::{alloc, dealloc, Layout};
3use std::ffi::{OsStr, OsString};
4use std::mem;
5use std::ptr;
6use thiserror::Error;
7use windows::core::PCWSTR;
8use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
9use windows::Win32::Graphics::Printing::*;
10#[derive(Clone, Debug)]
11/// Represents a printer device.
12pub struct PrinterDevice {
13    name: String,
14    os_server: OsString,
15    os_name: OsString,
16    os_attributes: u32,
17}
18
19#[derive(Error, Debug)]
20/// Represents an error occurred while enumerating printer devices.
21pub enum EnumDeviceError {
22    /// Failed to enum printer device.
23    #[error("Failed to enum printer device")]
24    FailedToEnumPrinterDevice(#[source] windows::core::Error),
25}
26
27impl PrinterDevice {
28    /// Fetch all printer devices.
29    pub fn all() -> Result<Vec<Self>, EnumDeviceError> {
30        let mut bytes_needed: u32 = 0;
31        let mut count_returned: u32 = 0;
32        let flags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS;
33        let name = PCWSTR::null();
34        let level = 4;
35        unsafe {
36            let mut buffer: *mut u8 = ptr::null_mut();
37            let mut buffer_size: usize = 0;
38
39            loop {
40                let slice = if buffer_size > 0 {
41                    Some(std::slice::from_raw_parts_mut(buffer, buffer_size))
42                } else {
43                    None
44                };
45                let result = EnumPrintersW(
46                    flags,
47                    name,
48                    level,
49                    slice,
50                    &mut bytes_needed,
51                    &mut count_returned,
52                );
53                match result {
54                    Ok(()) => break,
55                    Err(e) if e.code() == ERROR_INSUFFICIENT_BUFFER.to_hresult() => {
56                        let new_size = bytes_needed as usize;
57                        let align = mem::align_of::<PRINTER_INFO_4W>();
58                        if buffer_size > 0 {
59                            dealloc(
60                                buffer,
61                                Layout::from_size_align_unchecked(buffer_size, align),
62                            );
63                        }
64                        buffer = alloc(Layout::from_size_align_unchecked(new_size, align));
65                        buffer_size = new_size;
66                    }
67                    Err(e) => {
68                        if buffer_size > 0 {
69                            dealloc(
70                                buffer,
71                                Layout::from_size_align_unchecked(
72                                    buffer_size,
73                                    mem::align_of::<PRINTER_INFO_4W>(),
74                                ),
75                            );
76                        }
77                        return Err(EnumDeviceError::FailedToEnumPrinterDevice(e));
78                    }
79                }
80            }
81
82            let mut result = Vec::<PrinterDevice>::with_capacity(count_returned as usize);
83            for i in 0..count_returned {
84                let info = &*(buffer as *const PRINTER_INFO_4W).offset(i as isize);
85                let os_name = wchar::from_wide_ptr(info.pPrinterName.0);
86                result.push(Self {
87                    name: os_name.to_string_lossy().into_owned(),
88                    os_server: wchar::from_wide_ptr(info.pServerName.0),
89                    os_attributes: info.Attributes,
90                    os_name,
91                });
92            }
93
94            if buffer_size > 0 {
95                dealloc(
96                    buffer,
97                    Layout::from_size_align_unchecked(
98                        buffer_size,
99                        mem::align_of::<PRINTER_INFO_4W>(),
100                    ),
101                );
102            }
103
104            Ok(result)
105        }
106    }
107
108    /// Get a reference to the printer device's name.
109    pub fn name(&self) -> &str {
110        self.name.as_str()
111    }
112
113    /// Get the printer device's os attributes.
114    pub fn os_attributes(&self) -> u32 {
115        self.os_attributes
116    }
117
118    /// Get a reference to the printer device's os server.
119    pub fn os_server(&self) -> &OsStr {
120        self.os_server.as_ref()
121    }
122
123    /// Get a reference to the printer device's os name.
124    pub fn os_name(&self) -> &OsStr {
125        self.os_name.as_ref()
126    }
127
128    /// Check if the printer device is local.
129    pub fn is_local(&self) -> bool {
130        self.os_attributes & PRINTER_ATTRIBUTE_LOCAL != 0
131    }
132
133    /// Check if the printer device is remote.
134    pub fn is_remote(&self) -> bool {
135        self.os_attributes & PRINTER_ATTRIBUTE_NETWORK != 0
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::PrinterDevice;
142    use crate::test_utils::null_device;
143
144    #[test]
145    fn fetch_printer_device() {
146        let _devices = PrinterDevice::all().unwrap();
147    }
148
149    #[test]
150    fn test_printer_should_be_local() {
151        let device = null_device::thread_local();
152        assert!(device.is_local());
153        assert_eq!(device.is_remote(), false);
154        assert_eq!(device.os_server(), "");
155        // PRINTER_ATTRIBUTE_LOCAL == 0x00000040
156        assert!(device.os_attributes() & 0x00000040 != 0);
157    }
158}