bug : on Mac OS no ability to disable colour management in Epson or Canon printer drivers #188

Closed
opened 2026-09-02 15:56:51 +01:00 by gronod · 7 comments
Owner

Attempted to test with Epson XP-55 driver and Canon Pro 9500 II driver, clicking preferences goes to the System Settings Printers and Scanners tab, and doesn't go direct to the selected printer. No options available to disable print driver colour management.

Attempted to test with Epson XP-55 driver and Canon Pro 9500 II driver, clicking preferences goes to the System Settings Printers and Scanners tab, and doesn't go direct to the selected printer. No options available to disable print driver colour management.
gronod added the Kind/Bug
Priority
Critical
1
labels 2026-09-02 15:56:51 +01:00
Author
Owner

Investigation Results

The Issue

Currently, clicking the "Preferences" button in the ICCery UI on macOS invokes the following command in src-tauri/src/print/macos.rs:

std::process::Command::new("open")
    .args(["x-apple.systempreferences:com.apple.preference.printfax"])
    .spawn();

This simply opens the standard macOS "Printers & Scanners" settings pane. In macOS, unlike Windows, this settings pane does not provide access to driver-level configuration (such as Media Type, Color Matching, or disabling color management). macOS expects these options to be configured either at print-time via the NSPrintPanel dialog, or system-wide via the underlying CUPS administration interface.

Since ICCery currently dispatches print jobs directly via the lp command line utility (lp -d printer_name ...), it bypasses the native macOS NSPrintPanel entirely. As a result, the user has no opportunity to select their media type or disable printer color management (e.g., "Off (No Color Adjustment)" for Epson/Canon) before the target is printed. While ICCery does attempt to suppress ColorSync using -o AP_ColorMatchingMode=AP_ApplicationColorMatching, manufacturer-specific driver color management might still be active unless disabled in the driver options.

The Solution

To allow users to configure these default driver options (like media type and color management) so that lp uses them, we must direct the user to the CUPS Web Interface, which is the native way to manage default print queue options on macOS.

The proposed solution is to modify show_printer_properties in macos.rs to:

  1. Programmatically enable the CUPS Web Interface by executing cupsctl WebInterface=yes (this is required as it is disabled by default on macOS for security reasons).
  2. Open the user's default browser to the specific printer's CUPS administration page: http://localhost:631/printers/<printer_name>.

This will allow the user to navigate to the "Set Default Options" menu for their specific printer, where they can permanently disable color management and set the correct media type for their targets.

**Investigation Results** ### The Issue Currently, clicking the "Preferences" button in the ICCery UI on macOS invokes the following command in `src-tauri/src/print/macos.rs`: ```rust std::process::Command::new("open") .args(["x-apple.systempreferences:com.apple.preference.printfax"]) .spawn(); ``` This simply opens the standard macOS "Printers & Scanners" settings pane. In macOS, unlike Windows, this settings pane does not provide access to driver-level configuration (such as Media Type, Color Matching, or disabling color management). macOS expects these options to be configured either at print-time via the `NSPrintPanel` dialog, or system-wide via the underlying CUPS administration interface. Since ICCery currently dispatches print jobs directly via the `lp` command line utility (`lp -d printer_name ...`), it bypasses the native macOS `NSPrintPanel` entirely. As a result, the user has no opportunity to select their media type or disable printer color management (e.g., "Off (No Color Adjustment)" for Epson/Canon) before the target is printed. While ICCery does attempt to suppress ColorSync using `-o AP_ColorMatchingMode=AP_ApplicationColorMatching`, manufacturer-specific driver color management might still be active unless disabled in the driver options. ### The Solution To allow users to configure these default driver options (like media type and color management) so that `lp` uses them, we must direct the user to the CUPS Web Interface, which is the native way to manage default print queue options on macOS. The proposed solution is to modify `show_printer_properties` in `macos.rs` to: 1. Programmatically enable the CUPS Web Interface by executing `cupsctl WebInterface=yes` (this is required as it is disabled by default on macOS for security reasons). 2. Open the user's default browser to the specific printer's CUPS administration page: `http://localhost:631/printers/<printer_name>`. This will allow the user to navigate to the "Set Default Options" menu for their specific printer, where they can permanently disable color management and set the correct media type for their targets.
Author
Owner

Revised Investigation — The CUPS Web Interface Alone Won't Solve This

The original analysis proposed opening the CUPS web interface. After deeper investigation, this is insufficient for the following reasons:

