Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
16 KiB
11 — macOS printing and ColorSync suppression
Line numbers refer to ICCery v0.8.5 (
/tmp/ICCeryat analysis time) and the Gronod ArgyllCMS 3.5.0 fork.
This is the most implementation-sensitive chapter. A rewrite that opens System Settings or the CUPS web UI instead of NSPrintPanel will regress #188.
macOS (src-tauri/src/print/macos.rs) — MOST DETAIL
864 lines. This is the ColorSync-suppression engine built for #92 and rewritten for #188.
What Preferences opens — NSPrintPanel vs CUPS web UI vs System Settings
Issue #188 (user report): clicking Preferences opened System Settings → Printers & Scanners and did not bind the selected Epson/Canon queue. No driver PDE, so no "Off (No Color Adjustment)".
Current code (AGENTS.md:105-118, macos.rs:245-516, 679-710): Preferences opens a native AppKit NSPrintPanel, pre-bound to the selected CUPS destination via Core Printing. It is not:
- CUPS web UI (
http://localhost:631/printers/…) - System Settings → Printers & Scanners
NSWorkspaceopen of the printerlpoptionsGUI
Linux/Windows do not share this panel. Default button title is "Use Settings" (macos.rs:439) — this is a settings-capture dialog, not a print-now dialog. Actual spooling is a later lp invocation.
Must run on the Cocoa main thread. show_printer_properties (macos.rs:689-710):
- Resolve
display_nameoff the main thread viaunix::get_printer_display_name(lpoptions -p). app.run_on_main_thread→run_native_print_panel.tokio::sync::oneshotawaits the result.
objc2 crates (Cargo.toml:42-47, AGENTS.md:120-126)
objc2 0.6 MainThreadMarker
objc2-app-kit 0.3.2 features = ["NSPrintInfo", "NSPrintPanel", "NSPrinter"]
objc2-foundation 0.3.2 NSString
objc2-core-foundation 0.3.2 CFString, CFType
objc2-application-services 0.3.2 features = ["PMCore", "PMDefinitions", "PrintCore"]
PMPrintSession, PMPrinter, PMPrintSettings, PMPageFormat, …
Local extern "C" { fn free; fn dlsym; }. No libloading crate.
Binding the selected printer (macos.rs:276-347)
CUPS destination IDs ≠ NSPrinter display names. Core Printing "Printer ID" is the CUPS queue name.
printer_id_cf = CFString(printer_name)
pm_printer = PMPrinterCreateFromPrinterID(&printer_id_cf)
if pm_printer is null:
fallback NSPrinter::printerWithName(display_name)
print_info.setPrinter(ns_printer)
print_info.setUpPrintOperationDefaultValues()
if that also fails → Err("No printer found for '…'")
pm_session = print_info.PMPrintSession()
pm_settings = print_info.PMPrintSettings()
pm_page_format = print_info.PMPageFormat()
if pm_printer was created from ID:
PMSessionSetCurrentPMPrinter(pm_session, pm_printer) // non-zero → Err
PMSessionDefaultPrintSettings(pm_session, pm_settings) // warn on fail
PMSessionDefaultPageFormat(pm_session, pm_page_format) // warn on fail
Printer.display_name is the CUPS printer-info cached at enumeration. That is what System Settings shows ("Epson XP-55 Series") while printer_name is the queue id (EPSON_XP_55_Series). #188 failed because the old path used the wrong name with the wrong API.
PMPrinter from PMPrinterCreateFromPrinterID is released with PMRelease on all exit paths (cancel, error, success). The NSPrinter fallback path leaves pm_printer null so no release.
ColorSync suppression strategy — UI click to lp
End-to-end, six independent layers. All of them exist because no single Apple API is sufficient across Epson PDE / Canon PDE / cgpdftoraster / CUPS.
[Preferences click]
show_printer_properties
run_on_main_thread
run_native_print_panel
① PMSessionSetCurrentPMPrinter bind queue
② set_session_color_matching_mode SPI gray out PDE Color Matching
③ PMPrintSettingsSetValue AP_ColorMatchingMode + dotted
④ detect_driver_color_bypass → SetValue pre-select Canon/Epson/Gutenprint "off"
⑤ NSPrintInfo.printSettings dictionary same keys for AppKit PDEs
NSPrintPanel.runModalWithPrintInfo
user picks media / quality (color locked)
⑥ PMPrintSettingsToOptions → filter → PrintPropertiesResult
[frontend]
capturedCupsOptions[printer] = cups_options
[Print Target]
print_target_native → macos::print_target → build_lp_args
ALWAYS -o AP_ColorMatchingMode=AP_ApplicationColorMatching
ALWAYS -o AP.ColorMatchingMode=AP_ApplicationColorMatching
THEN captured cups_options as -o k=v
THEN media_type if not already present (detected key)
THEN detect_driver_color_bypass if no color-bypass key yet
THEN orientation / PageSize if not already present
lp -d <queue> -t "ICCery Target - …" … <tiff>
Linux uses -o raw instead of AP_* flags. macOS does not use -o raw: a raw queue would skip the raster filter that actually understands AP_ColorMatchingMode. The macOS strategy is "tell the filter the application already matched color", not "skip the filter".
Layer ② — private SPI via dlsym (set_session_color_matching_mode, macos.rs:32-130)
Used by Photoshop, Lightroom, X-Rite i1Profiler to gray-out Color Matching in driver PDEs. Undocumented; resolved at runtime so the binary does not hard-link a private symbol.
RTLD_DEFAULT = (-2isize) as *mut c_void
dlsym(RTLD_DEFAULT, "PMSessionSetColorMatchingModeLock")
dlsym(RTLD_DEFAULT, "PMSessionSetColorMatchingMode")
dlsym(RTLD_DEFAULT, "PMSessionSetColorMatchingModeNoLock")
Signature (all three):
OSStatus PMSessionSetColorMatchingModeLock (PMPrintSession, CFStringRef mode);
OSStatus PMSessionSetColorMatchingMode (PMPrintSession, CFStringRef mode);
OSStatus PMSessionSetColorMatchingModeNoLock(PMPrintSession, CFStringRef mode);
Rust:
type ModeFn = unsafe extern "C" fn(PMPrintSession, *const CFString) -> i32;
status == 0 is success (noErr).
Priority:
PMSessionSetColorMatchingModeLock— sets and locks the UI in one call (controls grayed).PMSessionSetColorMatchingMode— sets mode; lock behaviour unspecified.PMSessionSetColorMatchingModeNoLock— sets without locking.
For each symbol, try mode strings in order:
"AP_ApplicationColorMatching" // kPMApplicationColorMatching
"ApplicationColorMatching" // unprefixed alias
First (symbol, mode) that returns 0 wins; then break both loops.
Preconditions: pm_session non-null; PMSessionGetCurrentPrinter succeeds with a non-null printer. Otherwise skip SPI (session has no printer to attach a PDE to).
Stub on non-macOS: always false so unit tests compile.
If all fail: log "All PMSessionSetColorMatchingMode calls failed; color controls may not be grayed" and fall through to public PMPrintSettingsSetValue.
Why AP_ColorSyncMatching and AP_VendorColorMatching are avoided
AGENTS.md:118, macos.rs:82-88:
Only application-managed color-matching modes are appropriate for profiling targets. ColorSync or vendor modes would apply color management and corrupt the target patches.
| Mode string | Effect on a profiling target |
|---|---|
AP_ApplicationColorMatching / ApplicationColorMatching |
Used. "Application already did color." Filters/PDEs must not transform pixels. UI Color Matching grayed. |
AP_ColorSyncMatching |
Avoided. ColorSync applies the printer/display profile. Patches become color-managed. |
AP_VendorColorMatching |
Avoided. Epson/Canon driver color engine (ICM inside the PDE). Same corruption. |
ICCery-CPU uses a different vocabulary (APCustomColorMatching / APColorSync / APPrinterExtension on PMColorMatchingMode). See the CPU section. Do not mix the two dictionaries.
Layer ③ — PMPrintSettingsSetValue (macos.rs:357-375)
key = "AP_ColorMatchingMode" and "AP.ColorMatchingMode"
val = "AP_ApplicationColorMatching"
lock = true
PMPrintSettingsSetValue(pm_settings, key, val, true)
The dotted AP.ColorMatchingMode is a legacy form used by some raster drivers and PDEs (macos.rs:540-541). Both are always written.
Layer ④ — pre-select driver "no color adjustment" in the panel (macos.rs:377-397, 422-432)
lpoptions -p <printer> -l → unix::detect_driver_color_bypass:
| Driver | Key | Value |
|---|---|---|
| Canon | CNIJIntent2 |
4 |
| Canon (old) | CNIJIntent |
4 |
| Epson | EPIJ_CCor |
0 (if that key exists, preferred) |
| Epson | EPIJ_CMat |
3 |
| Gutenprint | StpColorCorrection |
Uncorrected |
| generic | ColorCorrection |
Uncorrected |
| Epson alt | EpsonColorMode |
Off |
Written twice: PMPrintSettingsSetValue(..., lock=false) and print_info.printSettings().insert. Lock is false so the user can still change media/quality; only color matching is intended to be locked by the SPI.
Layer ⑤ — NSPrintInfo.printSettings dictionary (macos.rs:399-432)
AppKit PDEs and some raster drivers read this NSMutableDictionary, not the PM object.
print_info.updateFromPMPageFormat()
print_info.updateFromPMPrintSettings()
print_settings.insert("AP_ColorMatchingMode", "AP_ApplicationColorMatching")
print_settings.insert("AP.ColorMatchingMode", "AP_ApplicationColorMatching")
print_settings.insert(<bypass_key>, <bypass_val>)
NSString is transmuted to &AnyObject for the dictionary (macos.rs:414-416).
Panel options (macos.rs:434-449)
NSPrintPanel::printPanel(mtm)
opts = NSPrintPanelOptions::all()
opts.insert(ShowsPageSetupAccessory)
panel.setOptions(opts)
panel.setDefaultButtonTitle(Some("Use Settings"))
response = panel.runModalWithPrintInfo(&print_info)
response != 1 (NSModalResponseOK / NSOKButton) → Ok(None) (cancellation is not an error). PMPrinter released.
Layer ⑥ — PMPrintSettingsToOptions capture (macos.rs:452-515)
After OK:
PMSessionGetCurrentPrinter→PMPrinterGetID→selected_printerstring.panel.printInfo().PMPrintSettings().PMPrintSettingsToOptions(updated_settings, &mut opts_ptr)— CUPS malloc'd C string ofkey=value key=value ….- Copy via
CStr,free(opts_ptr). filter_cups_options_string→cups_options.extract_media_type_from_options→media_type.- Return
PrintPropertiesResult { selected_printer, options: PrintOptions { media_type, cups_options, ppd_uncorrected_passthrough: Some(true), ..Default } }.
Failure of PMPrintSettingsToOptions is a hard Err.
RELEVANT_CUPS_OPTION_KEYS (macos.rs:142-174)
Forwarded from the panel to lp:
Media: MediaType, CNIJMediaType, EPIJ_Medi, StpMediaType
Tray: InputSlot, AP_D_InputSlot
Size: PageSize
Color: CNIJIntent2, CNIJIntent, EPIJ_CMat, EPIJ_CCor, EPIJ_OSColMat,
ColorCorrection, StpColorCorrection, EpsonColorMode, ColorModel
Quality: Resolution, cupsPrintQuality, Quality, EPIJ_Quality, CNIJQuality,
StpQuality, OutputMode
Duplex: Duplex, sides
is_relevant_cups_option (macos.rs:178-204) additionally:
- Drops empty keys
- Drops
com.apple.*ticket keys - Drops
AP_ColorMatchingModeandAP.ColorMatchingMode(we always set those ourselves) - Drops empty values (
AP_D_InputSlot=) - Drops
collate,copies,pserrorhandler-requested,job-sheets - Keeps unknown non-
com.*keys (permissive: unknown driver keys survive)
build_lp_args (macos.rs:518-643)
Always, even with options=None:
lp -d <printer> -t "ICCery Target - <filename>"
-o AP_ColorMatchingMode=AP_ApplicationColorMatching
-o AP.ColorMatchingMode=AP_ApplicationColorMatching
… captured / detected options …
<tiff_path>
Order after the two AP_* flags:
- Parse
opts.cups_optionsinto-o k=v, record lowercased keys inadded_keys. - If
media_typeset and none ofmediatype/cnijmediatype/epij_medi/stpmediatypealready added:detect_media_type_key(lpoptions)and add it. - If no color-bypass key yet (
cnijintent2,cnijintent,epij_cmat,epij_ccor,epij_oscolmat,colorcorrection,stpcolorcorrection,epsoncolormode):detect_driver_color_bypassand add. Not gated onppd_uncorrected_passthrough. - Orientation →
orientation-requested=4|3unless already present. PageSize=unlesspagesizealready present.
ppd_uncorrected_passthrough is stored from the panel but does not change macOS lp flags. There is no -o raw on macOS.
print_target (macos.rs:646-677)
Exists-check, build_lp_args, Command::new("lp").args(&args).output(). Error wrapping same pattern as Unix ("macOS CUPS print job failed: …").
Cancellation as None
run_native_print_panel → Ok(None) on cancel. show_printer_properties propagates that. Frontend: if (result === null) { showNotification("info", "Printer properties dialog cancelled."); return; }.
Preferences crash / #188
#188 was not a segfault; it was a wrong UI: System Settings instead of the driver PDE, so color management could not be disabled on Epson XP-55 / Canon Pro 9500 II.
The rewrite:
- Bind by CUPS Printer ID (
PMPrinterCreateFromPrinterID+PMSessionSetCurrentPMPrinter), not by opening System Settings. display_namefallback forNSPrinter::printerWithName.- Private SPI to lock Color Matching.
- Dual AP_* keys (underscore + dotted).
- Driver-specific PPD bypass pre-selected and re-applied on
lp. - Capture via
PMPrintSettingsToOptionsintocapturedCupsOptions.
PMPrinter lifetime is explicit PMRelease on every path. SPI is dlsym'd so missing symbols on old OS X do not prevent launch. Panel must be main-thread (MainThreadMarker::new().ok_or("Print panel must be invoked on the main thread")).
macOS tests (macos.rs:712-863 + tests.rs:278-318)
- Filter drops
com.apple.*,collate,copies,AP_ColorMatchingMode, emptyAP_D_InputSlot; keepsMediaType,EPIJ_CMat,PageSize,CNIJIntent2,ColorCorrection. extract_media_type_from_optionsprefersMediaTypethenEPIJ_Medi.build_lp_argsalways contains both AP_* flags; captured options win over explicitmedia_type/ orientation / auto color-bypass; last arg is the TIFF path.detect_driver_color_bypassCanon4, Epson3, GutenprintUncorrected.- Missing TIFF errors.
ColorSync suppression — complete key/SPI/flag roster
Private SPI symbols (dlsym, macOS only)
PMSessionSetColorMatchingModeLock (PMPrintSession, CFStringRef) -> i32
PMSessionSetColorMatchingMode (PMPrintSession, CFStringRef) -> i32
PMSessionSetColorMatchingModeNoLock (PMPrintSession, CFStringRef) -> i32
Mode strings attempted: AP_ApplicationColorMatching, ApplicationColorMatching.
Public Core Printing used around the SPI:
PMPrinterCreateFromPrinterID
PMSessionSetCurrentPMPrinter
PMSessionGetCurrentPrinter
PMSessionDefaultPrintSettings
PMSessionDefaultPageFormat
PMPrintSettingsSetValue
PMPrintSettingsToOptions
PMPrinterGetID
PMRelease
AppKit: NSPrintInfo, NSPrintPanel, NSPrinter::printerWithName, NSPrintPanelOptions::all + ShowsPageSetupAccessory.
CUPS / lp flags
| Flag | Platform | When |
|---|---|---|
-o AP_ColorMatchingMode=AP_ApplicationColorMatching |
macOS | always |
-o AP.ColorMatchingMode=AP_ApplicationColorMatching |
macOS | always |
-o CNIJIntent2=4 |
macOS (auto) / if captured | Canon |
-o CNIJIntent=4 |
macOS auto if no Intent2 | Canon old |
-o EPIJ_CCor=0 |
macOS auto if present in lpoptions | Epson |
-o EPIJ_CMat=3 |
macOS auto | Epson Off |
-o StpColorCorrection=Uncorrected |
macOS auto | Gutenprint |
-o ColorCorrection=Uncorrected |
macOS auto | generic |
-o EpsonColorMode=Off |
macOS auto | Epson alt |
-o CNIJMediaType= / EPIJ_Medi= / StpMediaType= / MediaType= |
macOS (detected key); Linux always MediaType= |
media |
-o PageSize= |
macOS/Linux | if set |
-o orientation-requested=3|4 |
macOS/Linux | portrait/landscape |
-o raw |
Linux only | default |
-o ColorModel=Gray -o cm-calibration |
Linux only | PPD fallback checkbox |
captured InputSlot, AP_D_InputSlot, Resolution, cupsPrintQuality, Quality, EPIJ_Quality, CNIJQuality, StpQuality, OutputMode, Duplex, sides, EPIJ_OSColMat, ColorModel |
macOS | if panel produced them |
PPD keys (detection)
Media: CNIJMediaType, EPIJ_Medi, StpMediaType, MediaType, MediaSource, InputSlot, PageSize, MediaSize
Color: CNIJIntent2, CNIJIntent, EPIJ_CCor, EPIJ_CMat, StpColorCorrection, ColorCorrection, EpsonColorMode