feat: printer info & xps printer

This commit is contained in:
ArcticLampyrid committed 2024-03-07 00:19:30 +08:00
commit 820f1d0d63
11 files changed
+253

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+14
View File
@@ -0,0 +1,14 @@
[package]
edition = "2018"
name = "winprint"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
scopeguard = "1.1"
thiserror = "1.0"
windows = "0.20"
[build-dependencies]
windows = "0.20"
+12
View File
@@ -0,0 +1,12 @@
fn main() {
windows::build! {
Windows::Win32::Graphics::Gdi::{CreateDCW, DeleteDC},
Windows::Win32::Graphics::Printing::*,
Windows::Win32::System::Com::{CoCreateInstance, CoInitializeEx, CoUninitialize},
Windows::Win32::Storage::Xps::*,
Windows::Win32::Storage::Xps::Printing::*,
Windows::Win32::Foundation::CloseHandle,
Windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject},
Windows::Win32::System::WindowsProgramming::INFINITE
};
}
+1
View File
@@ -0,0 +1 @@
windows::include_bindings!();
+18
View File
@@ -0,0 +1,18 @@
mod bindings;
mod utils;
pub mod printer;
#[cfg(test)]
mod tests {
use crate::printer::PrinterInfo;
use crate::printer::XpsPrinter;
use crate::printer::FilePrinter;
use std::path::Path;
#[test]
fn it_works() {
let printers = PrinterInfo::all();
println!("{:#?}", printers);
let my_printer = printers.iter().find(|x| x.name() == "pdfFactory Pro").unwrap();
let xps = XpsPrinter::new(my_printer.clone());
xps.print(Path::new("D:\\1.xps")).unwrap();
}
}
+15
View File
@@ -0,0 +1,15 @@
use std::path::Path;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FilePrinterError {
#[error("Failed to open printer")]
FailedToOpenPrinter,
#[error("Failed to create job")]
FailedToCreateJob,
#[error("Failed to write document")]
FailedToWriteDocument,
}
pub trait FilePrinter {
fn print(&self, path: &Path) -> std::result::Result<(), FilePrinterError>;
}
+88
View File
@@ -0,0 +1,88 @@
use crate::utils::wchar;
use crate::bindings::Windows::Win32::{Foundation::*, Graphics::Printing::*};
use std::ffi::{OsString, OsStr};
use std::ptr;
#[derive(Clone, Debug)]
pub struct PrinterInfo {
name: String,
os_server: OsString,
os_name: OsString,
os_attributes: u32,
}
impl PrinterInfo {
pub fn all() -> Vec<Self> {
let mut bytes_needed = 0;
let mut count_returned = 0;
let flags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS;
let name = PWSTR::default();
let level = 4;
unsafe {
EnumPrintersW(
flags,
name,
level,
ptr::null_mut(),
0,
&mut bytes_needed,
&mut count_returned,
)
};
let mut buffer = Vec::<u8>::with_capacity(bytes_needed as usize);
unsafe {
EnumPrintersW(
flags,
name,
level,
buffer.as_mut_ptr(),
bytes_needed,
&mut bytes_needed,
&mut count_returned,
)
};
unsafe { buffer.set_len(bytes_needed as usize) };
let mut result = Vec::<PrinterInfo>::with_capacity(count_returned as usize);
for i in 0..count_returned {
let info = unsafe {
std::ptr::read_unaligned(
(buffer.as_mut_ptr() as *const PRINTER_INFO_4W).offset(i as isize),
)
};
let os_name = wchar::from_wide_ptr(info.pPrinterName.0);
result.push(Self {
name: os_name.to_string_lossy().into_owned(),
os_server: wchar::from_wide_ptr(info.pServerName.0),
os_attributes: info.Attributes,
os_name
})
}
result
}
/// Get a reference to the printer info's name.
pub fn name(&self) -> &str {
self.name.as_str()
}
/// Get the printer info's os attributes.
pub fn os_attributes(&self) -> u32 {
self.os_attributes
}
/// Get a reference to the printer info's os server.
pub fn os_server(&self) -> &OsStr {
self.os_server.as_ref()
}
/// Get a reference to the printer info's os name.
pub fn os_name(&self) -> &OsStr {
self.os_name.as_ref()
}
pub fn is_local(&self) -> bool {
self.os_attributes & PRINTER_ATTRIBUTE_LOCAL != 0
}
pub fn is_remote(&self) -> bool {
self.os_attributes & PRINTER_ATTRIBUTE_NETWORK != 0
}
}
+7
View File
@@ -0,0 +1,7 @@
mod info;
pub use info::PrinterInfo;
mod file;
pub use file::FilePrinter;
pub use file::FilePrinterError;
mod xps;
pub use xps::XpsPrinter;
+70
View File
@@ -0,0 +1,70 @@
use crate::bindings::Windows::Win32::{
Foundation::{CloseHandle, PWSTR},
Storage::Xps::{Printing::*, *},
System::{Com::*, Threading::*, WindowsProgramming::INFINITE},
};
use crate::printer::FilePrinter;
use crate::printer::FilePrinterError;
use crate::printer::PrinterInfo;
use crate::utils::wchar;
use scopeguard::defer;
use std::path::Path;
use std::ptr;
use windows::Handle;
pub struct XpsPrinter {
printer: PrinterInfo,
}
impl XpsPrinter {
pub fn new(printer: PrinterInfo) -> Self {
Self { printer }
}
}
impl FilePrinter for XpsPrinter {
fn print(&self, path: &Path) -> std::result::Result<(), FilePrinterError> {
unsafe {
let _ = CoInitializeEx(std::ptr::null_mut(), COINIT_MULTITHREADED);
defer! {
CoUninitialize();
}
let event = CreateEventW(ptr::null_mut(), true, false, None)
.ok()
.map_err(|_| FilePrinterError::FailedToCreateJob)?;
defer! {
CloseHandle(event);
}
let xps_factory: IXpsOMObjectFactory =
CoCreateInstance(&XpsOMObjectFactory, None, CLSCTX_ALL)
.map_err(|_| FilePrinterError::FailedToOpenPrinter)?;
let mut document_stream_container = Option::<IXpsPrintJobStream>::None;
StartXpsPrintJob(
PWSTR(wchar::to_wide_chars(self.printer.os_name()).as_mut_ptr()),
PWSTR(wchar::to_wide_chars(path.file_name().unwrap_or(path.as_ref())).as_mut_ptr()),
None,
None,
event,
ptr::null(),
0,
ptr::null_mut(),
ptr::addr_of_mut!(document_stream_container),
ptr::null_mut(),
)
.map_err(|_| FilePrinterError::FailedToCreateJob)?;
let document_stream =
document_stream_container.ok_or(FilePrinterError::FailedToCreateJob)?;
let xps_package = xps_factory
.CreatePackageFromFile(PWSTR(wchar::to_wide_chars(path).as_mut_ptr()), false)
.map_err(|_| FilePrinterError::FailedToWriteDocument)?;
xps_package
.WriteToStream(&document_stream, false)
.map_err(|_| FilePrinterError::FailedToWriteDocument)?;
document_stream
.Close()
.map_err(|_| FilePrinterError::FailedToWriteDocument)?;
WaitForSingleObject(event, INFINITE);
}
Ok(())
}
}
+1
View File
@@ -0,0 +1 @@
pub(crate) mod wchar;
+25
View File
@@ -0,0 +1,25 @@
use std::ffi::{OsStr, OsString};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
extern "C" {
fn wcslen(string: *const u16) -> usize;
}
pub fn from_wide_ptr(ptr: *const u16) -> OsString {
unsafe {
if ptr.is_null() {
OsString::default()
} else {
let len = wcslen(ptr);
let slice = std::slice::from_raw_parts(ptr, len);
OsString::from_wide(slice)
}
}
}
pub fn to_wide_chars<P: AsRef<OsStr>>(s: P) -> Vec<u16> {
s.as_ref()
.encode_wide()
.chain(Some(0).into_iter())
.collect()
}