Why the CUPS Web Interface Is Not Enough

  1. The real problem is two-fold: (a) the Preferences button opens the wrong place, and (b) the lp print command in macos.rs does not pass the manufacturer-specific PPD options needed to disable driver colour management.

  2. Confirmed on this machine: The Canon Pro 9500 II series PPD exposes CNIJIntent2/Color Mode with values 1 (Standard), 4 (Linear Tone), 3 (Vivid Photo). There is no explicit 'None' or 'Off' value — to disable Canon driver colour management for ICC profiling, the correct approach is to bypass the Canon driver colour pipeline entirely by using AP_ColorMatchingMode=AP_ApplicationColorMatching (already done) and setting CNIJIntent2=4 (Linear Tone) which applies no perceptual remapping.

  3. Epson drivers typically expose ColorCorrection=Uncorrected (Gutenprint) or a driver-specific option via the native print dialog's "Off (No Color Adjustment)" setting, but this option is only accessible through NSPrintPanel, not through CUPS defaults or lpoptions when using the proprietary Epson driver.

The Correct Solution (Two Parts)

Part 1 — Backend: Automatically detect and pass driver-specific colour-disable options at print time

Modify show_printer_properties() in macos.rs to query lpoptions -p <printer> -l and parse the output to discover colour management options (CNIJIntent2, ColorCorrection, EpsonColorMode, etc.). Then, in build_lp_args(), automatically append the correct manufacturer-specific option to disable driver colour management (e.g., -o CNIJIntent2=4 for Canon, -o ColorCorrection=Uncorrected for Gutenprint/Epson).

Part 2 — Frontend: Expose discovered media type and colour options in the ICCery UI

Instead of trying to redirect the user to an external interface, extend the existing get_printer_capabilities / parse_lpoptions_l pipeline to also extract media type options (e.g., CNIJMediaType) and present them as a dropdown in the ICCery UI alongside the existing Paper Source and Orientation controls. This is far more user-friendly than sending users to the CUPS web interface and is consistent with the Windows experience.

Part 3 — Preferences button: Open CUPS web interface as a fallback

As a fallback/advanced option, keep the Preferences button but change it to open the CUPS web interface for the specific printer (as originally proposed), giving power users access to the full range of PPD options.

**Revised Investigation — The CUPS Web Interface Alone Won't Solve This** The original analysis proposed opening the CUPS web interface. After deeper investigation, this is insufficient for the following reasons: ### Why the CUPS Web Interface Is Not Enough 1. **The real problem is two-fold**: (a) the Preferences button opens the wrong place, and (b) the `lp` print command in `macos.rs` does not pass the manufacturer-specific PPD options needed to disable driver colour management. 2. **Confirmed on this machine**: The Canon Pro 9500 II series PPD exposes `CNIJIntent2/Color Mode` with values `1` (Standard), `4` (Linear Tone), `3` (Vivid Photo). There is **no explicit 'None' or 'Off' value** — to disable Canon driver colour management for ICC profiling, the correct approach is to bypass the Canon driver colour pipeline entirely by using `AP_ColorMatchingMode=AP_ApplicationColorMatching` (already done) **and** setting `CNIJIntent2=4` (Linear Tone) which applies no perceptual remapping. 3. **Epson drivers** typically expose `ColorCorrection=Uncorrected` (Gutenprint) or a driver-specific option via the native print dialog's "Off (No Color Adjustment)" setting, but this option is **only accessible through `NSPrintPanel`**, not through CUPS defaults or `lpoptions` when using the proprietary Epson driver. ### The Correct Solution (Two Parts) **Part 1 — Backend: Automatically detect and pass driver-specific colour-disable options at print time** Modify `show_printer_properties()` in `macos.rs` to query `lpoptions -p <printer> -l` and parse the output to discover colour management options (`CNIJIntent2`, `ColorCorrection`, `EpsonColorMode`, etc.). Then, in `build_lp_args()`, automatically append the correct manufacturer-specific option to disable driver colour management (e.g., `-o CNIJIntent2=4` for Canon, `-o ColorCorrection=Uncorrected` for Gutenprint/Epson). **Part 2 — Frontend: Expose discovered media type and colour options in the ICCery UI** Instead of trying to redirect the user to an external interface, extend the existing `get_printer_capabilities` / `parse_lpoptions_l` pipeline to also extract media type options (e.g., `CNIJMediaType`) and present them as a dropdown in the ICCery UI alongside the existing Paper Source and Orientation controls. This is far more user-friendly than sending users to the CUPS web interface and is consistent with the Windows experience. **Part 3 — Preferences button: Open CUPS web interface as a fallback** As a fallback/advanced option, keep the Preferences button but change it to open the CUPS web interface for the specific printer (as originally proposed), giving power users access to the full range of PPD options.
Author
Owner

