Files
SJNmusic/gui-rs
..
2026-06-25 18:27:15 +02:00
2026-06-25 18:27:15 +02:00
2026-06-25 18:27:15 +02:00

sjnmusic GUI (Rust)

Pure-Rust remote control for sjnmusicd. Built on egui + eframe. Talks to the daemon over HTTP at 127.0.0.1:14567 by default and owns no audio state.

This replaces the previous Electron GUI. The daemon API is unchanged.

Build & Run

Prerequisites: Rust 1.80+ (stable). On Linux you may also need:

  • libssl-dev if you ever swap ureq for reqwest (not needed today)
  • libdbus-1-dev + a system tray daemon (e.g. libappindicator, KDE StatusNotifierItem watcher, GNOME Shell extension) for the optional tray icon. The tray fails gracefully if one is missing.
cd gui-rs
cargo run --release

To launch with the system tray enabled the GUI runs the tray at every startup, but it falls back silently on systems without a tray service, so it's safe to leave it on.

Environment variables

Variable Default Meaning
SJNMUSIC_HOST 127.0.0.1 Daemon host
SJNMUSIC_PORT 14567 Daemon port
SJNMUSIC_THEME dark dark or light (also persisted to disk)
RUST_LOG info Standard env_logger filter

Features (feature parity with the Electron GUI)

  • Sidebar with 8 nav routes + offline banner
  • Now-playing footer with transport, seek-back, repeat, volume
  • Library view with fuzzy-search filter
  • Queue view with shuffle / clear / skip-to / remove
  • Playlists: list view + single playlist view (add/remove/reorder/ rename/duplicate/delete/play)
  • Search + Download with yt-dlp candidate picker and inline thumbnails
  • Downloads: live status table auto-refresh
  • History (last 100 plays)
  • Stats with cards and top songs
  • Settings (theme picker)
  • Spacebar = pause/resume
  • System tray (best effort): now-playing label, pause/resume, skip, show window, quit

Architecture

src/
  main.rs           entry: eframe::run_native, tray bootstrap
  app.rs            SJNMusicApp: eframe::App impl, panel layout, router
  state.rs          AppState, polling worker, Route enum, Theme
  daemon.rs         HTTP client + typed daemon responses
  fmt.rs            formatting helpers (time, html escape)
  tray.rs           tray-icon setup (graceful fallback)
  thumbnails.rs     async thumbnail download + TextureHandle cache
  views/
    mod.rs          dispatch helper
    library.rs      fuzzy-filtered song list
    queue.rs        current queue + shuffle / clear
    playlists.rs    list view + create
    playlist.rs     single playlist (add/remove/reorder/rename/...)
    search_download.rs   yt-dlp picker + thumbnails
    downloads.rs    live status table
    history.rs      recent plays
    stats.rs        aggregate cards + top songs
    settings.rs     theme picker

The UI thread owns an Arc<DaemonClient> and a background worker thread polls /now-playing, /queue, and /downloads every second, pushing snapshots into Arc<Mutex<Snapshot>>. After each poll the worker calls ctx.request_repaint() so the UI picks up changes.

UI actions (play / pause / skip / volume slider / ...) run as fire-and-forget std::thread::spawns calling DaemonClient directly, so a stalled daemon never freezes the GUI.