mirror of
https://github.com/storytold/winprint.rs.git
synced 2026-10-13 15:09:10 +00:00
feat: printer info & xps printer
This commit is contained in:
11 files changed
+253
No files matched your search
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
+14
@@ -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"
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
windows::include_bindings!();
|
||||
+18
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub(crate) mod wchar;
|
||||
@@ -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()
|
||||
}
|
||||
Reference in new issue
Block a user