<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="https://ayuch.dev/feed_style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <tabi:metadata xmlns:tabi="https://github.com/welpo/tabi">
        <tabi:base_url>https:&#x2F;&#x2F;ayuch.dev</tabi:base_url>
        <tabi:separator>
            •
        </tabi:separator>
        <tabi:about_feeds>This is a web feed, also known as an Atom feed. Subscribe by copying the URL from the address bar into your newsreader. Visit About Feeds to learn more and get started. It&#x27;s free.</tabi:about_feeds>
        <tabi:visit_the_site>Visit website</tabi:visit_the_site>
        <tabi:recent_posts>Recent posts</tabi:recent_posts>
        <tabi:last_updated_on>Updated on $DATE</tabi:last_updated_on>
        <tabi:default_theme>dark</tabi:default_theme>
        <tabi:post_listing_date>updated</tabi:post_listing_date>
        <tabi:current_section>OpenPrinting</tabi:current_section>
    </tabi:metadata><link rel="extra-stylesheet" href="https://ayuch.dev/skins/blue.css?h=a4dc1e94d3f5759784d2" /><title>~/ayu.ch - OpenPrinting</title>
        <subtitle>Ayu&#x27;s personal blog about programming, technology, and life.</subtitle>
    <link href="https://ayuch.dev/tags/openprinting/atom.xml" rel="self" type="application/atom+xml"/>
    <link href="https://ayuch.dev/tags/openprinting/" rel="alternate" type="text/html"/>
    <generator uri="https://www.getzola.org/">Zola</generator><updated>2026-08-18T00:00:00+00:00</updated><id>https://ayuch.dev/tags/openprinting/atom.xml</id><entry xml:lang="en">
        <title>GSoC 2026 Submission Report</title>
        <published>2026-08-18T00:00:00+00:00</published>
        <updated>2026-08-18T00:00:00+00:00</updated>
        <author>
            <name>Ayush Chauhan</name>
        </author>
        <link rel="alternate" href="https://ayuch.dev/blog/gsoc-2026-report/" type="text/html"/>
        <id>https://ayuch.dev/blog/gsoc-2026-report/</id>
        
            <content type="html">&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;gsoc-banner.png&quot; alt=&quot;banner&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“A ship in harbor is safe, but that is not what ships are built for.”&lt;&#x2F;em&gt; – Jhon A. Shedd&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;project-cosmic-desktop-print-dialog&quot;&gt;Project: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;summerofcode.withgoogle.com&#x2F;programs&#x2F;2026&#x2F;projects&#x2F;vTB6j0HO&quot;&gt;COSMIC Desktop Print Dialog&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h4 id=&quot;organization-the-linux-foundation&quot;&gt;&lt;em&gt;Organization: The Linux Foundation&lt;&#x2F;em&gt;&lt;&#x2F;h4&gt;
