Skip to content

UI Daemon Bridge

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

UI–Daemon Bridge (interface.rs)

Relevant source files

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

The interface.rs file serves as the central nervous system of the aurora-player application. It manages the lifecycle of the connection to aurora-daemon, handles asynchronous IPC communication via Unix sockets, and bridges the Rust backend state with the Slint UI event loop.

Overview and Initialization

The primary entry point is the interface() function. This function establishes a connection to the daemon and initializes the shared state used by the frontend.

Connection Lifecycle

The bridge attempts to connect to the Unix socket defined by aurora_protocol::SOCKFILE. If the daemon is not reachable, the bridge enters a retry loop, attempting to reconnect every 1 second app/src/interface.rs:328-341. If the connection is lost during operation, the UI is notified via aurora.set_disconnected(true) app/src/interface.rs:356-358.

State Initialization

Before starting the communication tasks, the bridge initializes the StateStruct. This struct holds the local cache of the library, search results, and UI selection states app/src/types/mod.rs:10-26.

State Initialization Steps:

  1. Default Art: Loads the fallback album art into a SharedPixelBuffer app/src/interface.rs:343-345.
  2. MPSC Channel: Creates a tokio::sync::mpsc channel for sending Request variants from UI callbacks to the unix_sender task app/src/interface.rs:347.
  3. State Wrapping: Wraps the StateStruct in an Arc<Mutex<...>> (aliased as State) for thread-safe access app/src/interface.rs:348-366.

Sources: app/src/interface.rs:328-366, app/src/types/mod.rs:9-26


Communication Pipeline

The bridge utilizes two dedicated asynchronous tasks to handle full-duplex communication over the Unix socket.

Data Flow Diagram: IPC Bridge

This diagram illustrates how a UI interaction (like clicking play) travels through the bridge to the daemon and back.

graph TD
    subgraph "Slint UI Thread"
        [UI_Callback] --> |"tx.send(Request)"| MPSC_Channel["mpsc::Receiver<Request>"]
        [UI_Component] -.-> |"Reactive Binding"| Slint_Properties["Slint Properties (e.g., set_Title)"]
    end

    subgraph "Async Bridge Tasks (interface.rs)"
        MPSC_Channel --> unix_sender["unix_sender (Task)"]
        unix_sender --> |"JSON + Length Prefix"| UnixSocket_Write["UnixStream (Write Half)"]
        
        UnixSocket_Read["UnixStream (Read Half)"] --> unix_recver["unix_recver (Task)"]
        unix_recver --> |"Response Dispatch"| Response_Logic["Response Handling Logic"]
        Response_Logic --> |"upgrade_in_event_loop"| Slint_Properties
    end

    subgraph "aurora-daemon"
        UnixSocket_Write -.-> Daemon_Process["Daemon Request Handler"]
        Daemon_Process -.-> UnixSocket_Read
    end
Loading

Sources: app/src/interface.rs:40-72, app/src/interface.rs:114-156

unix_sender

The unix_sender task waits for Request enums on the MPSC receiver. When a request arrives, it:

  1. Serializes the Request to JSON using serde_json app/src/interface.rs:44.
  2. Prepends a 4-byte big-endian length prefix app/src/interface.rs:45-46.
  3. Writes the buffer to the OwnedWriteHalf of the Unix socket app/src/interface.rs:47.

unix_recver

The unix_recver task continuously listens for data from the daemon. It:

  1. Reads the 4-byte length header app/src/interface.rs:64-66.
  2. Reads the exact number of bytes specified by the header into a buffer app/src/interface.rs:68-69.
  3. Deserializes the buffer into a Response enum app/src/interface.rs:70.
  4. Dispatches the response to the appropriate handler logic app/src/interface.rs:72-230.

Sources: app/src/interface.rs:40-72


Response Dispatch & UI Synchronization

When unix_recver receives a Response, it updates the local StateStruct and schedules UI updates on the Slint event loop.

Response Variant Action Taken
Status Updates playback state (position, volume, shuffle, repeat) and triggers the Album Art Pipeline.
Queue Updates state.queue and calls state.update_queue(app) to redraw the queue view app/src/interface.rs:158-163.
SearchResults Updates search results or artist-specific song lists based on the pending_artist_search flag app/src/interface.rs:164-175.
PlaylistResults Updates the currently viewed playlist songs app/src/interface.rs:177-184.
Theme Converts hex strings to slint::Color and updates the global Theme singleton app/src/interface.rs:213-230.

Album Art Pipeline

To prevent UI stuttering, album art is processed on the async thread before being sent to the UI:

  1. Check Change: The bridge compares the new art_path with state.current_art_path app/src/interface.rs:86.
  2. Async Load: If changed, it reads the file from disk and decodes it into a SharedPixelBuffer using album_art_from_data app/src/interface.rs:103-112.
  3. UI Update: The buffer is passed into aurora.set_AlbumArt(Image::from_rgba8(buf)) inside the Slint event loop app/src/interface.rs:153.

Sources: app/src/interface.rs:73-156, app/src/interface.rs:30-38, app/src/types/helpers.rs:15-43


Slint Callback Registrations

The bridge registers numerous callbacks that allow the Slint UI to communicate back to the Rust backend. These are defined in the interface() function after the socket connection is established.

Callback Entity Mapping

This diagram maps Slint UI callback names to the internal Request variants they generate.

classDiagram
    class Slint_AuroraPlayer {
        <<Global Component>>
        +callback play_pause()
        +callback skip_next()
        +callback seek(ms)
        +callback search(query)
        +callback add_to_playlist(song_id, playlist_id)
        +callback toggle_selected(song_id)
    }

    class Request_Enum {
        <<aurora_protocol>>
        +Pause
        +Next
        +Seek(Duration)
        +Search(String, SearchType)
        +Playlist(PlaylistCommand)
    }

    Slint_AuroraPlayer --|> Request_Enum : Triggers via unix_sender
Loading

Sources: app/src/interface.rs:373-610, protocol/src/lib.rs

Key Callback Implementation Patterns

  • Simple Commands: Callbacks like play_pause or skip_next simply clone the writer_tx and send the corresponding Request app/src/interface.rs:373-383.
  • Multi-Selection: The toggle_selected callback modifies a HashSet in the StateStruct. This set is used by helper functions (e.g., update_search_results) to mark songs as selected in the UI app/src/interface.rs:458-472.
  • Complex Actions: The play_selected callback retrieves all IDs from the selected_song_ids set, sends a Request::Enqueue for the batch, and then clears the selection app/src/interface.rs:474-490.

Sources: app/src/interface.rs:373-500, app/src/types/helpers.rs:72-98


Error Handling and Disconnection

The bridge handles two main failure modes:

  1. Initial Connection Failure: The interface() function loops indefinitely, printing "Retrying..." until the daemon is started app/src/interface.rs:336-340.
  2. Runtime Disconnection: If unix_recver or unix_sender encounter an IO error (e.g., the daemon crashes), the tokio::select! block in interface() terminates. The code then sets the UI disconnected property to true and attempts to restart the entire interface() loop app/src/interface.rs:356-364.

Sources: app/src/interface.rs:330-366

Clone this wiki locally