fix(print): preserve cached DEVMODE printer preferences on Windows (#36) #37
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "tmp_tauri",
|
"name": "tmp_tauri",
|
||||||
"version": "0.1.0",
|
"version": "0.1.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tmp_tauri",
|
"name": "tmp_tauri",
|
||||||
"version": "0.1.0",
|
"version": "0.1.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/plugin-dialog": "^2.7.2"
|
"@tauri-apps/plugin-dialog": "^2.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tmp_tauri",
|
"name": "tmp_tauri",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
|
|||||||
Generated
+1
-1
@@ -1606,7 +1606,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iccery"
|
name = "iccery"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"image",
|
"image",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iccery"
|
name = "iccery"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -186,5 +186,61 @@ printer Custom_Queue unknown state\n\
|
|||||||
let err = res.unwrap_err();
|
let err = res.unwrap_err();
|
||||||
assert!(err.contains("Target TIFF file not found"));
|
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
|
/// Print target TIFF file to printer with GDI ICM bypass, DEVMODE overrides, and auto-fit scaling
|
||||||
pub fn print_target(
|
pub fn print_target(
|
||||||
printer_name: &str,
|
printer_name: &str,
|
||||||
@@ -407,16 +436,16 @@ pub fn print_target(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut devmode_buf = if devmode_size > 0 {
|
let mut devmode_buf = if devmode_size > 0 {
|
||||||
let mut buf = devmode_store
|
let mut buf = if let Some(cached) = devmode_store.and_then(|s| s.get(printer_name)) {
|
||||||
.and_then(|s| s.get(printer_name))
|
let mut b = cached;
|
||||||
.unwrap_or_else(|| vec![0u8; devmode_size as usize]);
|
if b.len() < devmode_size as usize {
|
||||||
|
b.resize(devmode_size as usize, 0);
|
||||||
if buf.len() < devmode_size as usize {
|
|
||||||
buf.resize(devmode_size as usize, 0);
|
|
||||||
}
|
}
|
||||||
|
b
|
||||||
let p_devmode = buf.as_mut_ptr() as *mut DEVMODEW;
|
} else {
|
||||||
let res = DocumentPropertiesW(
|
let mut b = vec![0u8; devmode_size as usize];
|
||||||
|
let p_devmode = b.as_mut_ptr() as *mut DEVMODEW;
|
||||||
|
let _ = DocumentPropertiesW(
|
||||||
HWND::default(),
|
HWND::default(),
|
||||||
h_printer,
|
h_printer,
|
||||||
PCWSTR(printer_wide.as_ptr()),
|
PCWSTR(printer_wide.as_ptr()),
|
||||||
@@ -424,35 +453,15 @@ pub fn print_target(
|
|||||||
None,
|
None,
|
||||||
DM_OUT_BUFFER,
|
DM_OUT_BUFFER,
|
||||||
);
|
);
|
||||||
|
b
|
||||||
|
};
|
||||||
|
|
||||||
if res >= 0 {
|
let p_devmode = buf.as_mut_ptr() as *mut DEVMODEW;
|
||||||
// Apply strict ICM bypass
|
apply_print_options_to_devmode(p_devmode, options);
|
||||||
(*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)
|
Some(buf)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let p_devmode_ptr = devmode_buf
|
let p_devmode_ptr = devmode_buf
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "ICCery",
|
"productName": "ICCery",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"identifier": "com.gronod.iccery",
|
"identifier": "com.gronod.iccery",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
|
|||||||
Reference in New Issue
Block a user