clipd — clipboard service
mix --serve <prefix>/lib/clipd/clipd.mix --name clipd runs a clipboard Bus
citizen. There is no clipd Rust crate, GUI, Wayland dependency in Mix, or new
transport. Store mode holds data in memory. Wayland mode uses the session's
external wl-paste and wl-copy executables.
The graphical implementation supports materialised text and blob ownership.
wl-copy cannot advertise a remote lazy offer or run a fetch callback at paste time.
Remote password offers and uncached remote types are refused in Wayland mode
with LAZY_WAYLAND_UNAVAILABLE. They work lazily through the Bus in store mode.
Local Wayland password sets are also refused because wl-copy spools stdin to a
temporary file. Observing an existing password offer does not read its content.
Commands
Requests are JSON objects. selection is clipboard (default) or primary.
The Rust prototype's primary:true|false alias remains accepted; conflicting
aliases are refused. Errors are rc 10 with {error_code,message}. Error messages
and service logs never echo request contents.
| Command | Request and reply |
|---|---|
clipboard.get | {selection?,mime?,offset?,node?} → {mime,data,encoding,size,offset,next_offset,eof,sensitive,stamp} for text; a blob reference for binary/large data |
clipboard.set | {selection?,mime?,data,encoding?,sensitive?,stamp?} or {selection?,mime,blob} → {applied,stamp?} |
clipboard.clear | {selection?} → {applied:true} |
clipboard.info | Both selection metadata, node/session, history count, limits, peers and mode |
clipboard.history | Up to fifty text previews, newest first: {id,mime,data,size,truncated} |
clipboard.pick | {index,expected_id?,selection?}; n remains an alias for index |
clipboard.push | {to,selection?} pushes only to a configured peer key |
clipboard.peers | Configured peer map |
desktop.clipboard.rotate | Restore a history entry |
desktop.clipboard.menu | PICKER_UNAVAILABLE; use history and indexed pick |
clipboard.receive and clipboard.fetch are member-only peer commands.
clipboard.observe is a private local watcher callback with a per-process
token. clipboard.expire and clipboard.follow are local continuations and
refuse Bus dispatch, as do clipboard.cache and clipboard.unpin, which own
materialised blob references and release obsolete pins.
clipboard.changed contains metadata only:
{node,session,selection,primary,generation,origin_node,origin_session,mime_types, size,sensitive}. A stamp is {origin_node,origin_session,generation}. UUIDs
survive process restarts without reusing generations. The last 256 stamps and
the current selection fence echoes, independently for both selections.
Small text retains the prototype's UTF-8/base64 reply shape, including byte
offsets and eof. Binary/large content uses blobd's existing reference and
transfer lane: get returns data:"",encoding:"base64",eof:false,blob:{...}.
Call blob.fetch and blob.path rather than advancing next_offset on a blob
reply. Set binary data through blob.put followed by clipboard.set {blob};
inline non-text bytes are refused. This is a deliberate difference from the
unmerged Rust prototype's binary Bus chunks, required by the blob-only rule.
Wayland blob sets/imports whose bytes are not present return
{applied:false,pending:true,stamp}. The existing selection stays usable while
blobd fetches. Native blob.fetched/blob.pinned events resume ownership after
verified bytes arrive. clipboard.info.pending reports each selection's wait;
a later set, clear or external copy cancels the older pending offer.
Mix's Bus-only builtins are clip_get([selection,mime]) and
clip_set(text[,selection,mime]). A sole MIME argument remains accepted.
clip_get raises for a blob reference. The MCP tools are clipboard_get and
clipboard_set, with optional selection and the legacy primary alias.
Configuration and membership
MIXOS_CLIPD_CONFIG selects a schema-1 strict-data file; the default is
<etc>/clipboard.conf.mix. Peer keys are exact node names. Nothing is sent
outside this configured group. A graphical member has follow:true; server
entries default to explicit push/pull. PRIMARY follow requires
follow_primary:true at the source. Images are enabled for follow members and
require images:true for servers.
{
schema: 1,
eager_text_bytes: 1048576,
history_cap: 50,
password_timeout: 45,
follow_primary: false,
peers: {
beta: {target:"clipd.beta.example.bus", follow:true},
archive: {target:"clipd.archive.example.bus", images:false}
}
}
Configure reciprocal peers. Content APIs admit broker-stamped local callers
and configured mesh peers. Membership comes from the broker's broker_peer
stamp, never a body field or a client-supplied node claim. A bare service target
also admits that broker-stamped local service, for co-located citizens and tests.
Use native noded admission and the WireGuard mesh. Missing broker peer stamps
fail closed. No scope keys or parallel encryption protocol are introduced.
Follow forwards local changes once; imported changes are not re-exported. Password offers contain metadata only, retain their source and deadline, never enter history or blobd, are fetched on demand, and clear after 45 seconds by default. Store servers do not cache fetched secret bytes. Generation fences refuse stale lazy reads. Replaced or missing sources fail cleanly.
Session integration and external programs
MIXOS_CLIPD_MODE=auto|store|wayland defaults to auto. No display selects store;
a display selects Wayland and requires both external tools. Store mode makes
no wl-clipboard calls. MIXOS_CLIPD_SESSION identifies the session. The node
identity comes from the native node config. Optional config seat selects the
Wayland seat through the tools' --seat option.
The desktop role requires wl-paste and wl-copy from wl-clipboard in PATH;
the server role needs neither. Both need Mix and noded; binary content also
needs blobd. wl-clipboard is an external system package and is not bundled,
compiled or linked into MixOS binaries.
Two generation-owned spawn(argv,{exit_event:true}) children run
wl-paste [--primary] --watch <prefix>/bin/mix watch.mix <selection>. The callback
does not read stdin: it sends a local Bus notification, then closes the pipe.
The citizen lists types and samples ordinary text only up to the eager limit
(plus one byte to detect a larger offer); larger text stays lazy until read.
The KDE password hint
is treated conservatively as sensitive whenever present. On-demand reads use
wl-paste --type MIME --no-newline, with a five-second deadline and 64 MiB cap;
a Mix pipeline helper preserves raw bytes without a temporary read file.
Ordinary sets pass stdin to wl-copy [--primary] --type MIME, which forks and
owns the selection. Clears use wl-copy [--primary] --clear. Own eager-text
echoes are recognised by exact bytes, because wl-copy cannot add an arbitrary
origin marker MIME. Large/binary ownership and multi-MIME preservation need
further acceptance: the tool can supply one content payload plus text aliases.
The constraint comes from wl-copy's input and ownership path.
clipd@.service reads the graphical session environment; clipd.service is
the server store unit. The component installer and release packages ship both
units and scripts without enabling them. No desk is restarted by installation
of this source change. inputd migrates only the two old clipboard menu/rotate
targets from desktop-vt1 to clipd, preserving custom targets and unrelated
bindings. This source change does not rewrite a live keymap.
No generic Bus dialog/choice service exists in the current tree. Toolkit dialogs are window-local, and files' chooser is specific to files. A clipboard picker UI remains a follow-up; the service adds no GUI dependency.
Validation
mix services/clipd/tests/clipboard_test.mix checks policy, history, stamps,
byte offsets and broker membership. mix services/clipd/tests/clipd_test.mix
runs two store citizens against an owned private noded and checks the public API,
follow, lazy password fetch, no history, expiry and spoof refusal. It also
checks the runtime command list against the registry and uses a real private
blobd for binary references and reads outside the Bus.
mix services/clipd/tests/wayland_test.mix starts a private headless compd and
checks both selections with the real wl-clipboard tools, own-set echo handling,
password history exclusion and expiry. Set MIXOS_TEST_COMPD to a CBC-built
binary. The test does not connect to a live desktop.
The standard Mix gate includes the policy and private-broker service suites and
uses MIXOS_TEST_MIX and MIXOS_TEST_BLOBD to select freshly built binaries.
The private Wayland test also accepts those variables. Live cross-application
and cross-node acceptance is separate from these private tests. Graphical
password and uncached remote offers remain refused until the lazy ownership
policy is settled.