Detailed Technical Analysis — The 'Application Manages Colors' Hook & Driver PDE Interaction

Photoshop Print Dialog Hook Analysis

When "Photoshop Manages Colors" is selected in Photoshop (and other professional imaging applications on macOS):

  1. The application configures NSPrintInfo with the AP_ColorMatchingMode = AP_ApplicationColorMatching (and PMColorMatchingMode) job ticket property.
  2. When the print system / PDE (Print Dialog Extension) evaluates this job ticket, macOS and the manufacturer PDE (Canon / Epson) explicitly disable (grey out) the Color Matching pane and driver color correction controls, displaying the status message that color management is disabled / handled by the application.
  3. At the CUPS spooling and filter level, this translates directly to passing:
    -o AP_ColorMatchingMode=AP_ApplicationColorMatching
    along with the uncorrected / linear driver mode (e.g. -o CNIJIntent2=4 for Canon Linear Tone, or -o ColorCorrection=Uncorrected for Gutenprint).

Why the Current Implementation Fails on macOS

  1. Preferences Button: In src-tauri/src/print/macos.rs, clicking "Preferences" runs open x-apple.systempreferences:com.apple.preference.printfax, which only opens the macOS System Settings pane where driver options (such as Media Type and Print Quality) are completely absent.
  2. Missing Media Type & Linear Driver Flags: Unlike PostScript printers, photo inkjet drivers (Canon BJ / Epson ESC/P-R) require the specific Media Type (e.g. CNIJMediaType=42 for Semi-gloss) and linear rasterization mode to avoid applying default perceptual color adjustments.
  3. Target Spooling: When printing profiling targets, ICCery must supply both the AP_ColorMatchingMode=AP_ApplicationColorMatching hook AND the discovered Media Type / linear tone PPD flags.