&lt;br&#x2F;&gt;
&lt;p&gt;Hello! This is my final report for my GSoC 2026 project: COSMIC Desktop Print Dialog. The goal of this project was to build a CPDB-based print dialog for the COSMIC Desktop Environment and integrate it with &lt;code&gt;xdg-desktop-portal-cosmic&lt;&#x2F;code&gt; (PreparePrint + Print) so desktop and sandboxed apps can print through a native COSMIC UX.&lt;&#x2F;p&gt;
&lt;p&gt;TLDR: My GSoC project focused on bringing native print dialog support to the COSMIC desktop environment through two interconnected efforts: modernizing the Rust CPDB client library and building a fully functional print portal for xdg-desktop-portal-cosmic. The CPDB library work is &lt;strong&gt;done and merged upstream&lt;&#x2F;strong&gt; into &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenPrinting&#x2F;cpdb-rs&quot;&gt;&lt;code&gt;OpenPrinting&#x2F;cpdb-rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and published on crates.io as &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;cpdb-rs&#x2F;0.1.1&quot;&gt;&lt;code&gt;v0.1.1&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, and the print dialog is &lt;strong&gt;feature-complete and in review&lt;&#x2F;strong&gt; on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pop-os&#x2F;xdg-desktop-portal-cosmic&#x2F;pull&#x2F;331&quot;&gt;&lt;code&gt;pop-os&#x2F;xdg-desktop-portal-cosmic#331&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. The project is complete according to the proposal, all that remains is feedback from the review cycle and the follow-up work that a newer portal API will unlock.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Here is a detailed overview of my GSoC project:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;table-of-content&quot;&gt;Table of Content&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#background-why-this-project-exists&quot;&gt;Background: why this project exists&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#goals-of-the-project&quot;&gt;Goals of the project&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#part-one-updating-cpdb-rs&quot;&gt;Part one: updating &lt;code&gt;cpdb-rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#part-two-the-cosmic-print-dialog-and-portal&quot;&gt;Part two: The COSMIC Print dialog and portal&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#current-state&quot;&gt;Current state&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#what-s-left-to-do&quot;&gt;What’s left to do&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#challenges-faced-during-the-program&quot;&gt;Challenges faced during the program&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#what-i-learned&quot;&gt;What I learned&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#thanks&quot;&gt;Thanks&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;gsoc-2026-report&#x2F;#links&quot;&gt;Links&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;background-why-this-project-exists&quot;&gt;Background: why this project exists&lt;&#x2F;h2&gt;
&lt;p&gt;To explain the goal I need to introduce three things that had to be glued together.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;COSMIC&lt;&#x2F;strong&gt; is System76’s new desktop environment, written in Rust on top of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pop-os&#x2F;libcosmic&quot;&gt;&lt;code&gt;libcosmic&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, which itself builds on the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;iced.rs&quot;&gt;&lt;code&gt;iced&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; GUI toolkit. It is a new DE, and until this project it had no print dialog of its own. If you press &lt;kbd&gt;Ctrl&lt;&#x2F;kbd&gt;+&lt;kbd&gt;P&lt;&#x2F;kbd&gt; in an application on COSMIC, you get whatever dialog the application’s own toolkit provides, usually the GTK one, which looks and behaves like a different desktop.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;XDG Desktop Portals&lt;&#x2F;strong&gt; are the standard way for applications, especially sandboxed ones, to ask the desktop to do something on their behalf like opening a file, taking a screenshot, or like in our case printing a document. The application never gets direct access to your printers, It talks D-Bus to a portal, and the desktop’s portal implementation shows the UI and does the work. &lt;code&gt;xdg-desktop-portal-cosmic&lt;&#x2F;code&gt; is COSMIC’s implementation, and it had backends for screenshots, screencasting, and file choosing, but the &lt;code&gt;org.freedesktop.impl.portal.Print&lt;&#x2F;code&gt; interface was missing entirely.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;CPDB (Common Print Dialog Backends)&lt;&#x2F;strong&gt; is an OpenPrinting project that solves a long-standing duplication problem. Historically every toolkit (GTK, Qt, LibreOffice) implemented its own print dialog and its own CUPS integration. CPDB inverts that: print systems (like CUPS) each expose a &lt;strong&gt;backend&lt;&#x2F;strong&gt; process over D-Bus, and any dialog can be a &lt;strong&gt;frontend&lt;&#x2F;strong&gt; that discovers all the installed backends and talks to them through one common interface. Write your dialog once and it works with every print system that ships a CPDB backend.&lt;&#x2F;p&gt;
&lt;p&gt;So the shape of the project was clear: make COSMIC a good CPDB frontend, and expose that frontend through the print portal.&lt;&#x2F;p&gt;


&lt;noscript&gt;
    &lt;strong&gt;⚠️ JavaScript is required to render the diagram.&lt;&#x2F;strong&gt;
&lt;&#x2F;noscript&gt;
&lt;pre class=&quot;mermaid invertible-image&quot;&gt;
    graph TD
