Imported from schwafe/wlbreaktime (
AGENTS.md). Install upstream withnpx skills add schwafe/wlbreaktime. Copyright stays with the author.
Agent Guide for wlbreaktime
Project Overview
wlbreaktime is a Wayland-native break reminder application that:
- Uses_unix domain sockets for daemon/helper communication
- Integrates with systemd (socket activation, Type=notify)
- Shows Wayland popups during breaks
- Plays sounds and sends desktop notifications
- Can turn off monitors (currently supports niri compositor only)
Runtime requirements: Must run on systemd with a Wayland compositor. The daemon exits early if systemd is not running.
Essential Commands
Build
cargo build # Debug build
cargo build --release # Release build
Run
cargo run --bin wlbreaktime-daemon # Run the main daemon
cargo run --bin wlbreaktime # Run the helper CLI tool
Testing
cargo test # Run tests
Note: Tests are currently not implemented (noted in README TODO).
Linting & Formatting
cargo clippy # Run linter
cargo fmt # Format code
cargo fmt --check # Check formatting without modifying
Note: Linting passes cleanly with cargo clippy -- -D warnings; keep it that way.
Code Organization
src/
├── main.rs # Main daemon (wlbreaktime-daemon binary)
├── config.rs # Configuration parsing
├── wayland.rs # Wayland popup implementation & state management
└── bin/
└── helper.rs # CLI helper tool (wlbreaktime binary)
resources/
├── wlbreaktime.service # Systemd service unit
├── wlbreaktime-daemon.socket # Systemd socket unit (socket activation)
└── rebana_l_gong.wav # Break notification sound
Architecture
Daemon (main.rs)
- Receives Unix datagram socket from systemd via socket activation
- Alternates between work time and break time in a loop
- Handles socket commands:
break,set <minutes>,reset,get,snooze - After the notification announces a break, keeps reading the socket for
PRE_BREAK_SECONDS(10s) before the break starts, sosnooze(andset/reset) can still postpone it - Uses
Instant::now()with manual elapsed time calculation for timing - System suspension detected via
ErrorKind::Interruptedon socket read - Sends
NotifyState::Readyto systemd after initialization
Helper (src/bin/helper.rs)
- CLI tool for controlling the running daemon
- Bind temporary socket in XDG_RUNTIME_DIR:
wlbreaktime-helper.socket - Communicates with daemon via:
$XDG_RUNTIME_DIR/wlbreaktime-daemon.socket - Cleanup: unlinks its own temporary socket after each command
CLI Command Structure
All commands use flags with both long and short forms:
| Flag | Short | Wire Command | Description |
|---|---|---|---|
--get |
-g |
get |
Get remaining time. Full status by default; add --minutes for output in minutes only |
--set |
-s |
set |
Set remaining time. Requires <minutes> argument |
--reset |
-r |
reset |
Reset timer to configured interval |
--break |
-b |
break |
Skip work period and start break immediately |
--snooze |
-z |
snooze |
Skip/upcoming break during pre-break period |
The helper normalizes short flags to their long forms, then maps to wire protocol commands sent to the daemon.
Wayland Integration (wayland.rs)
- Uses
wayland-clientwith manual protocol dispatch - Implements
Dispatchtraits for all Wayland protocol objects - Creates SHM pool in XDG_RUNTIME_DIR for buffer allocation
- Draws checkerboard pattern directly to buffer files
- Surface size requested from compositor (falls back to 1920x1080)
Configuration (config.rs)
- Config path:
$XDG_CONFIG_HOME/wlbreaktime/configor/etc/wlbreaktime/config - User config overrides system config
- Format: simple key-value pairs parsed with regex
- Supported keys:
break_interval=N[s|m](default: 1800s = 30 min)break_duration=N[s|m](default: 80s)show_popup=true|false(default: true)play_sound=true|false(default: true)show_notification=true|false(default: true)turn_off_monitors=true|false(default: false)
Code Patterns & Conventions
Error Handling
- Uses
Result<T, Box<dyn std::error::Error>>extensively - Socket operations use timeout adjustments for dynamic timing
ErrorKind::WouldBlockhandled gracefully (expected timeout)ErrorKind::Interruptedindicates system suspension → triggers timer reset
Socket Communication
- Fixed buffer size: 300 bytes for daemon, 30 bytes for helper
- All responses sent back to return address (no unbound sockets accepted)
- String messages: simple plaintext commands
- Helper always creates fresh socket per execution
Wayland Protocol
- Boilerplate event dispatching for all protocol objects
- Most events logged but not acted upon
- Global binding handled in registry event dispatcher
- Initial commit triggers configure event, which must be acked before attaching buffer
Timing Pattern
let now = Instant::now();
let duration = 300; // seconds
// ...
let elapsed = now.elapsed().as_secs();
let remainder = duration.saturating_sub(elapsed);
Manual elapsed calculation used instead of Duration differences.
Important Gotchas
-
Early Exit on Non-Systemd: Daemon exits immediately if
daemon::booted()returns false. Test environment setup must account for this. -
Socket Activation: daemon requires systemd to pass exactly one file descriptor. Testing without systemd socket activation will fail assertion.
-
Format String Hack: wayland.rs:362 uses
format!("{format:?}")(Debug trait) for filename generation. This is a TODO - not a production pattern. -
Surface Size Fallback: Compositor may not provide surface size; code has fallback to 1920x1080. Note comment: "FIXME: sometimes the surface size is missing".
-
Monitor Control: Only works with niri compositor via
niri msg action power-off-monitors. Error is logged but doesn't break execution. -
No Helper Directory: The helper binary is directly at
src/bin/helper.rs(not insrc/bin/helper/), despite Cargo.toml listing it with path= notation. -
HelperSocket Lifecycle: Helper always removes its temp socket after each command, even if errors occur.
-
Double Buffering: Comments mention double-buffering is needed (pool size * 2), but implementation writes zeros for second buffer and doesn't seem to use it.
-
Asset Embedding: Sound file
rebana_l_gong.wavis embedded viainclude_bytes!()at compile time, not loaded at runtime.
Dependencies
Key dependencies:
wayland-client+wayland-protocols- Wayland clientlibsystemd- Systemd integration (activation, notify)notify-rust- Desktop notificationsrodio- Audio playbackregex- Config parsing
Testing Notes
- No tests currently implemented (TODO in README)
- Running
cargo test --no-runworks but there are no test functions - Test environment setup requires systemd for full daemon functionality
Edition & Toolchain
- Rust 2024 Edition
- Targets
x86_64-unknown-linux-gnu(implied from Linux/Wayland dependencies) - Uses
lazy_staticcrate despite being available in Rust standard library since 1.70