# 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: ```toml # 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. ```bash # 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`) ```rust pub struct AppState { pub current_screenshot: RwLock>>, pub active_screenshot: RwLock>>, pub target_color: RwLock>>, pub active_color: RwLock>, } ``` ### 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_screenshot` → `active_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 |