Refined Solution

  1. Frontend: Provide a Media Type dropdown populated dynamically from the printer's PPD capabilities (CNIJMediaType, MediaType, StpMediaType) so users can choose their exact paper stock directly in ICCery.
  2. Backend Spooling: In build_lp_args(), ensure the AP_ColorMatchingMode=AP_ApplicationColorMatching hook is always active alongside the selected Media Type and detected uncorrected/linear mode (CNIJIntent2=4).
  3. Preferences / Fallback: Change the Preferences button to open the CUPS web administration interface (http://localhost:631/printers/<printer_name>), enabling users to inspect and set permanent queue defaults if desired.
**Detailed Technical Analysis — The 'Application Manages Colors' Hook & Driver PDE Interaction** ### Photoshop Print Dialog Hook Analysis When "Photoshop Manages Colors" is selected in Photoshop (and other professional imaging applications on macOS): 1. The application configures `NSPrintInfo` with the `AP_ColorMatchingMode = AP_ApplicationColorMatching` (and `PMColorMatchingMode`) job ticket property. 2. When the print system / PDE (Print Dialog Extension) evaluates this job ticket, macOS and the manufacturer PDE (Canon / Epson) explicitly disable (grey out) the Color Matching pane and driver color correction controls, displaying the status message that color management is disabled / handled by the application. 3. At the CUPS spooling and filter level, this translates directly to passing: `-o AP_ColorMatchingMode=AP_ApplicationColorMatching` along with the uncorrected / linear driver mode (e.g. `-o CNIJIntent2=4` for Canon Linear Tone, or `-o ColorCorrection=Uncorrected` for Gutenprint). ### Why the Current Implementation Fails on macOS 1. **Preferences Button**: In `src-tauri/src/print/macos.rs`, clicking "Preferences" runs `open x-apple.systempreferences:com.apple.preference.printfax`, which only opens the macOS System Settings pane where driver options (such as Media Type and Print Quality) are completely absent. 2. **Missing Media Type & Linear Driver Flags**: Unlike PostScript printers, photo inkjet drivers (Canon BJ / Epson ESC/P-R) require the specific Media Type (e.g. `CNIJMediaType=42` for Semi-gloss) and linear rasterization mode to avoid applying default perceptual color adjustments. 3. **Target Spooling**: When printing profiling targets, ICCery must supply both the `AP_ColorMatchingMode=AP_ApplicationColorMatching` hook AND the discovered Media Type / linear tone PPD flags. ### Refined Solution 1. **Frontend**: Provide a Media Type dropdown populated dynamically from the printer's PPD capabilities (`CNIJMediaType`, `MediaType`, `StpMediaType`) so users can choose their exact paper stock directly in ICCery. 2. **Backend Spooling**: In `build_lp_args()`, ensure the `AP_ColorMatchingMode=AP_ApplicationColorMatching` hook is always active alongside the selected Media Type and detected uncorrected/linear mode (`CNIJIntent2=4`). 3. **Preferences / Fallback**: Change the Preferences button to open the CUPS web administration interface (`http://localhost:631/printers/<printer_name>`), enabling users to inspect and set permanent queue defaults if desired.
Author
Owner

The "fix" attempts to open the CUPS web interface, but this is disabled by default, and this is not the expected behaviour. The required printer properties dialog is not a web page, rather a native window.

The "fix" attempts to open the CUPS web interface, but this is disabled by default, and this is not the expected behaviour. The required printer properties dialog is not a web page, rather a native window.
gronod reopened this issue 2026-09-02 19:10:14 +01:00
Author
Owner

Printer preferences button does not launch the expected dialog, with error message:

Could not open printer properties: No NSPrinter found for 'EPSON_XP_55_Series_2'

Printer preferences button does not launch the expected dialog, with error message: Could not open printer properties: No NSPrinter found for 'EPSON_XP_55_Series_2'
Author
Owner

Native printer setttings dialog does now load as expected, but color management is not disabled.

Native printer setttings dialog does now load as expected, but color management is not disabled.
Author
Owner

Executive Summary
The segmentation fault (EXC_BAD_ACCESS / SIGSEGV at address 0x0000000000000020) is caused by passing a primitive integer (1, intended as a boolean lock flag) into a register that PrintCore expects to contain an object reference pointer (CFStringRef).
The failure originates directly in set_session_color_matching_mode() within /src-tauri/src/print/macos.rs. The function attempts speculative, brute-force transmutes of the private symbol PMSessionSetColorMatchingMode into fabricated 3-argument C function signatures:

  • Attempt 1 (lines 80–92): PMSessionSetColorMatchingMode(session, mode, 1)
  • Attempt 2 (lines 94–105): PMSessionSetColorMatchingMode(session, 1, mode)
    In both permutations, passing integer 1 into a pointer parameter results in PMSessionSetColorMatchingMode forwarding 0x1 to PMString::Equal(__CFString const*, __CFString const*, unsigned long). When PMString::Equal calls CFStringCompare and issues objc_msgSend(0x1, @selector(length)), the Objective-C runtime attempts to resolve class metadata against base address 0x0, dereferencing offset +0x20 and triggering an immediate kernel page fault.
    Crash Analysis & Register State
    Tracing the thread state alongside the disassembly in the crash log confirms the execution path:
    Thread 0 Crashed:: main Dispatch queue: com.apple.main-thread
    0 libobjc.A.dylib 0x7ff80cd802ff lookUpImpOrForward + 40
    1 libobjc.A.dylib 0x7ff80cd7fd1b _objc_msgSend_uncached + 75
    2 CoreFoundation 0x7ff80d1c9473 CFStringCompare + 24
    3 PrintCore 0x7ff81cd2e9df PMString::Equal(__CFString const*, __CFString const*, unsigned long) + 19
    4 PrintCore 0x7ff81cd2a573 PMSessionSetColorMatchingMode + 46
    5 iccery 0x101558f3b 0x1011c5000 + 3751739

Register Dump at Fault

  • rip: 0x7ff80cd802ff (lookUpImpOrForward + 40)
  • cr2: 0x0000000000000020 (Faulting user data read address)
  • rdi: 0x0000000000000001 (First argument / self receiver passed to objc_msgSend)
  • rsi: 0x00007ff82d1995ff (Selector length)
  • rdx: 0x0000000000000000
    Instruction Stream
    48 bb f8 ff ff ff ff 7f 00 0f movabs rbx, 0xf007ffffffffff8
    [48] 8b 42 20 mov rax, QWORD PTR [rdx+0x20] <== CRASH
    48 21 d8 and rax, rbx

In lookUpImpOrForward, the runtime attempts to inspect the class dispatch table of the receiver (rdi = 0x1). Because 0x1 is not a valid object pointer, rdx resolves to null (0x0). Dereferencing [rdx + 0x20] attempts to read memory at 0x20, causing KERN_INVALID_ADDRESS at 0x0000000000000020.
The instruction at PMSessionSetColorMatchingMode + 46 is the very first function call inside the function. It compares the mode argument passed by the caller against an internal constant via PMString::Equal. One of the two string arguments passed to PMString::Equal was 0x1.
Root Cause in macos.rs
The crash is driven by four structural defects in set_session_color_matching_mode:

  1. Fabricated 3-Argument Function Signatures (Attempts 1 & 2)
    Lines 84 and 98 define and execute speculative signatures:
    // Attempt 1: lines 84-88
    type Fn3 = unsafe extern "C" fn(PMPrintSession, *const CFString, u8) -> c_int;
    let fn3: Fn3 = std::mem::transmute(main_ptr);
    let status = fn3(pm_session, mode_ptr, 1);

// Attempt 2: lines 98-101
type Fn3Rev = unsafe extern "C" fn(PMPrintSession, u8, *const CFString) -> c_int;
let fn3rev: Fn3Rev = std::mem::transmute(main_ptr);
let status = fn3rev(pm_session, 1, mode_ptr);

In the System V AMD64 ABI:

  • arg1 is passed in rdi
  • arg2 is passed in rsi
  • arg3 is passed in rdx
    In macOS PrintCore, PMSessionSetColorMatchingMode does not accept a boolean parameter:
  • If PMSessionSetColorMatchingMode takes two arguments (PMPrintSession session, CFStringRef mode):
    • In Attempt 1, arg1 = pm_session, arg2 = mode_ptr, and arg3 = 1 (in rdx, ignored by the callee). If the mode string returns a non-zero status (e.g. rejected by the driver or session state), execution proceeds directly to Attempt 2.
    • In Attempt 2, arg1 = pm_session, arg2 = 1 (in rsi), and arg3 = mode_ptr. The callee treats arg2 (1) as CFStringRef mode. At offset +46, it executes PMString::Equal(mode, ...), passing 0x1 into CFStringCompare and crashing.
  • If PMSessionSetColorMatchingMode takes three arguments (PMPrintSession session, PMPrintSettings settings, CFStringRef mode):
    • In Attempt 1, arg1 = pm_session, arg2 = mode_ptr (passed as settings), and arg3 = 1 (passed as mode). The callee immediately attempts PMString::Equal(mode, ...) using arg3 (0x1) and crashes on the first attempt.
      Passing a raw scalar 1 where a 64-bit object pointer is expected is guaranteed to crash in either branch.
  1. Misinterpretation of Exported Symbols
    The exported symbols in PrintCore are:
  • PMSessionSetColorMatchingMode(PMPrintSession, CFStringRef): Sets the session colour matching mode.
  • PMSessionSetColorMatchingModeNoLock(PMPrintSession, CFStringRef): Sets the mode explicitly leaving UI unlocked.
  • PMSessionSetColorMatchingModeLock(PMPrintSession, Boolean): Toggles the lock state of the colour matching UI independently.
    Lines 80 and 94 gate Attempts 1 and 2 on if !main_ptr.is_null() && !lock_ptr.is_null(). The implementation assumed that the existence of PMSessionSetColorMatchingModeLock indicated that PMSessionSetColorMatchingMode accepts a 3-argument signature combining both actions. They are independent entry points.
  1. Complete Disconnect from PMPrintSettings
    In Apple Core Printing, print settings tickets (PMPrintSettings) hold job-specific configuration keys. In run_native_print_panel (line 217), pm_settings is created:
    let pm_settings: PMPrintSettings = print_info.PMPrintSettings().as_ptr() as PMPrintSettings;

However, set_session_color_matching_mode (line 35) only accepts pm_session: PMPrintSession. If the session itself has not bound the active PMPrintSettings dictionary prior to calling PMSessionSetColorMatchingMode, the SPI fails or returns parameter validation errors.
Remediation
To resolve the crash and correctly apply the colour matching override:

  • Remove Attempts 1 and 2 completely. Never invoke main_ptr with 3 arguments or pass primitive booleans into object pointer positions.

  • Use the correct 2-argument C signatures:

    • PMSessionSetColorMatchingMode(PMPrintSession, *const CFString) -> OSStatus
    • PMSessionSetColorMatchingModeLock(PMPrintSession, Boolean) -> OSStatus
  • Set the lock independently via PMSessionSetColorMatchingModeLock after setting the mode.
    Corrected set_session_color_matching_mode
    #[cfg(target_os = "macos")]
    unsafe fn set_session_color_matching_mode(pm_session: PMPrintSession) {
    use std::ffi::{c_char, c_void};
    use objc2_core_foundation::CFString;
    use objc2_application_services::{PMSessionGetCurrentPrinter, PMPrinter};

    if pm_session.is_null() {
    log::warn!("pm_session is null; skipping private color-matching SPI");
    return;
    }

    let mut current_printer: PMPrinter = std::ptr::null_mut();
    let printer_status = PMSessionGetCurrentPrinter(pm_session, (&mut current_printer).into());
    if printer_status != 0 || current_printer.is_null() {
    log::warn!(
    "PMSessionGetCurrentPrinter failed ({}) or returned null printer; skipping SPI",
    printer_status
    );
    return;
    }

    const RTLD_DEFAULT: *mut c_void = (-2isize) as *mut c_void;

    let sym_main = c"PMSessionSetColorMatchingMode";
    let sym_nolock = c"PMSessionSetColorMatchingModeNoLock";
    let sym_lock = c"PMSessionSetColorMatchingModeLock";

    let main_ptr = dlsym(RTLD_DEFAULT, sym_main.as_ptr() as *const c_char);
    let nolock_ptr = dlsym(RTLD_DEFAULT, sym_nolock.as_ptr() as *const c_char);
    let lock_ptr = dlsym(RTLD_DEFAULT, sym_lock.as_ptr() as *const c_char);

    type ModeFn = unsafe extern "C" fn(PMPrintSession, *const CFString) -> i32;
    type LockFn = unsafe extern "C" fn(PMPrintSession, u8) -> i32;

    const MODES: &[&str] = &[
    "AP_ApplicationColorMatching",
    "AP_ColorSyncMatching",
    "AP_VendorColorMatching",
    ];

    let mut set_success = false;

    // 1. Set mode via PMSessionSetColorMatchingMode or PMSessionSetColorMatchingModeNoLock
    for &mode_str in MODES {
    let mode = CFString::from_str(mode_str);
    let mode_ptr = &*mode as *const CFString;

     if !main_ptr.is_null() {
         let set_fn: ModeFn = std::mem::transmute(main_ptr);
         let status = set_fn(pm_session, mode_ptr);
         log::info!("PMSessionSetColorMatchingMode({}) returned {}", mode_str, status);
         if status == 0 {
             set_success = true;
             break;
         }
     }
    
     if !nolock_ptr.is_null() {
         let nolock_fn: ModeFn = std::mem::transmute(nolock_ptr);
         let status = nolock_fn(pm_session, mode_ptr);
         log::info!("PMSessionSetColorMatchingModeNoLock({}) returned {}", mode_str, status);
         if status == 0 {
             set_success = true;
             break;
         }
     }
    

    }

    // 2. Lock controls independently if the symbol exists
    if !lock_ptr.is_null() {
    let lock_fn: LockFn = std::mem::transmute(lock_ptr);
    let lock_status = lock_fn(pm_session, 1);
    log::info!("PMSessionSetColorMatchingModeLock(1) returned {}", lock_status);
    }

    if !set_success {
    log::warn!("Private PMSessionSetColorMatchingMode SPI calls did not return 0; falling back to PMPrintSettingsSetValue");
    }
    }

Note that lines 226–241 of macos.rs already call:
PMPrintSettingsSetValue(pm_settings, cm_key_ref, Some(cm_val_ref), true);

Passing locked: true directly to PMPrintSettingsSetValue for AP_ColorMatchingMode locks the setting in the CUPS ticket on macOS 10.6 through macOS 14+. Once the invalid 3-argument calls are eliminated from set_session_color_matching_mode, the print dialog will initialise without faulting.

Executive Summary The segmentation fault (EXC_BAD_ACCESS / SIGSEGV at address 0x0000000000000020) is caused by passing a primitive integer (1, intended as a boolean lock flag) into a register that PrintCore expects to contain an object reference pointer (CFStringRef). The failure originates directly in set_session_color_matching_mode() within /src-tauri/src/print/macos.rs. The function attempts speculative, brute-force transmutes of the private symbol PMSessionSetColorMatchingMode into fabricated 3-argument C function signatures: * Attempt 1 (lines 80–92): PMSessionSetColorMatchingMode(session, mode, 1) * Attempt 2 (lines 94–105): PMSessionSetColorMatchingMode(session, 1, mode) In both permutations, passing integer 1 into a pointer parameter results in PMSessionSetColorMatchingMode forwarding 0x1 to PMString::Equal(__CFString const*, __CFString const*, unsigned long). When PMString::Equal calls CFStringCompare and issues objc_msgSend(0x1, @selector(length)), the Objective-C runtime attempts to resolve class metadata against base address 0x0, dereferencing offset +0x20 and triggering an immediate kernel page fault. Crash Analysis & Register State Tracing the thread state alongside the disassembly in the crash log confirms the execution path: Thread 0 Crashed:: main Dispatch queue: com.apple.main-thread 0 libobjc.A.dylib 0x7ff80cd802ff lookUpImpOrForward + 40 1 libobjc.A.dylib 0x7ff80cd7fd1b _objc_msgSend_uncached + 75 2 CoreFoundation 0x7ff80d1c9473 CFStringCompare + 24 3 PrintCore 0x7ff81cd2e9df PMString::Equal(__CFString const*, __CFString const*, unsigned long) + 19 4 PrintCore 0x7ff81cd2a573 PMSessionSetColorMatchingMode + 46 5 iccery 0x101558f3b 0x1011c5000 + 3751739 Register Dump at Fault * rip: 0x7ff80cd802ff (lookUpImpOrForward + 40) * cr2: 0x0000000000000020 (Faulting user data read address) * rdi: 0x0000000000000001 (First argument / self receiver passed to objc_msgSend) * rsi: 0x00007ff82d1995ff (Selector length) * rdx: 0x0000000000000000 Instruction Stream 48 bb f8 ff ff ff ff 7f 00 0f movabs rbx, 0xf007ffffffffff8 [48] 8b 42 20 mov rax, QWORD PTR [rdx+0x20] <== CRASH 48 21 d8 and rax, rbx In lookUpImpOrForward, the runtime attempts to inspect the class dispatch table of the receiver (rdi = 0x1). Because 0x1 is not a valid object pointer, rdx resolves to null (0x0). Dereferencing [rdx + 0x20] attempts to read memory at 0x20, causing KERN_INVALID_ADDRESS at 0x0000000000000020. The instruction at PMSessionSetColorMatchingMode + 46 is the very first function call inside the function. It compares the mode argument passed by the caller against an internal constant via PMString::Equal. One of the two string arguments passed to PMString::Equal was 0x1. Root Cause in macos.rs The crash is driven by four structural defects in set_session_color_matching_mode: 1. Fabricated 3-Argument Function Signatures (Attempts 1 & 2) Lines 84 and 98 define and execute speculative signatures: // Attempt 1: lines 84-88 type Fn3 = unsafe extern "C" fn(PMPrintSession, *const CFString, u8) -> c_int; let fn3: Fn3 = std::mem::transmute(main_ptr); let status = fn3(pm_session, mode_ptr, 1); // Attempt 2: lines 98-101 type Fn3Rev = unsafe extern "C" fn(PMPrintSession, u8, *const CFString) -> c_int; let fn3rev: Fn3Rev = std::mem::transmute(main_ptr); let status = fn3rev(pm_session, 1, mode_ptr); In the System V AMD64 ABI: * arg1 is passed in rdi * arg2 is passed in rsi * arg3 is passed in rdx In macOS PrintCore, PMSessionSetColorMatchingMode does not accept a boolean parameter: * If PMSessionSetColorMatchingMode takes two arguments (PMPrintSession session, CFStringRef mode): * In Attempt 1, arg1 = pm_session, arg2 = mode_ptr, and arg3 = 1 (in rdx, ignored by the callee). If the mode string returns a non-zero status (e.g. rejected by the driver or session state), execution proceeds directly to Attempt 2. * In Attempt 2, arg1 = pm_session, arg2 = 1 (in rsi), and arg3 = mode_ptr. The callee treats arg2 (1) as CFStringRef mode. At offset +46, it executes PMString::Equal(mode, ...), passing 0x1 into CFStringCompare and crashing. * If PMSessionSetColorMatchingMode takes three arguments (PMPrintSession session, PMPrintSettings settings, CFStringRef mode): * In Attempt 1, arg1 = pm_session, arg2 = mode_ptr (passed as settings), and arg3 = 1 (passed as mode). The callee immediately attempts PMString::Equal(mode, ...) using arg3 (0x1) and crashes on the first attempt. Passing a raw scalar 1 where a 64-bit object pointer is expected is guaranteed to crash in either branch. 2. Misinterpretation of Exported Symbols The exported symbols in PrintCore are: * PMSessionSetColorMatchingMode(PMPrintSession, CFStringRef): Sets the session colour matching mode. * PMSessionSetColorMatchingModeNoLock(PMPrintSession, CFStringRef): Sets the mode explicitly leaving UI unlocked. * PMSessionSetColorMatchingModeLock(PMPrintSession, Boolean): Toggles the lock state of the colour matching UI independently. Lines 80 and 94 gate Attempts 1 and 2 on if !main_ptr.is_null() && !lock_ptr.is_null(). The implementation assumed that the existence of PMSessionSetColorMatchingModeLock indicated that PMSessionSetColorMatchingMode accepts a 3-argument signature combining both actions. They are independent entry points. 3. Complete Disconnect from PMPrintSettings In Apple Core Printing, print settings tickets (PMPrintSettings) hold job-specific configuration keys. In run_native_print_panel (line 217), pm_settings is created: let pm_settings: PMPrintSettings = print_info.PMPrintSettings().as_ptr() as PMPrintSettings; However, set_session_color_matching_mode (line 35) only accepts pm_session: PMPrintSession. If the session itself has not bound the active PMPrintSettings dictionary prior to calling PMSessionSetColorMatchingMode, the SPI fails or returns parameter validation errors. Remediation To resolve the crash and correctly apply the colour matching override: * Remove Attempts 1 and 2 completely. Never invoke main_ptr with 3 arguments or pass primitive booleans into object pointer positions. * Use the correct 2-argument C signatures: * PMSessionSetColorMatchingMode(PMPrintSession, *const CFString) -> OSStatus * PMSessionSetColorMatchingModeLock(PMPrintSession, Boolean) -> OSStatus * Set the lock independently via PMSessionSetColorMatchingModeLock after setting the mode. Corrected set_session_color_matching_mode #[cfg(target_os = "macos")] unsafe fn set_session_color_matching_mode(pm_session: PMPrintSession) { use std::ffi::{c_char, c_void}; use objc2_core_foundation::CFString; use objc2_application_services::{PMSessionGetCurrentPrinter, PMPrinter}; if pm_session.is_null() { log::warn!("pm_session is null; skipping private color-matching SPI"); return; } let mut current_printer: PMPrinter = std::ptr::null_mut(); let printer_status = PMSessionGetCurrentPrinter(pm_session, (&mut current_printer).into()); if printer_status != 0 || current_printer.is_null() { log::warn!( "PMSessionGetCurrentPrinter failed ({}) or returned null printer; skipping SPI", printer_status ); return; } const RTLD_DEFAULT: *mut c_void = (-2isize) as *mut c_void; let sym_main = c"PMSessionSetColorMatchingMode"; let sym_nolock = c"PMSessionSetColorMatchingModeNoLock"; let sym_lock = c"PMSessionSetColorMatchingModeLock"; let main_ptr = dlsym(RTLD_DEFAULT, sym_main.as_ptr() as *const c_char); let nolock_ptr = dlsym(RTLD_DEFAULT, sym_nolock.as_ptr() as *const c_char); let lock_ptr = dlsym(RTLD_DEFAULT, sym_lock.as_ptr() as *const c_char); type ModeFn = unsafe extern "C" fn(PMPrintSession, *const CFString) -> i32; type LockFn = unsafe extern "C" fn(PMPrintSession, u8) -> i32; const MODES: &[&str] = &[ "AP_ApplicationColorMatching", "AP_ColorSyncMatching", "AP_VendorColorMatching", ]; let mut set_success = false; // 1. Set mode via PMSessionSetColorMatchingMode or PMSessionSetColorMatchingModeNoLock for &mode_str in MODES { let mode = CFString::from_str(mode_str); let mode_ptr = &*mode as *const CFString; if !main_ptr.is_null() { let set_fn: ModeFn = std::mem::transmute(main_ptr); let status = set_fn(pm_session, mode_ptr); log::info!("PMSessionSetColorMatchingMode({}) returned {}", mode_str, status); if status == 0 { set_success = true; break; } } if !nolock_ptr.is_null() { let nolock_fn: ModeFn = std::mem::transmute(nolock_ptr); let status = nolock_fn(pm_session, mode_ptr); log::info!("PMSessionSetColorMatchingModeNoLock({}) returned {}", mode_str, status); if status == 0 { set_success = true; break; } } } // 2. Lock controls independently if the symbol exists if !lock_ptr.is_null() { let lock_fn: LockFn = std::mem::transmute(lock_ptr); let lock_status = lock_fn(pm_session, 1); log::info!("PMSessionSetColorMatchingModeLock(1) returned {}", lock_status); } if !set_success { log::warn!("Private PMSessionSetColorMatchingMode SPI calls did not return 0; falling back to PMPrintSettingsSetValue"); } } Note that lines 226–241 of macos.rs already call: PMPrintSettingsSetValue(pm_settings, cm_key_ref, Some(cm_val_ref), true); Passing locked: true directly to PMPrintSettingsSetValue for AP_ColorMatchingMode locks the setting in the CUPS ticket on macOS 10.6 through macOS 14+. Once the invalid 3-argument calls are eliminated from set_session_color_matching_mode, the print dialog will initialise without faulting.
Sign in to join this conversation.