cpdb-rs
Safe, native Rust async bindings for the Common Print Dialog Backends (CPDB) D-Bus interface.
Overview
cpdb-rs lets Rust applications communicate with CPDB print backends (like cpdb-backend-cups) directly over D-Bus without requiring any C dependencies.
The library uses zbus and tokio to provide a fully asynchronous, memory-safe, and pure-Rust implementation of the CPDB client protocol.
Features
- Pure Rust D-Bus Client: No
libcpdb-devor C compiler needed! Everything runs natively over D-Bus usingzbus. - Async First: All methods are
asyncand powered by Tokio. - Live Discovery: Subscribe to a native Rust
Streamfor real-time printer additions, removals, and state changes. - Activation Retry: Gracefully retries printer discovery to handle D-Bus activation race conditions.
- Keep-Alive Management: Automatically pings backends to keep them active in the background.
- (Optional) Legacy C-FFI bindings available via the
ffifeature flag.
Supported platforms
The default (zbus-backend) install is pure Rust and does not link
cpdb-libs. Requirements are minimal and Unix-only.
| Target | zbus-backend (default) |
ffi (opt-in) |
|---|---|---|
| Linux (glibc) | ✅ Fully supported. CI runs on Ubuntu. | ✅ Fully supported. |
| macOS | ✅ Compiles. Runtime requires a session bus and a CPDB backend. | ⚠️ Headers-only. Bindgen parses the headers and the crate compiles with CPDB_NO_LINK=1, but linking requires Linux D-Bus — useful only for compile-checks. |
| Windows | ❌ Not supported. Develop inside WSL Ubuntu — the repository on /mnt/c/… is reachable from WSL without copying. |
❌ Not supported. Use WSL. |
At runtime, whichever backend you pick, your system must have at least
one CPDB backend service installed (e.g. cpdb-backend-cups) for
printer discovery to return anything.
Prerequisites (default zbus-backend)
- Rust 1.85+ (2024 edition).
- A running D-Bus session bus.
- At least one CPDB backend service reachable on that bus for anything useful to happen at runtime.
That's it — no C compiler, no libcpdb-dev, no bindgen. Skip straight
to Installation.
Prerequisites (for the ffi feature)
Everything below is only needed if you opt in with
--features ffi (or default-features = false, features = ["ffi"]).
cpdb-libs (≥ 3.0)
The ffi feature targets the cpdb-libs 3.x ABI.
⚠️ Distro packages may be too old. As of mid-2026, Debian / Ubuntu ship cpdb-libs 2.0~b5 in
libcpdb-dev. That is incompatible with this crate — installing it leaves you with bothlibcpdb.so.2(from apt) andlibcpdb.so.3(from source) and the linker picks the wrong one. Either:
- Build from source (recommended until distros catch up), or
- Verify your package gives
libcpdb.so.3.*withls /usr/lib*/libcpdb.so.*before relying on it.
Build cpdb-libs 3.x from source:
# If you previously installed apt's older libcpdb*, remove it first:
||
Fedora / RHEL: install cpdb-libs-devel from a 3.x-shipping repository,
or build from source the same way.
libclang
bindgen needs libclang at build time. On Debian/Ubuntu:
Installation
Default install — pure Rust, no C dependencies:
[]
= "0.1.0"
= { = "1.0", = ["full"] }
Legacy synchronous C-FFI wrappers (requires cpdb-libs 3.x and
libclang at build time — see Prerequisites (for the ffi
feature)):
[]
= { = "0.1.0", = false, = ["ffi"] }
Enabling both features at once is supported: the default zbus-backend
API and the FFI wrappers coexist.
Quick start
use CpdbClient;
async
Examples
Fetching Printer Options and Media Sizes
use CpdbClient;
async
Live Discovery Event Stream
Watch for new printers appearing and disappearing in real-time.
use ;
use StreamExt;
async
You can run any of the shipped examples against your live D-Bus session:
FFI-flavoured examples are gated on the ffi feature:
Architecture (zbus Backend)
Instead of linking against libcpdb.so and using bindgen (which required unsafe C memory management, callbacks, and manual lifetime tracking), cpdb-rs now uses zbus to speak the D-Bus protocol directly to the print backends. This provides massive benefits:
- 100% Safe Rust: No raw pointers, no manual memory management, no undefined behavior.
- Zero C Dependencies: You don't need
libcpdb-devto compile. - Async Tokio Integration:
zbusintegrates perfectly with Tokio, allowing you to await D-Bus calls and use RustStreams for live discovery events. - Activation Retries: Automatically retries initial calls to handle
UnknownMethodrace conditions when systemd auto-activates D-Bus backends.
Legacy Architecture (C-FFI)
[!WARNING]
The C-FFI interface is behind theffifeature flag and has been moved to the underlyingcpdb-syscrate. The default feature is nowzbus-backend. To continue usingcpdb_rs::Frontend, update yourCargo.tomlto:cpdb-rs = { default-features = false, features = ["ffi"] }.
If you have legacy code that still requires the synchronous C-FFI wrappers around cpdb-libs, they are still available by enabling the ffi feature flag in your Cargo.toml. See the ffi module documentation for details.
┌───────────────────────────────────────────┐
│ cpdb_rs::Frontend │
│ (D-Bus connection, backend list, hash │
│ table of discovered printers) │
└─────┬─────────────────────────────────┬───┘
│ borrowed │ owned
│ (lifetime tied to &Frontend) │ (Drop frees)
▼ ▼
┌────────────────────┐ ┌──────────────────────┐
│ Printer<'frontend> │ │ Printer<'static> │
│ from get_printer / │ │ from load_from_file │
│ find_printer / ... │ └──────────────────────┘
└────────┬───────────┘
│
├── per-printer settings (add_setting / clear_setting)
├── option lookups (get_default / get_current / get_option)
├── translations ─ TranslationMap (owned snapshot)
├── media ─ MediaSize, Margins
└── job submission (print_file / submit_job / print_fd / print_socket)
┌──────────────────────────┐
│ cpdb_rs::Settings │
│ (free-standing serial- │
│ isable settings object) │
└──────────────────────────┘
▲
│ persisted via save_to_disk / read_from_disk
▼
~/.config/cpdb/ (cpdb-libs-managed location)
Two add_setting methods, two scopes (C-FFI)
| Method | Scope | Persists across runs? |
|---|---|---|
Printer::add_setting |
This printer only, in-memory on the printer object | Only if you re-add on each run |
Settings::add_setting |
Free-standing settings collection | Yes, via Settings::save_to_disk() |
Printer::add_setting is the per-job knob: tweak copies, sides, etc.
before calling print_file / submit_job. Settings is the global,
serialisable view that cpdb-libs reads back from disk on startup.
Module map
| Module | What lives here |
|---|---|
cpdb_rs::client |
(zbus) CpdbClient — Main async D-Bus client & discovery logic |
cpdb_rs::events |
(zbus) DiscoveryEvent, PrinterSnapshot for async streams |
cpdb_rs::media |
(zbus) MediaCollection, MediaInfo, MarginInfo |
cpdb_rs::config |
(zbus) PrinterConfig for job submission configuration |
cpdb_rs::options |
OptionInfo, OptionsCollection (shared across both implementations) |
cpdb_rs::error |
CpdbError and the crate-wide Result alias |
cpdb_rs::proxy |
(zbus) Auto-generated zbus proxy trait PrintBackend |
cpdb_rs::frontend |
(ffi) Frontend — D-Bus lifecycle, discovery, default printer |
cpdb_rs::printer |
(ffi) Printer, Margin/Margins, MediaSize, TranslationMap |
cpdb_rs::settings |
(ffi) Settings, Options, Media |
cpdb_rs::callbacks |
(ffi) Closure trampolines + PrinterUpdate enum |
cpdb_rs::common |
(ffi) init, version, path/config helpers |
cpdb_rs::util |
(ffi) Internal CStr helpers + the COptions C-array builder |
cpdb_rs::ffi |
(ffi) Raw bindgen output; everything unsafe |
Ownership model (C-FFI)
Printer carries a lifetime tied to the Frontend it came from. Borrowed
printers (those returned by get_printers, get_printer, find_printer,
get_default_printer, ...) cannot outlive their frontend — the compiler
checks this for you. Owned printers (Printer::load_from_file) have a
'static lifetime and are freed when dropped.
Printer is intentionally not Send or Sync. cpdb-libs does not
lock internally; if you need cross-thread access, wrap the printer in a
Mutex (or, more typically, run your printer operations on a single
thread).
Frontend is Send but not Sync — for the same reason.
Error handling
CpdbError is #[non_exhaustive], so always include a wildcard arm:
use CpdbError;
match some_op
Building the ffi feature on macOS
The ffi feature can be compiled on macOS but not linked without a
Linux D-Bus environment. Use CPDB_NO_LINK=1 to skip link directives:
CPDB_NO_LINK=1
The default zbus-backend build has no such caveat on macOS.
Testing
# Unit tests — no live D-Bus required
# Integration tests — require a running session bus and CPDB backends
Troubleshooting
Default (zbus-backend)
-
D-Bus connection failed/zbus: Address::from_env failed— Confirm a session bus is running (echo $DBUS_SESSION_BUS_ADDRESS) and that CPDB backend services (e.g.cpdb-backend-cups) are installed. In headless environments spin up an ephemeral bus withdbus-launch --exit-with-session <command>. -
No printers found— Verify printers are configured in the underlying spooler (CUPS, IPP, …) and the corresponding CPDB backend service is reachable over the session bus. A backend can take a moment to auto-activate;CpdbClientretries a few times, but a fully idle bus will still return an empty list. -
Windows is not supported — no CPDB backend daemon exists on Windows. Develop inside WSL Ubuntu; the repository on
/mnt/c/…is reachable from WSL without copying.
ffi feature only
-
undefined symbol: cpdbGetVersion/libcpdb.so.3, may conflict with libcpdb.so.2— You have apt's older cpdb-libs 2.x installed alongside the source-built 3.x. The linker is picking the v2 library. Fix:&& && && -
fatal error: 'cpdb/cpdb.h' file not found— Headers are missing from/usr/include/cpdb/. Reinstall cpdb-libs from source (see Prerequisites (for theffifeature)). -
Unable to find libclang(bindgen) — Installlibclang-devandclang(Debian/Ubuntu) or the equivalent on your distro. -
cpdb-libs not found— Install cpdb-libs 3.x so pkg-config can locatecpdb.pc. Override the discovery path withCPDB_LIBS_PATH=<prefix>when working against an uninstalled checkout. -
Linker errors on a non-standard cpdb-libs prefix — Set
PKG_CONFIG_PATH=<prefix>/lib/pkgconfigso pkg-config can resolvecpdbandcpdb-frontendfrom your install. -
error: linker 'link.exe' not foundon Windows native — Theffifeature does not support Windows targets. Develop inside WSL Ubuntu.
Contributing
See CONTRIBUTING.md.
-
Fork and clone.
-
cargo build --workspace --all-targets— the default (zbus-backend) path needs no C dependencies. If you also want to touch theffipath, install cpdb-libs 3.x first (see Prerequisites (for theffifeature)) and add--features ffi. -
Make changes, add tests.
-
Ensure the following all pass:
-
Open a pull request.
License
MIT — see LICENSE.
Related projects
- cpdb-libs — the upstream C library.
cpdb-rsspeaks its D-Bus protocol directly on the default backend, and wraps its C API when built with--features ffi. - OpenPrinting
- CUPS
Support
Changelog
See CHANGELOG.md.