Adalight
English | Русский
⚠️ Beta. Adalight is pre-1.0 — a few rough edges are expected. Please
report bugs and ideas in the issues.
Screen-edge ambient lighting (ambilight) for an LED strip behind your monitor:
the app captures the screen, averages the colors along the edges and streams
them to an Arduino/ESP over the classic Adalight serial protocol.
Runs on Windows (DXGI Desktop Duplication via dxcam, mss fallback) and
Linux/Wayland (Hyprland/wlroots: wf-recorder, grim fallback), with a
Qt (PySide6) GUI: every setting in one window, a live preview of the LED
layout, and a tray icon so the lighting keeps running with the window closed.


Features
- Boards introduce themselves: the bundled firmware answers with its id,
firmware version, LED count and baud rate, so the app picks the baud rate on
its own, lists the board as “Backlight (A1B2C3D4)” instead of “/dev/ttyUSB1”,
and remembers settings per board rather than per port number. Boards with older
firmware keep working in compatibility mode.
- “Blink” and board self-test: not sure which COM port is yours? Make the
strip breathe. Want to take the computer out of the chain? Let the board run a
chase dot by itself.
- Smart port picker: unidentified ports go into their own “other ports”
group and are never hidden; recognition is by USB descriptor (VID/PID), like
Arduino IDE’s “Get Board Info”, plus a “Show all ports” checkbox.
- Channel order (RGB/GRB/BGR/…) — WS2812 strips usually expect GRB. The baud
rate moved to “advanced”: normally nobody needs to see it.
- LED counts per side, start corner, strip direction (cw/ccw), X/Y mirroring.
- Monitor selection, target FPS, gamma / brightness / saturation / smoothing.
- Live settings: image parameters, schedule and adaptive brightness apply
instantly without restarting (and without resetting the board); layout/port
changes auto-apply 5 seconds after the last edit.
- Brightness schedule: time ranges with their own brightness
(e.g. 08:00–20:00 → 0.9, 20:00–00:00 → 0.5), overnight ranges supported,
the default brightness applies outside all ranges.
- Adaptive brightness: dims the strip on dark scenes and ramps it up on
bright ones, with min/max bounds and reaction speed.
- Autostart: launch on login minimized to tray with lighting on
(Windows registry / XDG autostart).
- Lamp mode: solid color / gradient / rainbows / breathing / fireplace
(a hearth with sparks) / comet / aurora / starry sky — the strip
works without screen capture.
- Tray notifications: lighting on/off (when the window is hidden) and new
version releases (checked every 30 minutes); can be disabled in System.
- Music mode: system loopback audio drives the LEDs — a perimeter spectrum,
a bass-driven pulse, bass waves and beat flashes, with adjustable
sensitivity.
- Night mode: one button makes everything warmer (3400K), dimmer (×0.6)
and smoother — on top of your current settings.
- Color pipeline: color temperature (white balance) and a shadow noise
cut-off so dark scenes don’t make the LEDs glow with noise.
- Auto-update: the app checks GitHub Releases, downloads the new binary and
restarts itself — no manual downloading; with “update automatically” enabled
new versions install silently at startup. After an update a “What’s new”
dialog shows the changelog (across several versions too), in the interface
language with a language switch right in the window.
- Plugin manager: a dedicated window with two views — Installed
(enable/disable, per-plugin settings, delete) and Catalog (search and
one-click install official/community plugins). Plugins are .py files with
create_plugin() in <config>/plugins/; a plugin can declare a
settings_schema and the manager builds its settings form automatically —
no GUI code needed. Docs and a template: docs/PLUGINS.md (ru),
examples/plugins/break_reminder.py.
- Notification flashes (built-in plugin): Telegram — a blue flash,
Discord — purple; the “any app” mode colors the flash from the sending
app’s icon. The flash is a “ripple” — a drop with a wave spreading along the
strip (a plain blob is also available). Position is set by dragging a spot
along the screen edge, works over any mode. Windows only sees system
notifications, so enable system notifications in the sending app (Telegram:
Settings → Notifications → use Windows notifications).
- Power guard (built-in plugin, off by default): estimates the strip current
from every frame — a WS2812B channel draws 20 mA at full, so a white LED is
60 mA — and dims the whole strip while it is over the power budget (500 mA for
USB by default, minus a safety headroom and the board’s own draw), never below
the minimum brightness you set. Dropping is instant, recovery is smooth, and
the status card shows the estimate live. Datasheet currents are hidden behind
“Show advanced settings”. It is a calculation, not a measurement — it lowers
the risk on bright scenes but does not replace an external power supply.
- Report a bug / idea: buttons in System open a prefilled GitHub issue with
diagnostics (version, OS, capture backend, LED count) attached.
- WLED transport (beta): an ESP strip running WLED over Wi-Fi
(UDP DRGB/DNRGB, port 21324) — no wire, no baud-rate cap.
- Modern UI: sidebar navigation with SVG icons, a status card
(state · backend · fps), live preview with the captured screen and sampling
zones — clicking an LED in the preview flashes it on the real strip;
dark / light / system theme.
- Language: Russian and English UI (System → Language); additional languages
install as locale plugins (a
create_locale() file), template in
examples/locales/en.py.
- First-run wizard: port → LEDs → side check in three steps
(also available anytime: System → “Setup wizard…”).
- Windows installer (
Adalight-Setup.exe): installs per-user (no admin),
start-menu/desktop shortcuts and optional autostart.
- Single-instance: launching the app again just raises the running window.
- Settings profiles: built-in presets 🎬 Movie / 🎮 Game / 💼 Work (layered
on top of your hardware settings) plus your own saved profiles; one-click
switching from the window or the tray menu.
- White balance: per-channel R/G/B multipliers to calibrate the strip.
- Import/export settings to a JSON file — backup and transfer between machines.
- Calibration test modes: color-per-side fill and a running-dot chase.
- Headless CLI for autostart setups; GUI and CLI share the same JSON config
(
%APPDATA%\adalight\config.json on Windows, ~/.config/adalight/ on Linux).
If FPS is low
- Baud rate is a hard cap: at 115200 the wire fits ~11.5 KB/s, i.e. ~76 fps
for 48 LEDs but only ~13 fps for 300. The app detects whatever the firmware is
flashed with, so raising it means reflashing
SERIAL_RATE (ESP boards handle
921600, classic Arduino 500000).
- The status bar shows which capture backend is actually running; on Windows
the fast path is
bettercam (a maintained dxcam fork), then dxcam, and if
both fall back to mss the reason is shown. The mss backend captures only
the edge bands rather than the full screen.
Download
Grab a binary from the latest release —
no Python required:
- Windows:
Adalight-Setup.exe (installer, recommended) or portable Adalight.exe
- Linux:
Adalight-linux-x86_64 (then chmod +x Adalight-linux-x86_64;
Wayland capture additionally needs wf-recorder installed)
Port permissions (Linux)
If you see “no access to the port” on start — install the bundled udev rule:
the app offers to install it with one button, or copy
packaging/99-adalight.rules to
/etc/udev/rules.d/ manually and run:
sudo udevadm control --reload-rules && sudo udevadm trigger
The rule grants access to the user of the graphical session via systemd-logind —
no group membership or re-login needed (replug the board over USB after
installing). Fallback for systems without logind: sudo usermod -aG dialout $USER (uucp group on Arch), then re-login.
Firmware
Your board (Arduino/ESP) needs an Adalight-compatible sketch. A ready-to-flash
reference sketch is in firmware/ — original author AlexGyver
(https://alexgyver.ru/arduino_ambilight/), included with attribution and
only lightly adapted. See firmware/README.md for flashing
and how the sketch settings map to the app.
Run from source
Requires uv:
uv sync
# GUI
uv run main.py
# headless & service modes
uv run main.py --live
uv run main.py --sides # test: top=red, right=green, bottom=blue, left=yellow
uv run main.py --chase # test: running dot
uv run main.py --off # turn the strip off
uv run main.py --list-monitors
uv run main.py --list-ports
uv run main.py --identify # which port is the board, what firmware, what baud
uv run main.py --self-test # the board runs the chase dot on its own
Calibration
- Start “Test: sides”: the top edge must light up red, right green,
bottom blue, left yellow. If sides are mixed up, adjust the start corner,
direction or mirroring — the preview in the window mirrors your changes live.
- If the hues are wrong (red shows as green etc.), change the channel order —
WS2812 is usually GRB.
- “Test: chase” runs a single bright dot along the strip to verify the
exact LED order; the first LED is marked with a ring in the preview.
- Not sure which port is your board? “Blink” on the Device tab makes the
strip breathe. To check the strip and board with the computer taken out of
the chain, use “Board self-test” — the firmware drives the dot itself.
Tech map
| Layer |
Module |
What it does |
| Capture |
capture/ |
Windows: bettercam → dxcam (DXGI, grab() polling), mss fallback; Wayland: wf-recorder / grim; mss grabs edge bands only |
| Geometry |
geometry.py |
LED layout around the perimeter, color-sampling zones |
| Engine |
engine.py |
Capture→process→send loop (Qt-free); live/lamp/music/test modes; schedule, adaptive and night brightness; live settings |
| Effects |
effects.py, audio.py |
Lamp (solid/gradient/rainbows/breathing), music (FFT + AGC over loopback audio via soundcard) |
| Device |
device.py |
Adalight protocol, channel order, LUT gamma, color temperature, shadow cut-off |
| Board probe |
probe.py |
Identity line, baud autodetect, breathing blink, board self-test |
| Frame filters |
pipeline.py |
Registry of per-frame hooks before sending — what Power guard runs on |
| GUI |
gui/ |
PySide6: tabs, live preview with zones, tray, themes, auto-update |
| Infra |
updates.py, autostart.py, CI |
GitHub Releases (auto-update), autostart (registry/XDG), exe+installer+linux binary built on v* tags |
Roadmap
Got an idea? Open an issue.
How it works
- Header
"Ada" + count_hi + count_lo + (hi^lo^0x55), then 3 bytes per LED —
the standard Adalight protocol, compatible with common Arduino sketches.
- Edge zones are precomputed per LED from the layout; each frame is averaged
per zone, exponentially smoothed and gamma-corrected through a precomputed
LUT (no per-frame
pow).
- The capture/processing loop is Qt-free (
adalight/engine.py); the GUI runs
it in a background thread, the CLI drives it directly.
adalight/
config.py # settings dataclass + JSON load/save
geometry.py # LED layout and capture zones
device.py # Adalight protocol, LUT gamma, channel order
engine.py # capture -> process -> send loop (no Qt)
pipeline.py # frame filters applied right before sending
capture/ # backends: dxcam (Windows), mss, wf-recorder, grim
cli.py # headless modes
gui/ # PySide6: main window, preview, tray
main.py # entry point: GUI without args, CLI otherwise
Releases
- CI (
.github/workflows/ci.yml): ruff + pytest on every push/PR.
- Release (
.github/workflows/release.yml): pushing a v* tag builds
Adalight.exe with PyInstaller on a Windows runner and publishes a GitHub
Release. The tag must match the version in pyproject.toml.
License
MIT
评论