A native Signal client for macOS, Linux and Windows.
Written in Rust + Svelte. Not Electron.
Apple's Signal client runs on Electron ā that's a full Chromium VM whose job is to display a list of messages. Piccione is the lean version: a single native binary, a native WebKit WebView for rendering, an encrypted SQLite store for state, and just enough Rust to wire it all to Signal's servers.
On a 2024 MacBook the cold-start physical footprint is ~100 MB. Signal Desktop on the same machine sits at 350-500 MB. We don't ship JavaScript runtimes we don't use.
SyncMessage::Contacts
stub. Implemented from scratch in Rust as part of this project; PRs
pending against the whisperfish/presage and whisperfish/libsignal-service-rs
upstream projects.~/Library/Application Support/app.piccione/.db_key at modetauri-plugin-notification is not wired yet).| Layer | Choice | Why |
|---|---|---|
| Shell | Tauri 2 | Native WebKit on macOS, native WebView2 on Windows, native WebKitGTK on Linux. ~10 MB binaries. |
| UI | Svelte 5 + TypeScript | Runes ($state, $derived), small bundles, reactive by default. |
| Signal protocol | presage (main) |
Only pure-Rust Signal client lib worth building on. We patch it (storage-service branch) until our PRs land. |
| Storage | presage-store-sqlite |
SQLCipher via libsqlite3-sys fork. |
| Crypto | libsignal v0.91 |
The real Signal protocol primitives. |
| Allocator | mimalloc |
macOS's default malloc holds onto freed pages forever. mimalloc returns them to the OS. |
| Process | ps RSS (over-counted) |
Physical footprint |
|---|---|---|
piccione (Rust core) |
~125 MB | ~42 MB |
| WebKit.GPU | 30 MB | 11 MB |
| WebKit.Networking | 24 MB | ~6 MB |
| WebKit.WebContent | 67 MB | 41 MB |
| Total | ~244 MB | ~100 MB |
ps over-counts because it includes the shared read-only system framework
mappings (__TEXT, __OBJC_RO, __LINKEDIT) in every process's RSS even
though they're physically loaded once for the whole machine. Activity
Monitor's "Memory" column and vmmap --summary report the honest number.
See inbox/memory-accounting.md for the
full breakdown.
| Platform | Need |
|---|---|
| macOS | Xcode CLT, Node 20+, Rust stable, cargo install tauri-cli@2 |
| Linux | libwebkit2gtk-4.1-dev, libgtk-3-dev, libssl-dev, librsvg2-dev, libayatana-appindicator3-dev, Node 20+, Rust stable |
| Windows | Visual Studio Build Tools (Desktop C++ workload), WebView2 (preinstalled on Win11), Node 20+, Rust stable |
git clone https://github.com/calibrae/piccione.git
cd piccione
npm install
npm run tauri dev
Vite serves the frontend on localhost:1420; Tauri spawns the Rust core
and hosts it inside a native WebView. Hot reload works for the Svelte
side; Rust changes trigger a cargo build + relaunch.
npm run tauri build
Produces a signed-ready bundle at src-tauri/target/release/bundle/<platform>/:
.app + .dmg.deb, .rpm, .AppImage.msiSigning + notarisation for macOS distribution requires the env vars listed
in .github/workflows/release.yml. CI does this automatically on v* tag push.
src/ Svelte 5 frontend
App.svelte root: loading ā LinkDevice or ChatLayout
lib/
components/
ChatLayout.svelte conversation list + thread + new-message
LinkDevice.svelte QR provisioning screen
MediaBrowser.svelte per-conversation attachment grid + lightbox
Settings.svelte privacy + theme + sign-out modal
ToastContainer.svelte
stores/
messaging.svelte.ts conversations, messages, receipts, typing
provisioning.svelte.ts link-device state machine
settings.svelte.ts light/dark/auto + privacy flags
types.ts
src-tauri/
src/
lib.rs Tauri Builder, command registration, startup
app_state.rs AppState { ProvisioningManager, MessagingService, Settings, ... }
commands/ #[tauri::command] wrappers
provisioning/ link_secondary_device + state machine
messaging/ receive loop, send queue, contacts/groups/messages reads
settings.rs persisted user preferences
store/keychain.rs passphrase storage
bin/piccione_cli.rs signalui-cli equivalent (pair/paired/devices)
inbox/
storage-service-research.md How Signal Storage Service works + what we built
memory-accounting.md Why ps lies about Cocoa app RSS
This was a 3-day build by one person. PRs welcome but the bar is "explain what you're doing in the commit message so someone reading it in 18 months gets the why". No co-author lines from AI assistants in commit messages.
If your change touches one of the patched dependencies (presage or
libsignal-service-rs, both pinned to forks on calibrae),
PR there first ā those are what the upstream projects will see.
presage and libsignal-service-rs, and the reason any of this
works.libsignal
with usable Rust bindings.AGPL-3.0. Same as Signal-Android and Signal-Desktop. Not Apple's fault if they don't accept this in the Mac App Store.