App[&quot;Application&lt;br&#x2F;&gt;(Flatpak &#x2F; native)&quot;]
Portal[&quot;xdg-desktop-portal&quot;]
Cosmic[&quot;xdg-desktop-portal-cosmic&lt;br&#x2F;&gt;&lt;i&gt;the dialog lives here&lt;&#x2F;i&gt;&quot;]
Backend[&quot;cpdb-backend-cups,&lt;br&#x2F;&gt;cpdb-backend-cups, …&quot;]
CUPS[&quot;CUPS&quot;]

    App --&gt;|&quot;D-Bus:&lt;br&#x2F;&gt;org.freedesktop.portal.Print&quot;| Portal
    Portal --&gt; Cosmic
    Cosmic --&gt;|&quot;cpdb-rs&lt;br&#x2F;&gt;D-Bus: org.openprinting.PrintBackend&quot;| Backend
    Backend --&gt; CUPS
&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;goals-of-the-project&quot;&gt;Goals of the project&lt;&#x2F;h2&gt;
&lt;p&gt;As per the proposal:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Modernize &lt;code&gt;cpdb-rs&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; so that Rust GUI applications, specifically async, event-driven ones like &lt;code&gt;iced&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;libcosmic&lt;&#x2F;code&gt;, can use CPDB comfortably without depending on GNOME LIB and GIO runtime. I planned to use &lt;code&gt;zbus&lt;&#x2F;code&gt; here.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Implement &lt;code&gt;org.freedesktop.impl.portal.Print&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; in &lt;code&gt;xdg-desktop-portal-cosmic&lt;&#x2F;code&gt;, covering both the &lt;code&gt;PreparePrint&lt;&#x2F;code&gt; and &lt;code&gt;Print&lt;&#x2F;code&gt; methods of the portal contract.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Build a production-quality print dialog&lt;&#x2F;strong&gt; with live printer discovery, the full set of print settings, sensible defaults, settings persistence and localization.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Handle the unhappy paths&lt;&#x2F;strong&gt;: no printers, a printer that disappears mid-dialog, a backend that goes away, a job that fails to submit.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;part-one-updating-cpdb-rs&quot;&gt;Part one: updating &lt;code&gt;cpdb-rs&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;the-problem-with-the-old-bindings&quot;&gt;The problem with the old bindings&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;cpdb-rs&lt;&#x2F;code&gt; already existed as a set of safe-ish Rust wrappers around &lt;code&gt;cpdb-libs&lt;&#x2F;code&gt;, the C frontend library. The approach would have worked, but it had some costs for a consumer like COSMIC:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It drags in GLib.&lt;&#x2F;strong&gt; &lt;code&gt;cpdb-libs&lt;&#x2F;code&gt; uses GLib’s main loop for its D-Bus plumbing and its callbacks. An &lt;code&gt;iced&lt;&#x2F;code&gt; application already has its own event loop and its own async runtime. Running a GLib main loop next to it, just to get printer events, means we use an extra CPU thread and an extra dependency.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Callbacks with async Rust.&lt;&#x2F;strong&gt; The C API delivers “printer added” and “printer removed” through function pointers invoked from GLib’s thread. Getting that into an &lt;code&gt;iced&lt;&#x2F;code&gt; &lt;code&gt;Subscription&lt;&#x2F;code&gt; means bouncing through channels and &lt;code&gt;unsafe&lt;&#x2F;code&gt; at every step.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;It needs C headers and a build environment.&lt;&#x2F;strong&gt; Anyone building COSMIC’s portal would have needed &lt;code&gt;cpdb-libs&lt;&#x2F;code&gt; development headers installed, with introduces another layer of friction in development, testing and distribution.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;After discussing this with &lt;strong&gt;Michael (@mmstick)&lt;&#x2F;strong&gt; he suggested to use zbus which solves all these problems and was already being used in COSMIC, and so I did.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-i-built&quot;&gt;What I built&lt;&#x2F;h3&gt;
&lt;p&gt;I wrote a completely new, pure-Rust, async CPDB client on top of &lt;code&gt;zbus&lt;&#x2F;code&gt;, with &lt;strong&gt;no C dependency at all&lt;&#x2F;strong&gt;. The crate now has two features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zbus-backend&lt;&#x2F;code&gt; &lt;em&gt;(default)&lt;&#x2F;em&gt;: the new native async client.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ffi&lt;&#x2F;code&gt; &lt;em&gt;(optional)&lt;&#x2F;em&gt;: the original C bindings, moved into a separate workspace member, &lt;code&gt;cpdb-sys&lt;&#x2F;code&gt;, and preserved.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Now anyone can use &lt;code&gt;cpdb-rs&lt;&#x2F;code&gt; with just pure async rust, and with tokio as well:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; cpdb_rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;CpdbClient&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; client&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; CpdbClient&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; printers&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; client&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;get_all_printers&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;for&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;printers&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-macro z-rust&quot;&gt;    println!&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;] - &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;make_model&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;options&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; media&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; client&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;    .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;get_printer_details&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;printer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-call z-rust&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;printer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-call z-rust&quot;&gt;backend&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;    .&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;CpdbClient::new()&lt;&#x2F;code&gt; scans D-Bus for services matching the CPDB backend naming convention, connects to each, and holds a proxy per backend. A system may run more than one backend, so every method that identifies a printer takes both a printer ID and a backend name, because IDs are only unique within a backend.&lt;&#x2F;p&gt;
&lt;p&gt;The API covers:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Capability&lt;&#x2F;th&gt;&lt;th&gt;Method&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Discover printers across all backends&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;get_all_printers()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Discover, minus remote&#x2F;temporary printers&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;get_filtered_printers()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Live add&#x2F;remove&#x2F;state-change events&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;discovery_stream()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Options, media sizes and margins&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;get_printer_details()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Localised option and value names&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;get_translations()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;The system default printer&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;get_default_printer()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Submit a job, receive a writable FD&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;print_fd()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Submit a job over a socket&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;print_socket()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Keep backends alive during a dialog session&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;keep_alive_all()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Toggle remote&#x2F;temporary printer visibility&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;show_remote_printers()&lt;&#x2F;code&gt;, &lt;code&gt;show_temporary_printers()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;the-event-stream&quot;&gt;The event stream&lt;&#x2F;h3&gt;
&lt;p&gt;This is the most interesting part. Every backend emits &lt;code&gt;PrinterAdded&lt;&#x2F;code&gt;, &lt;code&gt;PrinterRemoved&lt;&#x2F;code&gt; and &lt;code&gt;PrinterStateChanged&lt;&#x2F;code&gt; D-Bus signals whenever a new printer is added, goes offline, or is enabled&#x2F;disabled to receive print jobs. The client subscribes to all of them across all backends, maps each signal into a typed enum, and merges them into a single stream with &lt;code&gt;futures_util::stream::SelectAll&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-storage z-type z-rust&quot;&gt;pub enum&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; DiscoveryEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    PrinterAdded&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;PrinterSnapshot&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;    PrinterRemoved&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; backend&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;    PrinterStateChanged&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; backend&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; PrinterState&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Keeping this shape allows us an easy integration with the GUI. On the COSMIC side, subscribing to the event stream looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;run_with&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;PrinterDiscovery&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;_&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;    cosmic&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;iced&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;stream&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;100&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; output&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; async move&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; client&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; CpdbClient&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust z-storage z-modifier&quot;&gt;        let mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; stream&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; client&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;discovery_stream&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-storage z-type z-rust&quot;&gt;        while let&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Some&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; stream&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;next&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; output&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;send&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;DiscoveryEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    })&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A printer that comes online while the dialog is open appears in the list. One that goes offline grays out and, if it was selected, the dialog falls back gracefully. No polling, no main loop, no &lt;code&gt;unsafe&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;other-things-that-went-into-the-crate&quot;&gt;Other things that went into the crate&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Typed states instead of string as data.&lt;&#x2F;strong&gt; Printer state arrives as a string over D-Bus; the crate parses it into a &lt;code&gt;PrinterState&lt;&#x2F;code&gt; enum with an &lt;code&gt;is_ready()&lt;&#x2F;code&gt; helper.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A structured config type&lt;&#x2F;strong&gt;, &lt;code&gt;PrinterConfig&lt;&#x2F;code&gt;, for global and per-printer settings.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Translations.&lt;&#x2F;strong&gt; CPDB backends can return localized names for options and their values.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Documentation and examples.&lt;&#x2F;strong&gt; Documentation for the whole crate and runnable examples (&lt;code&gt;discover_printers&lt;&#x2F;code&gt;, &lt;code&gt;filter_printers&lt;&#x2F;code&gt;, &lt;code&gt;get_translations&lt;&#x2F;code&gt;, &lt;code&gt;print_a_document&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The work landed as &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenPrinting&#x2F;cpdb-rs&#x2F;pull&#x2F;6&quot;&gt;PR #6 on &lt;code&gt;OpenPrinting&#x2F;cpdb-rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and is published on crates.io as &lt;strong&gt;&lt;code&gt;cpdb-rs&lt;&#x2F;code&gt; 0.1.1&lt;&#x2F;strong&gt; and &lt;strong&gt;&lt;code&gt;cpdb-sys&lt;&#x2F;code&gt; 0.1.1&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;part-two-the-cosmic-print-dialog-and-portal&quot;&gt;Part two: the COSMIC print dialog and portal&lt;&#x2F;h2&gt;
&lt;p&gt;With the library ready to be used, the second half of the summer went into &lt;code&gt;xdg-desktop-portal-cosmic&lt;&#x2F;code&gt; itself: roughly &lt;strong&gt;4,200~&lt;&#x2F;strong&gt; lines of code (at the time of writing this).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-portal-contract&quot;&gt;The portal contract&lt;&#x2F;h3&gt;
&lt;p&gt;The print portal has two methods, and the split between them is not obvious at first:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PreparePrint&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: the application says requests to print a document and passes some preferred settings. The portal shows the dialog, the user picks a printer and settings, and the portal returns the chosen settings plus a token. &lt;strong&gt;No document is transferred in this step.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Print&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: the application passes an FD holding the document. If it includes a token from a previous &lt;code&gt;PreparePrint&lt;&#x2F;code&gt; call, the portal must print &lt;strong&gt;without showing any UI&lt;&#x2F;strong&gt;, reusing the settings associated with the token (tokens are one time use). Without a token, the portal shows the dialog and then prints.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;settings-translation&quot;&gt;Settings translation&lt;&#x2F;h3&gt;
&lt;p&gt;There is a problem that I faced when mapping CPDB option values and XDG option values, &lt;strong&gt;the XDG print portal and CPDB do not agree on names.&lt;&#x2F;strong&gt; The portal speaks its own language and CPDB speaks IPP’s, some names are consistent but not all. One such example: Duplex is &lt;code&gt;&quot;horizontal&quot;&lt;&#x2F;code&gt; to the portal and &lt;code&gt;&quot;two-sided-long-edge&quot;&lt;&#x2F;code&gt; to CPDB. Similarly, Color is &lt;code&gt;use-color: &quot;true&quot;&lt;&#x2F;code&gt; on one side and a &lt;code&gt;color-mode&lt;&#x2F;code&gt; option on the other. And There are many such cases.&lt;&#x2F;p&gt;
&lt;p&gt;The solution I came up with is making &lt;strong&gt;the dialog’s own state the single source of truth&lt;&#x2F;strong&gt;, with two translation layers around it:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;apply_xdg_hints()&lt;&#x2F;code&gt;: takes the &lt;code&gt;settings&lt;&#x2F;code&gt; and &lt;code&gt;page-setup&lt;&#x2F;code&gt; vardicts the application sent and seeds the dialog’s state from them, so an app’s preferences are respected as the starting point.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;build_xdg_response()&lt;&#x2F;code&gt;: converts dialog state back into the keys the portal specification expects, for the response.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;build_cpdb_settings()&lt;&#x2F;code&gt;: converts the same dialog state into the option names the CPDB backends expect, for actual job submission.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Keeping these three functions separate, with the dialog state in the middle, means neither vocabulary leaks into the other, and adding a setting is a matter of touching one enum and three mapping arms.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;submitting-the-job&quot;&gt;Submitting the job&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;print_fd()&lt;&#x2F;code&gt; submits the job description to the backend and hands back a job ID and a &lt;strong&gt;writable file descriptor&lt;&#x2F;strong&gt;. The document then has to be streamed from the application’s FD into that one. A minor hiccup faced here: CPDB backends (CUPS in particular) populate internal lookup tables lazily, so a printer query must happen before submission or the job is rejected for an “unknown” printer that you just listed a second ago. The copy itself runs on &lt;code&gt;tokio::task::spawn_blocking&lt;&#x2F;code&gt;, because a large document should not stall the portal’s async executor:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;job_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; cpdb_writable_fd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    client&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;print_fd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;printer_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;backend&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;settings_ref&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;title&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;task&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;spawn_blocking&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;move&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; ||&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust z-storage z-modifier&quot;&gt;    let mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; reader&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; File&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;from&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;document_fd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust z-storage z-modifier&quot;&gt;    let mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; writer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; File&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;from&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;OwnedFd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;from&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt;cpdb_writable_fd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;    std&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;io&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;copy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; reader&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-rust&quot;&gt; writer&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;})&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;the-dialog-itself&quot;&gt;The dialog itself&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;src&#x2F;print_dialog.rs&lt;&#x2F;code&gt; is largest single piece of the project. It follows the standard &lt;code&gt;iced&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;libcosmic&lt;&#x2F;code&gt; architecture and covers:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Printer list with live updates, make&#x2F;model, and status.&lt;&#x2F;li&gt;
&lt;li&gt;Copies and collation.&lt;&#x2F;li&gt;
&lt;li&gt;Color mode, orientation, and duplex, each populated from what the selected printer reports.&lt;&#x2F;li&gt;
&lt;li&gt;Paper size and tray, queried from the media collection CPDB returns for that printer.&lt;&#x2F;li&gt;
&lt;li&gt;Margins: default, none, minimum, or custom, with the printer’s own hardware margins as bounds.&lt;&#x2F;li&gt;
&lt;li&gt;Print quality.&lt;&#x2F;li&gt;
&lt;li&gt;Page ranges: all pages, current page, odd or even only, or a custom range expression.&lt;&#x2F;li&gt;
&lt;li&gt;Scaling: auto-fit or a custom percentage.&lt;&#x2F;li&gt;
&lt;li&gt;Pages per sheet with a layout direction picker.&lt;&#x2F;li&gt;
&lt;li&gt;Print to file: saving the document as a PDF when the “Save as PDF” entry is selected (Save as PDF always appears as the first entry in the printer list).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Everything is driven off the selected printer’s queried capabilities.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;presets&quot;&gt;Presets&lt;&#x2F;h3&gt;
&lt;p&gt;To enable good user experience, A &lt;strong&gt;preset system&lt;&#x2F;strong&gt; was introduced in the design. I implemented it via &lt;code&gt;cosmic-config&lt;&#x2F;code&gt; in &lt;code&gt;cosmic-portal-config&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-storage z-type z-rust&quot;&gt;pub struct&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; PrintPreset&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; is_builtin&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; color_mode&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; orientation&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; duplex_index&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Option&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-brackets z-angle z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type z-numeric z-rust&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-brackets z-angle z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; copies&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type z-numeric z-rust&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; collate&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; pages_per_sheet_index&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Option&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-brackets z-angle z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type z-numeric z-rust&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-brackets z-angle z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; layout_direction&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; margins&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; scaling&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; custom_scaling_input&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type z-numeric z-rust&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; page_selection&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; custom_range_input&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Three presets are built in (Default, Color, and Black and White), and the user can save their own, edit them, and delete them.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;localization&quot;&gt;Localization&lt;&#x2F;h3&gt;
&lt;p&gt;Every user visible string in the dialog goes through &lt;code&gt;fl!&lt;&#x2F;code&gt;, Fluent-based localization. CPDB provides translations for the printer options, the provided translations are utilized in the dialog, well known IPP values were given fluent macro support as well. I added around &lt;strong&gt;110 new keys&lt;&#x2F;strong&gt; to the translation catalog. Once the PR is merged translations will be accepted as contributions via Weblate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;current-state&quot;&gt;Current state&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;cpdb-rs&lt;&#x2F;code&gt;: complete and merged upstream.&lt;&#x2F;strong&gt; The zbus backend is merged upstream into &lt;code&gt;OpenPrinting&#x2F;cpdb-rs&lt;&#x2F;code&gt;, is the crate’s default feature, and is published on crates.io at 0.1.1. The C bindings were preserved behind the &lt;code&gt;ffi&lt;&#x2F;code&gt; feature in &lt;code&gt;cpdb-sys&lt;&#x2F;code&gt; for anyone who needs them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The COSMIC print dialog: feature-complete, in review.&lt;&#x2F;strong&gt; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pop-os&#x2F;xdg-desktop-portal-cosmic&#x2F;pull&#x2F;331&quot;&gt;PR #331&lt;&#x2F;a&gt; implements the full &lt;code&gt;org.freedesktop.impl.portal.Print&lt;&#x2F;code&gt; interface. Every item on the PR’s TODO list is checked off: the token system, the &lt;code&gt;Print&lt;&#x2F;code&gt; execution path, Save as PDF, option presets, localization, and alignment with the design the COSMIC design team specified. It is currently in the review cycle with the COSMIC team and is not yet merged.&lt;&#x2F;p&gt;
&lt;p&gt;It works end to end today. With CUPS and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenPrinting&#x2F;cpdb-backend-cups&#x2F;&quot;&gt;&lt;code&gt;cpdb-backend-cups&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; installed, you can run the portal and either fire a request by hand:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; gdbus call --session --timeout&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 9999&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  --dest org.freedesktop.impl.portal.desktop.cosmic&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  --object-path &#x2F;org&#x2F;freedesktop&#x2F;portal&#x2F;desktop&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  --method org.freedesktop.impl.portal.Print.PreparePrint&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;&#x2F;org&#x2F;freedesktop&#x2F;portal&#x2F;desktop&#x2F;request&#x2F;1&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;com.test.App&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;  &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;Test Print Document&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;{&amp;#39;gtk-print-backgrounds&amp;#39;: &amp;lt;true&amp;gt;, &amp;#39;gtk-print-header-footer&amp;#39;: &amp;lt;false&amp;gt;}&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;@a{sv} {}&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;{&amp;#39;modal&amp;#39;: &amp;lt;true&amp;gt;}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or, just press &lt;kbd&gt;Ctrl&lt;&#x2F;kbd&gt;+&lt;kbd&gt;P&lt;&#x2F;kbd&gt; in a Flatpak application. It has been tested working with Evince and gedit.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-left-to-do&quot;&gt;What’s left to do&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Review feedback on PR #331.&lt;&#x2F;strong&gt; The review is active and I will be iterating on it. This is expected for a change of this size touching a shared component.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Broader real-world printer testing.&lt;&#x2F;strong&gt; The dialog has been exercised against the printers and backends available to me. Printers vary in what options they expose and how, and more hardware coverage may surface edge cases in the capability-driven UI.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The portal API itself.&lt;&#x2F;strong&gt; While building this, a set of limitations in the current XDG print portal API became clear, and they cap how good &lt;strong&gt;any&lt;&#x2F;strong&gt; implementation of this dialog can be:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No live preview:&lt;&#x2F;strong&gt; The portal transfers the document only at the &lt;code&gt;Print&lt;&#x2F;code&gt; step, after all settings are chosen. The dialog therefore cannot show you what the page will look like as you change margins, scaling or pages-per-sheet, a feature users reasonably expect from a print dialog today.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Applications cannot expose their own settings:&lt;&#x2F;strong&gt; A word processor wanting to offer “print comments” or a browser wanting “print backgrounds” has no well defined way to follow what to declare.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Vocabulary mismatch.&lt;&#x2F;strong&gt; The settings dictionaries are informally do not match between CPDB and XDG.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Thanks to &lt;strong&gt;Till Kamppeter&lt;&#x2F;strong&gt; at OpenPrinting, there is an active effort to discuss these limitations and design a better print API at the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;flatpak&#x2F;xdg-desktop-portal&#x2F;discussions&#x2F;2016&quot;&gt;xdg-desktop-portal’s GitHub discussion&lt;&#x2F;a&gt;. The COSMIC dialog deliberately targets the existing portal API for this cycle so it can ship and be useful now. When the improved API is ready, I will update the dialog to adopt it. The architecture, dialog state as the source of truth with mapping functions at the boundaries, was chosen partly so that this migration touches the edges rather than the core.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;challenges-faced-during-the-program&quot;&gt;Challenges faced during the program&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Learning a desktop stack from the inside.&lt;&#x2F;strong&gt; Building a portal backend meant simultaneously understanding D-Bus service lifetimes, getting familiar with an unfamiliar GUI toolkit’s architecture, and a printing protocol. I did a lot of reading (some writing as well, I wrote a short blog on how iced works, you can read it at my blog or find the link at the bottom). What saved me was that &lt;code&gt;xdg-desktop-portal-cosmic&lt;&#x2F;code&gt; already had well-built implementations of the screenshot, screencast and file-chooser portals. Having working reference code for “what does a portal request look like in this codebase” turned a from-scratch design problem into a pattern-matching one, and it is a strong argument for the value of a codebase with more than one example of each thing.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The two-vocabulary problem.&lt;&#x2F;strong&gt; As described above, making sure the XDG portal and CPDB agree on the same option names was the fiddliest part of the project. Not intellectually hard, but full of small asymmetries where a value exists on one side but not the other. It also took me a while to accept that the right answer was three explicit mapping functions rather than one clever bidirectional abstraction.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The design.&lt;&#x2F;strong&gt; A printing dialog is a GUI at the end of the day, it needs a user interface to allow users to interact with it and a good one at that. Now I am not desginer, and thankfully I didn’t need to be one, I got ample support from the cosmic team for the design part of this project.&lt;&#x2F;p&gt;
&lt;p&gt;Two people made a real difference here. &lt;strong&gt;Michael&lt;&#x2F;strong&gt; from the COSMIC team was consistently available whenever I hit a technical wall in &lt;code&gt;libcosmic&lt;&#x2F;code&gt; or the portal internals. &lt;strong&gt;Maria&lt;&#x2F;strong&gt; from the COSMIC team gave full support on the design side. The dialog follows the COSMIC design, and any design question I had got answered.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;What I learned&lt;&#x2F;h2&gt;
&lt;p&gt;More than I expected going in:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FFI, and then how to avoid it.&lt;&#x2F;strong&gt; I had spent a couple weeks reasoning about &lt;code&gt;cpdb-libs&lt;&#x2F;code&gt;’ C API - bindgen, callback trampolines, ownership rules across the boundary. The most valuable thing that taught me was how to recognize when the FFI layer is not actually load-bearing. This was my first time working with FFI, especially the Rust and C boundary, but taking in account its practicality with the project I ended up using zbus and not this.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IPC over D-Bus.&lt;&#x2F;strong&gt; Spent a whole week just going over how D-Bus works, not how it was being used here but how it plainly works. The zbus tutorials were really helpful and then reading the existing implementations helped out the most in the end. Writing the zbus backend for &lt;code&gt;cpdb-rs&lt;&#x2F;code&gt; was probably the most fun thing I did this year.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Desktop GUI programming with &lt;code&gt;iced&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;libcosmic&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt; Enough that I wrote up a separate blog on how &lt;code&gt;iced&lt;&#x2F;code&gt; works, building a small live CPU-usage system monitor as the worked example: &lt;strong&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;iced&quot;&gt;Getting started with iced&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;How printing actually works.&lt;&#x2F;strong&gt; IPP attributes, CUPS’ role, what “media” and “margins” mean when a printer reports them, and why a print dialog is much harder than the list of widgets on it suggests.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Shipping a crate.&lt;&#x2F;strong&gt; &lt;code&gt;cpdb-rs&lt;&#x2F;code&gt; is the first crate I have had a major part in shipping to crates.io that will have real users.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;thanks&quot;&gt;Thanks&lt;&#x2F;h2&gt;
&lt;p&gt;To &lt;strong&gt;Till Kamppeter&lt;&#x2F;strong&gt; and OpenPrinting for mentorship, for the CPDB project itself, and for taking the portal API limitations seriously enough to start a conversation about fixing them upstream. To &lt;strong&gt;Michael&lt;&#x2F;strong&gt; and &lt;strong&gt;Maria&lt;&#x2F;strong&gt; from the COSMIC team for technical and design support throughout. And to Google Summer of Code for making a summer of this kind of work possible.&lt;&#x2F;p&gt;
&lt;p&gt;The COSMIC desktop will have a native print dialog, and Rust applications on Linux now have a clean, async, dependency-free way to talk to the printing stack.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cpdb-rs&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenPrinting&#x2F;cpdb-rs&quot;&gt;GitHub&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;cpdb-rs&quot;&gt;crates.io&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;cpdb-rs&quot;&gt;docs.rs&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenPrinting&#x2F;cpdb-rs&#x2F;pull&#x2F;6&quot;&gt;PR #6&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;COSMIC print dialog&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pop-os&#x2F;xdg-desktop-portal-cosmic&#x2F;pull&#x2F;331&quot;&gt;PR #331&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;CPDB&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenPrinting&#x2F;cpdb-libs&quot;&gt;cpdb-libs&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenPrinting&#x2F;cpdb-backend-cups&#x2F;&quot;&gt;cpdb-backend-cups&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;XDG Print portal spec&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;flatpak.github.io&#x2F;xdg-desktop-portal&#x2F;docs&#x2F;doc-org.freedesktop.portal.Print.html&quot;&gt;flatpak.github.io&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Discussion on Improvements to printing portal&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;flatpak&#x2F;xdg-desktop-portal&#x2F;discussions&#x2F;2016&quot;&gt;github.com&#x2F;flatpak&#x2F;xdg-desktop-portal&#x2F;discussions&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;My blog on iced&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ayuch.dev&#x2F;blog&#x2F;iced&quot;&gt;ayuch.dev&#x2F;blog&#x2F;iced&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;…&lt;&#x2F;p&gt;
</content>
        <summary type="html">My GSoC 2026 final submission report for project: COSMIC Desktop Print Dialog.</summary>
        </entry>
</feed>
