[Bug/High] Printer properties dialog reopens on driver defaults — captured ticket never restored, pre-selections unvalidated #217
Notifications
Due Date
No due date set.
Blocks
Reference: gronod/iccery-v2-mac#217
Reference in New Issue
Block a user
Summary
The Stage 2 printer-properties dialog (gear button → bound
NSPrintPanel) always opens on the queue's driver defaults. Stage 2 selections are not presented, and — more importantly — vendor PDE state captured in a previous dialog session is discarded. Printed output is unaffected:TicketWriteResolverapplies Stage 2 overrides unconditionally at spool time (#201 D6). This is a dialog-fidelity and state-loss defect, not an output defect.Observed on an Epson XP-55 (InkjetPrinter2 driver family), macOS:
Follow-up to #186 (dialog binding/pre-select) in the light of #201's ticket machinery, which that issue predates.
Root Cause Analysis
Line numbers:
develop@f7f6a8b.RC1 — the captured
PrintTicketis never restored when the panel opens (primary).PrintPanelService.runNativePanel(Sources/ICCery/Print/PrintPanelService.swift:90-118) builds a freshNSPrintInfo, binds the queue (PMTicketBridge.bind,PMTicketBridge.swift:87-97), and seedsPMSessionDefaultPrintSettings+PMSessionDefaultPageFormat— queue defaults. It then pre-writes only four keys (PageSize, detected media key, detected quality key,orientation-requested;applyInitialSelections, PrintPanelService.swift:214-244) plusPMPageFormatpaper (PMTicketBridge.applyPaper, :319-372).PMTicketBridge.restore(:166-229) — the mechanism that rehydrates a captured ticket — is invoked only byNativeTargetSpoolerS4 (NativeTargetSpooler.swift:162-166).PrintSessionViewModel.openPrinterPreferencesstores the ticket intocapturedTickets[queue](:259-261) butshowPropertieshas no ticket parameter (PrintPanelService.swift:56-80) — it is never passed back.Every vendor-PDE setting outside the four mirrored fields lives solely in that discarded ticket → the next open reverts to defaults. This fully explains "changes made in the driver are reflected in ICCery but revert when the dialog is reopened".
RC2 — pre-selection writes are never validated into the ticket.
After
applyInitialSelections, neitherPMSessionValidatePrintSettingsnorprintInfo.updateFromPMPrintSettings()runs beforerunModal(PrintPanelService.swift:106-154; contrastrestoreat PMTicketBridge.swift:197-203 and the spooler at NativeTargetSpooler.swift:187). The vendor PDE opens on an unreconciled ticket; drivers that derive pane state from validated/companion keys can present defaults even for the four keys we do write.RC3 — why InputSlot alone survives "Last Used Settings" (hypothesis — verify on hardware).
PMSessionDefaultPrintSettingssources persisted queue defaults (lpoptions / org.cups.PrintingPrefs), whereInputSlot/MediaSourcelands when the driver persists it; vendor PDE keys live only in the discardedPMPrintSettingsticket. Related evidence:MediaQualityConstraintsevaluates Epson*EPIJUIConstraintLHS keys (EPIJ_PSrc,EPIJ_FdSo) against lpoptions defaults specifically to "reproduce the driver's PDE state when it opens" (MediaQualityConstraints.swift:11-14) — Epson's PDE open-state is at least partially lpoptions-derived, which may limit how much a restored ticket alone fixes. Verify:lpoptions -p <queue>before vs after a driver session; compare against aPMPrintSettingsToOptionsdump of the pre-open ticket.Checked and dismissed:
makeRequest→TicketWriteResolverwrites unconditionally (D6); prints honour Stage 2 picks.PMPrintSettingsToOptions+ mirror (openPrinterPreferences:255-284) demonstrably returns driver changes to Stage 2.PMPrintSettingsSetValuerejection — logged warn-only; cannot explain wholesale defaults including state the driver itself set last session.Proposed Fix
Restore-then-override on the panel path, mirroring the spooler's S3–S5 order (docs/11 §native spool):
PrintSessionViewModel.openPrinterPreferencespassescapturedTickets[queue]intoshowProperties(new optionalticket:parameter).runNativePanelorder: bind (PMSessionSetCurrentPMPrinter+PMSessionDefault*) →PMTicketBridge.restore(ticket)when present (cross-queue refusal already built in, R3) →applyInitialSelections(Stage 2 wins over the ticket — same rule as D6) →PMSessionValidatePrintSettings+printInfo.updateFromPMPrintSettings()(warn-only; lets the driver reconcile companion keys) →applyPaper→ suppression layers ②–⑤ →runModal.NativeTargetSpooler.makePrintInfo) so unit tests can assert the ticket is honoured without driving the modal.ICCERY_DEBUG_PANEL_DUMP=1) logging thePMPrintSettingsToOptionsoutput and serialised ticket size immediately beforerunModaland after OK.PrintTicketis deliberately session-only (queue- and driver-version-specific; PrintTicket.swift:8-9). Cross-launch persistence of driver state is out of scope.Contingency (only if hardware verification shows the Epson PDE sources pane state from lpoptions defaults rather than the session ticket — see RC3): persist the Stage 2 mirror via
lpoptions -p <queue> -o …before opening the panel. This mutates user-visible queue defaults — discuss before doing it.Fallback (when no ticket exists for the queue but
capturedCupsOptions[queue]does): optionally replay the capturedk=vpairs asPMPrintSettingsSetValuewrites beneath the explicit selections.Acceptance Criteria
Dependencies
None blocking. Related: #186 (original dialog binding/pre-select — this supersedes its residual symptom), #201 (introduced
PrintTicket/restore, wired to the spooler only). Blocks #218 (paper-source feature) — both touchPrintPanelInitialSelections,runNativePanel, andTicketWriteResolver.References
Sources/ICCery/Print/PrintPanelService.swift—runNativePanel(:84-208),applyInitialSelections(:214-244)Sources/ICCery/Print/PMTicketBridge.swift—bind(:87-97),serialise(:121-156),restore(:166-229)Sources/ICCery/Print/NativeTargetSpooler.swift— S3–S5 order (:147-188)Sources/ICCery/Print/PrintSessionViewModel.swift—openPrinterPreferences(:223-294),capturedTickets(:45)docs/11-print-macos.md§ColorSync suppression layers ①–⑦ / §native spool S1–S14docs/issue-printer-quality-ignored.md— #201 analysis (D3, D6)