fix(print): preserve cached DEVMODE printer preferences on Windows (#36) #37
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "tmp_tauri",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tmp_tauri",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.3",
|
||||
"dependencies": {
|
||||
"@tauri-apps/plugin-dialog": "^2.7.2"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tmp_tauri",
|
||||
"private": true,
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"tauri": "tauri"
|
||||
|
||||
Generated
+1
-1
@@ -1606,7 +1606,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iccery"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"image",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iccery"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -186,5 +186,61 @@ printer Custom_Queue unknown state\n\
|
||||
let err = res.unwrap_err();
|
||||
assert!(err.contains("Target TIFF file not found"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_windows_devmode_preservation_and_overrides() {
|
||||
use windows::Win32::Graphics::Gdi::DEVMODEW;
|
||||
use crate::print::windows::apply_print_options_to_devmode;
|
||||
|
||||
let devmode_size = std::mem::size_of::<DEVMODEW>() + 128; // Public DEVMODE + 128 bytes private OEM extra
|
||||
let mut buffer = vec![0u8; devmode_size];
|
||||
|
||||
// Simulate OEM private driver payload (e.g. Epson Print Preview flag in private offset)
|
||||
buffer[std::mem::size_of::<DEVMODEW>() + 10] = 0xAA;
|
||||
buffer[std::mem::size_of::<DEVMODEW>() + 11] = 0x55;
|
||||
|
||||
let p_devmode = buffer.as_mut_ptr() as *mut DEVMODEW;
|
||||
unsafe {
|
||||
(*p_devmode).dmSize = std::mem::size_of::<DEVMODEW>() as u16;
|
||||
(*p_devmode).dmDriverExtra = 128;
|
||||
}
|
||||
|
||||
// Store into PrinterDevModeStore
|
||||
let store = PrinterDevModeStore::new();
|
||||
store.set("EPSON-Test-Printer", buffer.clone());
|
||||
|
||||
let retrieved = store.get("EPSON-Test-Printer").expect("Failed to get cached DEVMODE");
|
||||
assert_eq!(retrieved.len(), devmode_size);
|
||||
// Verify private OEM payload is preserved
|
||||
assert_eq!(retrieved[std::mem::size_of::<DEVMODEW>() + 10], 0xAA);
|
||||
assert_eq!(retrieved[std::mem::size_of::<DEVMODEW>() + 11], 0x55);
|
||||
|
||||
// Apply UI overrides
|
||||
let opts = PrintOptions {
|
||||
paper_source: Some(3),
|
||||
orientation: Some("landscape".to_string()),
|
||||
paper_size: None,
|
||||
ppd_uncorrected_passthrough: None,
|
||||
};
|
||||
|
||||
let mut work_buf = retrieved;
|
||||
let p_work_devmode = work_buf.as_mut_ptr() as *mut DEVMODEW;
|
||||
apply_print_options_to_devmode(p_work_devmode, Some(&opts));
|
||||
|
||||
unsafe {
|
||||
// Verify ICM bypass is applied
|
||||
assert_eq!((*p_work_devmode).dmICMMethod, 1); // DMICMMETHOD_NONE = 1
|
||||
// Verify tray override
|
||||
assert_eq!((*p_work_devmode).Anonymous1.Anonymous1.dmDefaultSource, 3);
|
||||
// Verify orientation override
|
||||
assert_eq!((*p_work_devmode).Anonymous1.Anonymous1.dmOrientation, 2); // DMORIENT_LANDSCAPE = 2
|
||||
}
|
||||
|
||||
// Verify private OEM payload was NOT corrupted or overwritten
|
||||
assert_eq!(work_buf[std::mem::size_of::<DEVMODEW>() + 10], 0xAA);
|
||||
assert_eq!(work_buf[std::mem::size_of::<DEVMODEW>() + 11], 0x55);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -345,6 +345,35 @@ pub fn show_printer_properties(
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply ICM bypass and user options onto an active DEVMODE structure
|
||||
pub fn apply_print_options_to_devmode(
|
||||
p_devmode: *mut DEVMODEW,
|
||||
options: Option<&PrintOptions>,
|
||||
) {
|
||||
unsafe {
|
||||
// Apply strict ICM bypass
|
||||
(*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_ICMMETHOD);
|
||||
(*p_devmode).dmICMMethod = DMICMMETHOD_NONE;
|
||||
|
||||
// Apply UI options if specified
|
||||
if let Some(opts) = options {
|
||||
if let Some(tray_id) = opts.paper_source {
|
||||
(*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_DEFAULTSOURCE);
|
||||
(*p_devmode).Anonymous1.Anonymous1.dmDefaultSource = tray_id as i16;
|
||||
}
|
||||
|
||||
if let Some(ref orient) = opts.orientation {
|
||||
(*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_ORIENTATION);
|
||||
if orient.eq_ignore_ascii_case("landscape") {
|
||||
(*p_devmode).Anonymous1.Anonymous1.dmOrientation = DMORIENT_LANDSCAPE;
|
||||
} else {
|
||||
(*p_devmode).Anonymous1.Anonymous1.dmOrientation = DMORIENT_PORTRAIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Print target TIFF file to printer with GDI ICM bypass, DEVMODE overrides, and auto-fit scaling
|
||||
pub fn print_target(
|
||||
printer_name: &str,
|
||||
@@ -407,50 +436,30 @@ pub fn print_target(
|
||||
);
|
||||
|
||||
let mut devmode_buf = if devmode_size > 0 {
|
||||
let mut buf = devmode_store
|
||||
.and_then(|s| s.get(printer_name))
|
||||
.unwrap_or_else(|| vec![0u8; devmode_size as usize]);
|
||||
|
||||
if buf.len() < devmode_size as usize {
|
||||
buf.resize(devmode_size as usize, 0);
|
||||
}
|
||||
let mut buf = if let Some(cached) = devmode_store.and_then(|s| s.get(printer_name)) {
|
||||
let mut b = cached;
|
||||
if b.len() < devmode_size as usize {
|
||||
b.resize(devmode_size as usize, 0);
|
||||
}
|
||||
b
|
||||
} else {
|
||||
let mut b = vec![0u8; devmode_size as usize];
|
||||
let p_devmode = b.as_mut_ptr() as *mut DEVMODEW;
|
||||
let _ = DocumentPropertiesW(
|
||||
HWND::default(),
|
||||
h_printer,
|
||||
PCWSTR(printer_wide.as_ptr()),
|
||||
Some(p_devmode),
|
||||
None,
|
||||
DM_OUT_BUFFER,
|
||||
);
|
||||
b
|
||||
};
|
||||
|
||||
let p_devmode = buf.as_mut_ptr() as *mut DEVMODEW;
|
||||
let res = DocumentPropertiesW(
|
||||
HWND::default(),
|
||||
h_printer,
|
||||
PCWSTR(printer_wide.as_ptr()),
|
||||
Some(p_devmode),
|
||||
None,
|
||||
DM_OUT_BUFFER,
|
||||
);
|
||||
apply_print_options_to_devmode(p_devmode, options);
|
||||
|
||||
if res >= 0 {
|
||||
// Apply strict ICM bypass
|
||||
(*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_ICMMETHOD);
|
||||
(*p_devmode).dmICMMethod = DMICMMETHOD_NONE;
|
||||
|
||||
// Apply UI options if specified
|
||||
if let Some(opts) = options {
|
||||
if let Some(tray_id) = opts.paper_source {
|
||||
(*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_DEFAULTSOURCE);
|
||||
(*p_devmode).Anonymous1.Anonymous1.dmDefaultSource = tray_id as i16;
|
||||
}
|
||||
|
||||
if let Some(ref orient) = opts.orientation {
|
||||
(*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_ORIENTATION);
|
||||
if orient.eq_ignore_ascii_case("landscape") {
|
||||
(*p_devmode).Anonymous1.Anonymous1.dmOrientation = DMORIENT_LANDSCAPE;
|
||||
} else {
|
||||
(*p_devmode).Anonymous1.Anonymous1.dmOrientation = DMORIENT_PORTRAIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(buf)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(buf)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "ICCery",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"identifier": "com.gronod.iccery",
|
||||
"build": {
|
||||
"frontendDist": "../src"
|
||||
|
||||
Reference in New Issue
Block a user