mirror of
https://github.com/storytold/winprint.rs.git
synced 2026-10-13 23:17:45 +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