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.
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:
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).
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.
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
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.
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.
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.
When "Photoshop Manages Colors" is selected in Photoshop (and other professional imaging applications on macOS):
The application configures NSPrintInfo with the AP_ColorMatchingMode = AP_ApplicationColorMatching (and PMColorMatchingMode) job ticket property.
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.
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
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.
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.
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
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.
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).
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.
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.
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'
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:
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:
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.
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.
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.
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);
// 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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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.
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: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
NSPrintPaneldialog, or system-wide via the underlying CUPS administration interface.Since ICCery currently dispatches print jobs directly via the
lpcommand line utility (lp -d printer_name ...), it bypasses the native macOSNSPrintPanelentirely. 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
lpuses 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_propertiesinmacos.rsto:cupsctl WebInterface=yes(this is required as it is disabled by default on macOS for security reasons).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.
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
The real problem is two-fold: (a) the Preferences button opens the wrong place, and (b) the
lpprint command inmacos.rsdoes not pass the manufacturer-specific PPD options needed to disable driver colour management.Confirmed on this machine: The Canon Pro 9500 II series PPD exposes
CNIJIntent2/Color Modewith values1(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 usingAP_ColorMatchingMode=AP_ApplicationColorMatching(already done) and settingCNIJIntent2=4(Linear Tone) which applies no perceptual remapping.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 throughNSPrintPanel, not through CUPS defaults orlpoptionswhen 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()inmacos.rsto querylpoptions -p <printer> -land parse the output to discover colour management options (CNIJIntent2,ColorCorrection,EpsonColorMode, etc.). Then, inbuild_lp_args(), automatically append the correct manufacturer-specific option to disable driver colour management (e.g.,-o CNIJIntent2=4for Canon,-o ColorCorrection=Uncorrectedfor 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_lpipeline 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.
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):
NSPrintInfowith theAP_ColorMatchingMode = AP_ApplicationColorMatching(andPMColorMatchingMode) job ticket property.-o AP_ColorMatchingMode=AP_ApplicationColorMatchingalong with the uncorrected / linear driver mode (e.g.
-o CNIJIntent2=4for Canon Linear Tone, or-o ColorCorrection=Uncorrectedfor Gutenprint).Why the Current Implementation Fails on macOS
src-tauri/src/print/macos.rs, clicking "Preferences" runsopen 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.CNIJMediaType=42for Semi-gloss) and linear rasterization mode to avoid applying default perceptual color adjustments.AP_ColorMatchingMode=AP_ApplicationColorMatchinghook AND the discovered Media Type / linear tone PPD flags.Refined Solution
CNIJMediaType,MediaType,StpMediaType) so users can choose their exact paper stock directly in ICCery.build_lp_args(), ensure theAP_ColorMatchingMode=AP_ApplicationColorMatchinghook is always active alongside the selected Media Type and detected uncorrected/linear mode (CNIJIntent2=4).http://localhost:631/printers/<printer_name>), enabling users to inspect and set permanent queue defaults if desired.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.
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'
Native printer setttings dialog does now load as expected, but color management is not disabled.
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:
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
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:
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:
In macOS PrintCore, PMSessionSetColorMatchingMode does not accept a boolean parameter:
Passing a raw scalar 1 where a 64-bit object pointer is expected is guaranteed to crash in either branch.
The exported symbols in PrintCore are:
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.
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:
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;
}
// 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.