Architecture
Overview
siggy is a terminal Signal client that wraps signal-cli via JSON-RPC over stdin/stdout. It is built on a Tokio async runtime with Ratatui for rendering.
+------------+ mpsc channels +----------------+
| TUI | <---------------> | Signal |
| (main | SignalEvent | Backend |
| thread) | UserCommand | (tokio task) |
+------------+ +--------+-------+
|
stdin/stdout
|
+--------v-------+
| signal-cli |
| (child proc) |
+----------------+
Async runtime
The application uses a multi-threaded Tokio runtime (via #[tokio::main]).
The main thread runs the TUI event loop. signal-cli communication happens in
spawned Tokio tasks that communicate back to the main thread via
tokio::sync::mpsc channels.
Event loop
The main loop in main.rs runs on a 50ms tick:
- Poll keyboard – check for key events via Crossterm (non-blocking, 50ms timeout)
- Drain signal events – process all pending
SignalEventmessages from the mpsc channel - Render – call
ui::draw()with the currentAppstate
This keeps the UI responsive while processing backend events as they arrive.
Startup sequence
- Load config from TOML (or defaults)
- Check if setup is needed (
accountfield empty) - If needed: run the setup wizard (signal-cli detection, phone input, QR linking)
- Open SQLite database (or in-memory for
--incognito) - Spawn signal-cli child process
- Load conversations and contacts from database + signal-cli
- Enter the main event loop
Key dependencies
| Crate | Purpose |
|---|---|
ratatui 0.29 | Terminal UI framework |
crossterm 0.28 | Cross-platform terminal I/O |
tokio 1.x | Async runtime |
serde / serde_json | JSON serialization for signal-cli RPC |
rusqlite 0.32 | SQLite database (bundled) |
chrono 0.4 | Timestamp handling |
qrcode 0.14 | QR code generation for device linking |
image 0.25 | Image decoding for inline previews |
anyhow 1.x | Error handling |
toml 0.8 | Config file parsing |
dirs 6.x | Platform-specific directory paths |
uuid 1.x | RPC request ID generation |