fix(print): implement Windows media_types support and resolve CI build failure #192

Merged
gronod merged 5 commits from fix/windows-media-types into development 2026-09-02 19:07:43 +01:00
4 changed files with 89 additions and 25 deletions
+2 -5
View File
@@ -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.
+18 -16
View File
@@ -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]
+8 -3
View File
@@ -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]
+61 -1
View File
@@ -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<u16> {
@@ -262,9 +266,58 @@ pub fn get_printer_capabilities(printer_name: &str) -> Result<PrinterCapabilitie
}
}
// Query media types
let num_media = DeviceCapabilitiesW(
PCWSTR(printer_wide.as_ptr()),
PCWSTR::null(),
DC_MEDIATYPES,
std::ptr::null_mut(),
std::ptr::null(),
);
let mut media_types = Vec::new();
if num_media > 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::<u32>() {
(*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_MEDIATYPE);
(*p_devmode).dmMediaType = media_id;
}
}
}
}
}