Patin is a native Rust toolkit for building Wayland graphical shells. It provides the platform, rendering, layout, input, and damage foundations from which a consumer can compose bars, overlays, launchers, lock screens, and other shell surfaces.
Status: Patin is a library. The visible demo bar is an example/test consumer and is not instantiated by the toolkit.
patin-lockis a separate, explicitly launched lock-screen composition.
Patin clients can run above 0xin or another compatible layer-shell compositor. Patin is focused on graphical-shell needs; it is not intended to become a general-purpose application GUI framework.
- Consumers own shell behavior, composition, components, and service choices.
- Patin owns reusable Wayland, layout, rendering, input, scale, and damage mechanisms.
smithay-client-toolkitwill provide the Wayland client foundation.- CPU rendering with
wl_shm,tiny-skia, andcosmic-textcomes first. calloopdrives events. Optional adapter crates usezbusto connect standard system services; the corepatincrate never depends onzbus.- The library never automatically constructs a bar, phone UI, battery reader, volume reader, or compositor-specific adapter.
- 0xin integration will use a replaceable IPC adapter. Patin must still start when that socket is unavailable.
- Qt, QML, GTK, Electron, and other large GUI frameworks are out of scope.
The toolkit uses smithay-client-toolkit 0.21.1 with Calloop, tiny-skia
0.12.0, and cosmic-text 0.19.0. Chrono is used by the demo only.
Patin is a Cargo workspace. The root package is the patin toolkit crate
itself; crates/ holds optional, opt-in support and composition crates:
patin-icons— reusable vector icons expressed as Patin draw commands.patin-service-upower— battery state over D-Bus/UPower.patin-service-volume— audio volume/mute viawpctl/pactl.patin-service-brightness— display backlight via/sys/class/backlight.patin-service-network— connectivity state over D-Bus/NetworkManager.patin-service-workspaces— workspace focus/occupancy state over 0xin's control socket.patin-network-settings— independently launched Wi-Fi, mobile-data, and hotspot controls.patin-launcher— an independently launched, touch-friendly application list.patin-workspaces-bar— an independently launched, always-on strip of pills showing which workspace is focused and which have windows open; the reference consumer of 0xin'sworkspacescontrol-socket query.patin-lock— anext-session-lock-v1client with physical and touch keyboards and PAM authentication.patin-session— a compact, compositor-neutral session action menu.
A consumer depends on patin alone, or additionally on whichever adapter
crates it wants; none are pulled in automatically.
Patin uses ordinary stable Rust and pins the exact toolchain in
rust-toolchain.toml. A full workspace build also needs the xkbcommon and PAM
development packages (libxkbcommon-dev and libpam0g-dev on Debian/Ubuntu).
They provide the native metadata, headers, and linker inputs used by
smithay-client-toolkit and patin-lock; CI installs them before invoking
Cargo. On postmarketOS/Alpine, the matching packages are xkbcommon-dev and
linux-pam-dev.
cargo build --workspace
cargo run --example demo_bar
cargo fmt --all -- --check
cargo test --workspace --all-targets
cargo clippy --workspace --all-targets --all-features -- -D warnings
mdbook buildThe example connects to the compositor selected by WAYLAND_DISPLAY, creates
a top layer-shell bar, and demonstrates layout, rendering, scaling, and
damage. Its clock and its battery, volume, and network status
providers are fixtures for proving toolkit behavior, not built-in Patin
components. Status values are rendered as small dependency-free vector icons
from the opt-in patin-icons crate; only the time remains textual. Wi-Fi and
registered cellular service are
independent indicators with their own signal strength, so both can appear at
once; wired connectivity remains capability-driven as well. The clock and
volume grow inward from the inset left edge; network transports and battery
grow inward from the inset right edge. Flexible space between those clusters
keeps the output center clear. The
Wi-Fi and cellular icons are interactive. Cellular remains capability-driven;
Wi-Fi appears only while its radio is enabled. Tapping a visible icon launches
the optional network settings on the matching page.
All five status glyphs—battery, volume, Wi-Fi, wired, and cellular—come from
patin-icons; the bar owns only their placement and palette. The volume glyph
has explicit off, low, medium, and high states. Zero volume and a muted sink
draw a small neutral cross beside the speaker instead of looking like an empty
low-volume meter. When the Wi-Fi radio is off, its bar icon and slot disappear.
NetworkManager remains the system daemon; Patin is its frontend. The separate composition scans and joins Wi-Fi, toggles mobile data, and manages one persistent hotspot profile with editable SSID, password, security, and band. Wi-Fi, Cellular, and Hotspot are separate tabs and can each be selected at startup:
./scripts/install-network-settings-user.sh
patin-network-settings --page=wifi
patin-network-settings --page=cellular
patin-network-settings --page=hotspotProfile operations use NetworkManager's nmcli client. PolicyKit remains
session policy; Patin displays authorization failures but is not an auth agent.
Network settings is an ordinary XDG toplevel, so the compositor can tile,
move, resize, focus, and close it like any other application. Editable fields
use Wayland text-input-v3 to request the session OSK; physical keyboard input
and a compositor's manual OSK gesture remain available fallbacks.
The XDG window is created before the initial NetworkManager refresh, so cold
Wi-Fi discovery cannot delay the window appearing; each page shows a loading
label until that first refresh completes. The initial Wi-Fi list shows saved
infrastructure profiles that are currently available. Cached access points add
signal strength and a Wi-Fi icon, while the active row is marked connected.
Unavailable profiles remain tracked internally so they can reappear, but no
row or cross is shown. Network rows omit a redundant numeric percentage because
the icon carries the signal state. Availability is refreshed every two seconds while this tab
is open. A lightweight background scan is requested every ten seconds, and
access points not seen for thirty seconds expire to unavailable. “Scan for new
networks” requests an immediate scan and then adds newly discovered, unsaved
networks to the list.
Library consumers implement patin::platform::Shell, then choose either
layer-shell run for shell surfaces or run_window for an XDG toplevel.
The launcher is an optional composition, not toolkit startup behavior. It discovers visible freedesktop desktop entries, displays them in a compact floating list, and closes after successfully spawning the tapped application:
./scripts/install-launcher-user.sh
patin-launcherThe launcher uses a full-output transparent input surface but draws only a
centered 280×350 panel. The surrounding output remains visually unchanged;
tapping it dismisses the launcher without activating the application beneath.
The panel contains a simple vertical list with an XDG application icon and name
per row. Drag vertically on touch or use a pointer wheel to
scroll through ten compact lines; tap a row to launch it. Its deep-purple
palette matches the demo bar and lock screen rather than copying Fuzzel's
colors. On 0xin, the existing configurable shell
gestures can replace Fuzzel without adding a compositor-specific code path to
Patin:
gesture = top-down, spawn, pgrep -x patin-launcher >/dev/null || patin-launcher
gesture = to-top, spawn, pkill -x patin-launcherOther compositors can start the same binary from their own key or gesture configuration.
The optional session composition displays configured logout, reboot, and power-off actions in a compact floating panel. Its full-output surface is transparent outside the panel, and a tap there dismisses the menu without a Cancel row:
./scripts/install-session-user.sh
PATIN_SESSION_LOGOUT_PROGRAM="$HOME/.local/bin/0xinctl" \
PATIN_SESSION_LOGOUT_ARGUMENT=quit \
PATIN_SESSION_LOGOUT_LABEL="Log out to Phrog" \
patin-sessionReboot and power-off use systemctl directly. Logout is optional and supplied
by the shell integration through environment variables, so the Patin binary
does not depend on 0xin. The phone's existing 0xin-session-menu wrapper can
export those three variables and exec patin-session, leaving its 2-second
power-button mapping unchanged.
The lock is security-sensitive, so its binary and PAM policy are separate, explicit installation steps. Install the distribution-matching example policy; do not copy one for a different PAM stack.
./scripts/install-lock-user.sh
# postmarketOS / Alpine
sudo install -m 0644 data/pam/patin-lock.alpine /etc/pam.d/patin-lock
# Then, from the graphical session:
patin-lockArch and Debian examples are available as patin-lock.arch and
patin-lock.debian. The client refuses to acquire the lock when its PAM policy
is missing. It discovers outputs and seats at runtime, covers every output, and
accepts a physical keyboard, pointer, or its built-in touch keyboard.
Successful PAM authentication is the only normal unlock path.
By default the touch keyboard is the full QWERTY/symbol layout. Pass
--keypad=numeric for a 3x4 digit PIN pad instead — useful when the account's
real password is itself numeric, since either mode just types into the same
password PAM checks:
patin-lock --keypad=numericBoth keyboards use compact key groups with an adaptive lower-screen inset
rather than stretching to fill an output or sitting against its bottom edge.
The numeric keypad stays centered, while the full keyboard is width-limited on
larger outputs. The empty password field contains its Enter password hint;
typing replaces it with bullets, and authentication progress or errors remain
visible below the field.
Set PATIN_LOCK_KEYPAD=numeric (matching the existing PATIN_TRACE
convention) to make that the default without passing the flag every time —
export it from a shell profile for manual launches, or set it directly in
whatever spawns patin-lock (a compositor keybind, a session unit) so it
applies there too. An explicit --keypad= argument always overrides it.
If the compositor supports zwlr_output_power_manager_v1, the lock screen
also powers off the display after 1 second of no key/touch/pointer activity —
or 5 seconds once you've started typing a password, so pauses between digits
don't blank the screen mid-entry. Ordinary touch/pointer/keyboard input never
wakes it — only the power button
(XF86PowerOff) does, toggling the display off if it's on or back on if it's
off. This is deliberate: a phone in a pocket brushes its screen constantly,
and waking on any of that would defeat the point of blanking it.
No compositor keybind is needed for the power button: while a session is
locked, a spec-compliant compositor forwards physical keys straight to the
lock client rather than intercepting them for its own keybinds (0xin does
this explicitly to keep its keybinds from bypassing the lock), so
patin-lock sees XF86PowerOff as an ordinary keyboard event and handles it
itself. SIGUSR1 sent to the --worker process does the same toggle and
remains useful for scripted testing (e.g. over SSH), but isn't required for
the physical button to work.
For a persistent one-word user command, run this from a Patin checkout on the FP5:
./scripts/install-demo-user.sh
patinThis installs the demo_bar example as ~/.local/bin/patin and its separately
launched patin-network-settings companion. It does not add a default toolkit
binary. The FP5 login profile
already includes ~/.local/bin in PATH; open a new terminal after the first
installation if the current shell has not loaded that profile.
Normal runs print only startup/provider information and errors. Enable per-frame damage and raw touch diagnostics when needed:
PATIN_TRACE=1 patinThe current temporary native demo build can also be launched directly:
env -u LD_LIBRARY_PATH \
XDG_RUNTIME_DIR="/run/user/$(id -u)" \
WAYLAND_DISPLAY=wayland-0 \
/tmp/patin-fp5-test/target/release/examples/demo_barThe /tmp checkout is temporary and may disappear after reboot. From the
laptop, the same command can be invoked with:
ssh -t fp5 'env -u LD_LIBRARY_PATH \
XDG_RUNTIME_DIR=/run/user/$(id -u) \
WAYLAND_DISPLAY=wayland-0 \
/tmp/patin-fp5-test/target/release/examples/demo_bar'Patin is built from modular shell capabilities for any compatible Wayland environment. Outputs, scale, transforms, input capabilities, and optional protocols are discovered at runtime. Different compositions select from shared modules; neither hardware models nor compositor brands define the core architecture.
The project book lives under docs/. Preview it with:
mdbook serve- Foundation: pinned Rust project, license, checks, mdBook, and CI.
- First surface: a solid-color top layer-shell surface with an exclusive zone.
- Rendering: drawing primitives and a correctly scaled text clock.
- Input: pointer and multitouch interaction without stealing application focus.
- UI core: internal layout, styling, hit-testing, damage, and components.
- Services: battery, network, audio, notifications, and media state.
- Session lock: standalone multi-output lock composition with touch input and PAM authentication.
- Mobile profile: phone navigation, launcher, quick settings, and keyboard.
- Compositor integration: workspace state and commands through a replaceable 0xin control-socket adapter.
Each completed stage records its real verification commands and results in the book. Patin does not create commits on the user's behalf.
Patin is licensed under the MIT License.