Skip to content
← Back to docs
← demain.technology

Rust SDK

PCP is implemented as a Rust workspace called portal-pcp. The workspace contains ten sub-crates, each with a single responsibility. All crates compile directly into the compositor process; there is no separate daemon, no IPC boundary between PCP Core and the rest of the system.

The crate structure enforces separation of concerns at the dependency level. Platform-specific code lives in pcp-platform. Accessibility adapters live in pcp-atspi2 and pcp-wine. Core types and traits are isolated in pcp-core, which has zero platform dependencies.

portal-pcp/
├── Cargo.toml
├── pcp-core/ # Core traits, types, errors (no platform deps)
├── pcp-registry/ # Capability registry
├── pcp-atspi2/ # AT-SPI2 adapter (Tier 2)
├── pcp-native/ # Tier 1 server interface
├── pcp-events/ # Event system (poll-based)
├── pcp-push/ # Push event bus, coalescer, journal
├── pcp-stream/ # Streaming protocol
├── pcp-platform/ # Hardware API implementations
├── pcp-wine/ # Wine/MSAA bridge (Tier 2b)
├── pcp-simulator/ # Input simulation fallback (Tier 3)
┌─────────────┐
│ pcp-core │
└──────┬──────┘
┌───────────────┼───────────────┐
┌────▼────┐ ┌─────▼──────┐ ┌────▼──────┐
│pcp-registry│ │ pcp-events│ │ pcp-push │
└────┬────┘ └─────┬──────┘ └─────┬─────┘
┌────▼────┐ │ │
│pcp-atspi2│ │ │
└────┬────┘ │ │
┌────▼────┐ │ │
│ pcp-wine │ │ │
└─────────┘ │ │
│ │
┌──────▼───────────────▼──┐
│ pcp-core │
└──────────────────────────┘
┌─────────────────────┼──────────────────┐
┌────▼──────┐ ┌─────▼──────┐ ┌───────▼───────┐
│pcp-stream │ │pcp-platform│ │pcp-simulator │
└───────────┘ └────────────┘ └───────────────┘

pcp-core sits at the bottom. Every other crate depends on it for shared types, but nothing else is universal. The graph is deliberately shallow: most crates are one or two hops from core.

pcp-push depends only on pcp-core and tokio. It does not depend on pcp-events. The push bus is a parallel delivery path, not a replacement for the existing event system. Consumers opt into push by calling subscribe(); polling continues to work for those that have not migrated.

The foundation. Defines every shared type, trait, and error variant used across the workspace. Has zero platform dependencies; it compiles on any target that supports Rust.

Key exports: PcpAdapter trait, CapabilityId, ElementId, SurfaceId, AppId, Domain, AuthLevel, error types, and all JSON-RPC message structs.

Dependencies: serde, serde_json, chrono, thiserror.

The compositor’s live database of all capabilities from all running apps. Handles registration, deregistration, queries, and capability diffs. Listens to adapter lifecycle events and updates the registry in real time.

Dependencies: pcp-core.

The AT-SPI2 accessibility adapter for Tier 2 apps. Connects to the D-Bus accessibility bus, builds the element tree, maps AT-SPI2 roles to PCP capability IDs, and emits events when the tree changes.

Dependencies: pcp-core, zbus, atspi.

The Tier 1 server interface. Handles Ed25519 manifest verification, provides the typed capability interface that native apps expose, and manages the PCP side of the native server protocol. Also owns manifest signing key management.

Dependencies: pcp-core, ed25519-zebra.

The event system. Defines the event taxonomy (app lifecycle, capability change, system state, compositor, permission events), manages event buses, and implements the smart coalescing engine that reduces redundant event delivery.

Dependencies: pcp-core, tokio.

The push event delivery system. Provides EventBus, EventSubscription, PcpPushEvent, and the PushAdapter trait. Includes the coalescer, event journal for crash recovery, overflow strategies, and subscription audit trail integration.

Dependencies: pcp-core, tokio, serde, chrono, crc32fast, dashmap.

The streaming protocol. Handles continuous data channels for frame captures, audio streams, and other high-bandwidth, low-latency data paths that do not fit the request/response model.

Dependencies: pcp-core.

Hardware API implementations. Wraps Spaceboard haptics, display glasses, compositor window management, clipboard, and other platform-specific operations behind unified PCP traits.

Dependencies: pcp-core.

The Wine/MSAA accessibility bridge for Tier 2b apps. Translates MSAA and UI Automation trees from Windows applications running under Wine into the PCP element model.

Dependencies: pcp-core.

Input simulation for Tier 3 fallback. When no accessibility API is available and the app is not native, pcp-simulator injects keyboard and pointer events through the compositor’s input subsystem.

Dependencies: pcp-core.

The PushAdapter trait, defined in pcp-push/src/adapter.rs, lets any adapter declare whether it supports push delivery natively or needs a transparent polling wrapper.

pub trait PushAdapter: Send + Sync {
/// Returns true if this adapter can push events natively
/// (e.g., the compositor adapter pushes via Wayland events).
fn push_mode(&self) -> PushMode;
/// Register a subscriber for push events from this adapter.
/// Only called when push_mode() returns PushMode::Native.
fn subscribe(
&self,
domains: Vec<EventDomain>,
channel: tokio::sync::mpsc::Sender<PcpPushEvent>,
) -> Result<SubscriptionId, PushError>;
}

Adapters that return PushMode::Native (the compositor adapter) push events directly into the subscriber’s channel. Adapters that return PushMode::Polling (AT-SPI2, D-Bus system bus) are automatically wrapped in PollingPushAdapter, which polls internally and fans out to subscribers. The consumer sees the same Receiver<PcpPushEvent> either way.

This means migrating from polling to push is transparent for consumers. An adapter upgrade from polling to native push does not require changes downstream.

All crates in the workspace are compiled into the compositor binary. System Intelligence calls PCP through direct Rust trait methods with zero-copy Arc<T> sharing. There is no socket, no serialization, and no IPC between the intelligence subsystem and PCP Core. They share the same address space within the compositor process.

When adding a new crate to the workspace, the dependency graph must remain shallow. New crates should depend on pcp-core for types and at most one other crate for domain-specific logic. Circular dependencies are forbidden by the workspace’s Cargo.toml configuration.

Last updated: