Skip to content

System Architecture

Joseph Chacko edited this page Jul 22, 2026 · 2 revisions

System Architecture & IPC

Relevant source files

The following files were used as context for generating this wiki page:

Aurora utilizes a daemon/client architecture to decouple audio engine management from the graphical user interface. This split ensures that music playback remains uninterrupted even if the GUI is closed or restarted. The two components communicate over a local Unix domain socket using a custom, length-prefixed JSON protocol defined in the shared aurora-protocol crate.

High-Level Component Overview

The system is divided into three primary crates:

  1. aurora-daemon: The background service responsible for the audio pipeline (rodio), library indexing (SQLite), and system integrations like MPRIS2.
  2. aurora-player: The Slint-based frontend that provides the user interface and sends commands to the daemon.
  3. aurora-protocol: The "Source of Truth" containing shared data structures and the IPC message schema.

Data Flow Lifecycle

The following diagram illustrates the lifecycle of a request, from a user interaction in the GUI to audio output in the daemon.

Diagram: Request-Response Lifecycle

sequenceDiagram
    participant UI as "aurora-player (Slint UI)"
    participant Client as "aurora-player (interface.rs)"
    participant Protocol as "aurora-protocol"
    participant Daemon as "aurora-daemon (handlers.rs)"
    participant Audio as "rodio Sink"

    UI->>Client: User clicks "Play Song"
    Client->>Protocol: Construct Request::Play(Uuid)
    Protocol-->>Client: Serialize to JSON
    Client->>Daemon: Send length-prefixed JSON over Unix Socket
    Daemon->>Protocol: Deserialize JSON to Request enum
    Daemon->>Daemon: handle_client() dispatches to Play handler
    Daemon->>Audio: sink.play() / append()
    Daemon->>Protocol: Construct Response::Status
    Daemon->>Client: Broadcast Status to all connected clients
    Client->>UI: Update Slint Properties (position, is_playing)
Loading

Sources: README.md:12-18, protocol/src/interface.rs:5-35, daemon/src/main.rs:115-142


IPC Mechanism: Unix Domain Sockets

Aurora relies on Unix domain sockets for low-latency local communication. The daemon acts as a UnixListener, while the player acts as a UnixStream client.

Socket & Process Constants

The daemon manages its lifecycle using specific files located in /tmp:

Constant Path Purpose
PIDFILE /tmp/aurora-daemon.pid Ensures only one instance of the daemon runs.
SOCKFILE /tmp/aurora-daemon.sock The Unix socket address for IPC.
OUTFILE /tmp/aurora-daemon.out Redirected stdout for the daemonized process.

Sources: daemon/src/main.rs:24-26

Connection Handling

Upon startup, the daemon binds to SOCKFILE daemon/src/main.rs:108-112. When a client connects, the daemon splits the socket into a reader and writer daemon/src/main.rs:117.

  • The Writer is stored in the global StateStruct.clients list to allow for asynchronous broadcasts (e.g., status updates or theme changes) daemon/src/main.rs:121.
  • The Reader is passed to handlers::handle_client to process incoming Request variants daemon/src/main.rs:128.

Wire Protocol: Length-Prefixed JSON

To ensure reliable message framing over the stream-oriented Unix socket, Aurora uses a length-prefixed strategy.

  1. Length Header: Every message is preceded by a 4-byte big-endian integer representing the length of the following JSON payload.
  2. JSON Payload: The actual data is a serialized Request or Response enum variant.

The Protocol Bridge (aurora-protocol)

The aurora-protocol crate defines the exact shape of every message. This ensures that both the daemon and the player are always in sync regarding data types.

Key Structures:

Diagram: Protocol Entity Mapping

classDiagram
    class Request {
        <<enumeration>>
        +Play(Uuid)
        +Pause()
        +Enqueue(Uuid)
        +SetVolume(f32)
        +Search(SearchType)
    }
    class Response {
        <<enumeration>>
        +Status(Status)
        +Queue(Vec~Song~)
        +Theme(Theme)
        +Error(u8, String)
    }
    class Status {
        +Option~Song~ current_song
        +bool is_paused
        +Duration position
        +f32 volume
    }
    class Theme {
        +String bgd0
        +String acct
        +String txt1
    }

    Request ..> Response : Triggers
    Response o-- Status : Contains
    Response o-- Theme : Contains
Loading

Sources: protocol/src/interface.rs:1-51, protocol/src/lib.rs:12-40


System Startup & Data Flow

When the system initializes, the daemon prepares the environment for IPC and audio:

  1. Daemonization: If launched without arguments, the process forks into the background and creates the PIDFILE daemon/src/main.rs:31-40.
  2. State Initialization: The StateStruct is initialized, loading the library index from SQLite, recently played history, and liked songs daemon/src/main.rs:71-94.
  3. Thread Spawning: Several background threads are started to handle asynchronous events:
  4. IPC Loop: The async_main function enters an infinite loop, accepting new Unix socket connections and spawning a tokio task for each client daemon/src/main.rs:115-142.

Signal Handling

The daemon captures TERM_SIGNALS. Upon receiving a termination signal, it performs a cleanup by deleting the SOCKFILE and PIDFILE before exiting daemon/src/main.rs:52-63.

Sources: daemon/src/main.rs:51-143

Clone this wiki locally