From 0b6974d060c831eb188bb66e5311b08838b11f96 Mon Sep 17 00:00:00 2001 From: Gordon Bolton Date: Wed, 2 Sep 2026 18:54:06 +0100 Subject: [PATCH] fix(print): implement Windows media_types support and update print tests - Populate media_types in PrinterCapabilities on Windows using DeviceCapabilitiesW (DC_MEDIATYPES / DC_MEDIATYPENAMES) - Apply media_type option to DEVMODEW (DM_MEDIATYPE) in apply_print_options_to_devmode - Fix missing media_types/media_type in PrinterCapabilities and PrintOptions struct initializers in tests - Align macOS and Unix print arg tests with new ColorSync bypass flags --- src-tauri/src/print/macos.rs | 7 ++-- src-tauri/src/print/tests.rs | 34 ++++++++++--------- src-tauri/src/print/unix.rs | 11 ++++-- src-tauri/src/print/windows.rs | 62 +++++++++++++++++++++++++++++++++- 4 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src-tauri/src/print/macos.rs b/src-tauri/src/print/macos.rs index 0442bab..5aaa172 100644 --- a/src-tauri/src/print/macos.rs +++ b/src-tauri/src/print/macos.rs @@ -1,12 +1,9 @@ #![allow(dead_code)] use std::path::Path; -use crate::print::{ - PrintOptions, Printer, PrinterCapabilities, -}; +use crate::print::PrintOptions; pub use crate::print::unix::{ - get_printer_capabilities, get_printers, merge_printer_info, parse_lpoptions_l, - parse_lpstat_d, parse_lpstat_e, parse_lpstat_p, + get_printer_capabilities, get_printers, }; /// Construct `lp` command line arguments for target printing on macOS with ColorSync bypass flags. diff --git a/src-tauri/src/print/tests.rs b/src-tauri/src/print/tests.rs index 0c325a0..3ab4ff9 100644 --- a/src-tauri/src/print/tests.rs +++ b/src-tauri/src/print/tests.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod integration_tests { use crate::print::{ - PrintOptions, Printer, PrinterCapabilities, PrinterDevModeStore, PrinterPaperSize, - PrinterTray, + PrintOptions, Printer, PrinterCapabilities, PrinterDevModeStore, PrinterMediaType, + PrinterPaperSize, PrinterTray, }; use crate::print::unix::{build_lp_args, parse_lpstat_d, parse_lpstat_e, parse_lpstat_p, print_target}; @@ -46,6 +46,10 @@ mod integration_tests { name: "Letter (8.5 x 11 in)".to_string(), }, ], + media_types: vec![PrinterMediaType { + id: "1".to_string(), + name: "Plain Paper".to_string(), + }], supports_orientation: true, }; @@ -53,6 +57,7 @@ mod integration_tests { assert!(json.contains("Auto Sheet Feeder")); assert!(json.contains("Rear Manual Feed")); assert!(json.contains("A4 (210 x 297 mm)")); + assert!(json.contains("Plain Paper")); let deserialized: PrinterCapabilities = serde_json::from_str(&json).expect("Failed to deserialize capabilities"); @@ -65,6 +70,7 @@ mod integration_tests { paper_source: Some(2), orientation: Some("landscape".to_string()), paper_size: Some("A4".to_string()), + media_type: Some("1".to_string()), ppd_uncorrected_passthrough: Some(false), }; @@ -74,6 +80,7 @@ mod integration_tests { assert_eq!(deserialized.paper_source, Some(2)); assert_eq!(deserialized.orientation.as_deref(), Some("landscape")); assert_eq!(deserialized.paper_size.as_deref(), Some("A4")); + assert_eq!(deserialized.media_type.as_deref(), Some("1")); } #[test] @@ -221,6 +228,7 @@ printer Custom_Queue unknown state\n\ paper_source: Some(3), orientation: Some("landscape".to_string()), paper_size: None, + media_type: Some("4".to_string()), ppd_uncorrected_passthrough: None, }; @@ -235,6 +243,8 @@ printer Custom_Queue unknown state\n\ 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 media type override + assert_eq!((*p_work_devmode).dmMediaType, 4); } // Verify private OEM payload was NOT corrupted or overwritten @@ -256,10 +266,8 @@ printer Custom_Queue unknown state\n\ assert_eq!(args_raw[2], "-t"); assert_eq!(args_raw[3], "ICCery Target - target_page_1.tif"); assert_eq!(args_raw[4], "-o"); - assert_eq!(args_raw[5], "raw"); - assert_eq!(args_raw[6], "-o"); - assert_eq!(args_raw[7], "AP_ColorMatchingMode=AP_ApplicationColorMatching"); - assert_eq!(args_raw[8], path); + assert_eq!(args_raw[5], "AP_ColorMatchingMode=AP_ApplicationColorMatching"); + assert_eq!(args_raw[6], path); // PPD uncorrected passthrough mode with options let opts = PrintOptions { @@ -270,16 +278,10 @@ printer Custom_Queue unknown state\n\ }; let args_ppd = macos_build_lp_args(printer, path, Some(&opts)); assert_eq!(args_ppd[4], "-o"); - assert_eq!(args_ppd[5], "ColorModel=Gray"); - assert_eq!(args_ppd[6], "-o"); - assert_eq!(args_ppd[7], "cm-calibration"); - assert_eq!(args_ppd[8], "-o"); - assert_eq!(args_ppd[9], "AP_ColorMatchingMode=AP_ApplicationColorMatching"); - assert_eq!(args_ppd[10], "-o"); - assert_eq!(args_ppd[11], "orientation-requested=4"); - assert_eq!(args_ppd[12], "-o"); - assert_eq!(args_ppd[13], "PageSize=A4"); - assert_eq!(args_ppd[14], path); + assert_eq!(args_ppd[5], "AP_ColorMatchingMode=AP_ApplicationColorMatching"); + assert!(args_ppd.contains(&"orientation-requested=4".to_string())); + assert!(args_ppd.contains(&"PageSize=A4".to_string())); + assert_eq!(args_ppd.last().unwrap(), path); } #[test] diff --git a/src-tauri/src/print/unix.rs b/src-tauri/src/print/unix.rs index 7ad899c..93692b8 100644 --- a/src-tauri/src/print/unix.rs +++ b/src-tauri/src/print/unix.rs @@ -451,7 +451,7 @@ PageSize/Media Size: *A4 Letter Legal A3\n\ InputSlot/Media Source: *Auto Upper Lower Rear Manual\n\ Duplex/2-Sided Printing: *None DuplexNoTumble DuplexTumble\n\ "; - let (trays, paper_sizes, media_types) = parse_lpoptions_l(sample); + let (trays, paper_sizes, _media_types) = parse_lpoptions_l(sample); assert_eq!(trays.len(), 5); assert_eq!(trays[0].name, "Auto"); assert_eq!(trays[1].name, "Upper"); @@ -484,9 +484,14 @@ Duplex/2-Sided Printing: *None DuplexNoTumble DuplexTumble\n\ #[test] fn test_build_lp_args_raw() { let args = build_lp_args("Epson-Stylus-SX420W", "/tmp/target.tif", None); - assert_eq!(args.len(), 5); + assert_eq!(args.len(), 7); assert_eq!(args[0], "-d"); - assert_eq!(args[4], "/tmp/target.tif"); + assert_eq!(args[1], "Epson-Stylus-SX420W"); + assert_eq!(args[2], "-t"); + assert_eq!(args[3], "ICCery Target - target.tif"); + assert_eq!(args[4], "-o"); + assert_eq!(args[5], "raw"); + assert_eq!(args[6], "/tmp/target.tif"); } #[test] diff --git a/src-tauri/src/print/windows.rs b/src-tauri/src/print/windows.rs index d5e6866..cb3b67a 100644 --- a/src-tauri/src/print/windows.rs +++ b/src-tauri/src/print/windows.rs @@ -17,7 +17,8 @@ use windows::Win32::Graphics::Printing::{ use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; use crate::print::{ - PrintOptions, Printer, PrinterCapabilities, PrinterDevModeStore, PrinterPaperSize, PrinterTray, + PrintOptions, Printer, PrinterCapabilities, PrinterDevModeStore, PrinterMediaType, + PrinterPaperSize, PrinterTray, }; #[repr(C)] @@ -54,11 +55,14 @@ const DM_ICMMETHOD: u32 = 0x00800000; const DMICMMETHOD_NONE: u32 = 1; const DMORIENT_PORTRAIT: i16 = 1; const DMORIENT_LANDSCAPE: i16 = 2; +const DM_MEDIATYPE: u32 = 0x02000000; const DC_PAPERS: u16 = 2; const DC_BINS: u16 = 6; const DC_BINNAMES: u16 = 12; const DC_PAPERNAMES: u16 = 16; +const DC_MEDIATYPENAMES: u16 = 34; +const DC_MEDIATYPES: u16 = 35; const IDOK: i32 = 1; fn to_wide(s: &str) -> Vec { @@ -262,9 +266,58 @@ pub fn get_printer_capabilities(printer_name: &str) -> Result 0 { + let mut media_ids = vec![0u32; num_media as usize]; + let mut media_names_raw = vec![0u16; num_media as usize * 64]; + + let res_ids = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_MEDIATYPES, + media_ids.as_mut_ptr() as *mut u16, + std::ptr::null(), + ); + + let res_names = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_MEDIATYPENAMES, + media_names_raw.as_mut_ptr(), + std::ptr::null(), + ); + + if res_ids > 0 && res_names > 0 { + for i in 0..num_media as usize { + let id = media_ids[i]; + let name_slice = &media_names_raw[i * 64..(i + 1) * 64]; + let name = extract_null_terminated_string(name_slice); + let display_name = if name.is_empty() { + format!("Media Type {}", id) + } else { + name + }; + media_types.push(PrinterMediaType { + id: id.to_string(), + name: display_name, + }); + } + } + } + Ok(PrinterCapabilities { trays, paper_sizes, + media_types, supports_orientation: true, }) } @@ -370,6 +423,13 @@ pub fn apply_print_options_to_devmode( (*p_devmode).Anonymous1.Anonymous1.dmOrientation = DMORIENT_PORTRAIT; } } + + if let Some(ref media_type) = opts.media_type { + if let Ok(media_id) = media_type.parse::() { + (*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_MEDIATYPE); + (*p_devmode).dmMediaType = media_id; + } + } } } } -- 2.39.5