2026-03-17 11:29:56 +01:00
2026-03-17 11:29:56 +01:00
2026-03-15 00:41:14 +01:00
2026-03-16 23:40:30 +01:00
2026-03-17 11:20:38 +01:00
2026-03-17 11:20:38 +01:00
2026-03-16 23:40:30 +01:00
2026-03-15 00:41:14 +01:00
2026-03-16 23:40:30 +01:00
2026-03-16 23:58:49 +01:00
2026-03-16 23:58:49 +01:00
2026-03-16 17:31:21 +01:00
2026-03-16 18:28:00 +01:00

Ambiligth

Ambiligth is a highly optimized, cross-platform Rust application that captures your screen's dominant color in real-time and synchronizes it with your smart lights via Home Assistant. It acts as a software-based "Ambilight" for your monitor, creating an immersive viewing experience.

Features

  • Cross-Platform Screen Capture:
    • Linux (via grim-rs)
    • macOS (via CoreGraphics)
    • Windows (via GDI / BitBlt)
  • Advanced Color Processing: Uses okmain (Oklab color space) to accurately and efficiently extract the perceptual dominant color of the screen.
  • Smart Image Diffing: Uses image-compare (MSSIM simple algorithm) to perceptually compare screen frames. It only sends updates to Home Assistant if the screen content has changed significantly, preventing API spam and light flickering.
  • RGBW/RGBWW Support: Intelligently preserves brightness and white channels for advanced smart lights, updating only the color values.
  • Smooth Transitions & Filtering: Built-in exponential smoothing and configurable Home Assistant transition times.
  • Auto-Restore: Optionally takes a snapshot of your lights' state on startup and restores them to their original colors when you exit the program.

Prerequisites

  • Rust (cargo)
  • A running Home Assistant instance with a Long-Lived Access Token.
  • Color-capable smart lights integrated into Home Assistant.
  • Optional: nix (if you are using the provided Nix flake environment).

Configuration

Ambiligth is configured via a config.toml file in the root of the project, or via environment variables (e.g., HA_URL, HA_TOKEN).

Create a config.toml file with the following options:

# Your Home Assistant URL (default: "http://localhost:8123")
ha_url = "http://192.168.1.100:8123"

# Your Long-Lived Access Token (Required)
ha_token = "ey..."

# Target Frames Per Second for light updates (default: 15)
# This is the MAXIMUM rate - lights only update when color actually changes
target_fps = 15

# Color smoothing factor from 0.0 to 1.0 (default: 0.0)
# Higher values mean slower, smoother color transitions.
smoothing = 0.5

# Time in seconds for the lights to transition to the new color (default: 0.5)
transition = 0.5

# Minimum perceptual difference percentage to trigger an update (default: 1.0)
# Increase this if lights are flickering on mostly static screens.
min_diff_percent = 2.0

# Restore lights to their original state on exit (default: true)
restore_on_exit = true

# List of specific light entity IDs to control. 
# If left empty, it will auto-detect all color-capable lights.
lights = [
    "light.monitor_backlight",
    "light.desk_strip"
]

Running the Application

Because screen capture and image processing are highly CPU-intensive, you must run this application in release mode to achieve real-time performance and avoid high CPU usage.

# Standard cargo run
cargo run --release

# If you are using the Nix dev shell
nix develop -c cargo run --release

How It Works

  1. Capture: Takes a fast, SIMD-optimized screenshot using platform-native APIs.
  2. Diff: Compares the new screenshot against the previous one using Structural Similarity (MSSIM). If the difference is below min_diff_percent, it skips the update.
  3. Analyze: Calculates the dominant color of the current frame using the Oklab color space.
  4. Smooth: Blends the new dominant color with the previous one based on the smoothing factor.
  5. Update: Constructs a highly specific JSON payload for Home Assistant, ensuring white channels and brightness are preserved, and sends it asynchronously via reqwest.

TODO: Parallel Event Loop Restructuring (COMPLETED)

This architecture has been implemented. See src/eventloop.rs for details.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                      Shared State                            │
│                                                              │
│  current_screenshot  ← Latest captured (screenshot loop)    │
│  active_screenshot   ← Screenshot that produced target_color│
│  target_color        ← Color calculated from active_screenshot│
│  active_color        ← Smoothed color sent to lights        │
└─────────────────────────────────────────────────────────────┘

Data Flow:

Screenshot Loop (screenshot_fps)     Color Calc (on-demand)
        │                                    │
        ▼                                    ▼
 [current_screenshot]               [target_color]
        │                                    │
        │ diff(current, active)              │
        │ > min_diff_percent?                │
        ▼                                    │
 [active_screenshot] ◄───────────────────────┘
        │                                    │
        │         Smoothing Loop (target_fps)│
        │                 │                  │
        │                 ▼                  │
        │           [active_color] ◄─────────┘
        │                 │
        ▼                 ▼
   Light Update (parallel to all lights)

Implementation Tasks

Phase 1: Settings Update (settings.rs)

  • Add screenshot_fps config option (default: 3)
  • Remove transition option (smoothing handles transitions)

Phase 2: Create Shared State (state.rs)

pub struct AppState {
    pub current_screenshot: RwLock<Option<Arc<Screenshot>>>,
    pub active_screenshot: RwLock<Option<Arc<Screenshot>>>,
    pub target_color: RwLock<Option<Rgb<f32>>>,
    pub active_color: RwLock<Rgb<f32>>,
}

Phase 3: Parallel Event Loop (eventloop.rs)

Thread 1 - Screenshot Loop (std::thread, runs at screenshot_fps)

  • Capture screenshot → store in current_screenshot
  • Compare current_screenshot vs active_screenshot
  • If diff > min_diff_percent:
    • Copy current_screenshotactive_screenshot
    • Spawn color calc task

On-demand - Color Calculation (tokio::spawn)

  • Calculate dominant color from active_screenshot
  • Update target_color

Thread 2 - Smoothing & Light Update (tokio, runs at target_fps)

  • Lerp active_color toward target_color using smoothing factor
  • Send active_color to all lights in parallel

Phase 4: Parallel Light Updates (homeassistant.rs)

  • Refactor set_lights_color to update single light, return future
  • Create set_lights_color_parallel using futures::future::join_all

Concurrency Strategy

Resource Lock Type Writer Readers
current_screenshot RwLock Screenshot loop Diff check
active_screenshot RwLock Screenshot loop Color calc
target_color RwLock Color calc task Smoothing loop
active_color RwLock Smoothing loop Light update

Each field has a single writer - no complex synchronization needed.

File Changes Summary

File Changes
settings.rs Add screenshot_fps, remove transition
state.rs New - Shared state struct
eventloop.rs Rewrite - Multi-threaded architecture
homeassistant.rs Add parallel light update method
main.rs Wire up new state, pass to eventloop
Description
No description provided
Readme 1.3 MiB
Languages
Rust 95.6%
Nix 4.4%