Metadata-Version: 2.4
Name: alienfx-leds
Version: 0.1.0
Summary: Keyboard and chassis lighting for Alienware and Dell machines on Linux: a confined D-Bus service and its command line
Keywords: alienware,dell,rgb,keyboard,lighting,linux,d-bus
Author: Zeecka
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: No Input/Output (Daemon)
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Dist: pygobject>=3.42
Requires-Python: >=3.10
Project-URL: Homepage, https://github.com/Zeecka/alienfx-leds
Project-URL: Issues, https://github.com/Zeecka/alienfx-leds/issues
Description-Content-Type: text/markdown

<div align="center">

<img src="data/icons/io.github.zeecka.AlienFix.svg" width="112" alt="AlienFX LEDs icon">

# AlienFX LEDs

**Keyboard and chassis lighting for Alienware and Dell machines on Linux.**<br>
Per-key colors, effects, a typing ripple, and a toggle in GNOME Quick Settings, all driven by a small, locked-down service.

[![CI](https://github.com/Zeecka/alienfx-leds/actions/workflows/ci.yml/badge.svg)](https://github.com/Zeecka/alienfx-leds/actions/workflows/ci.yml)
[![CodeQL](https://github.com/Zeecka/alienfx-leds/actions/workflows/codeql.yml/badge.svg)](https://github.com/Zeecka/alienfx-leds/actions/workflows/codeql.yml)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square)](https://github.com/astral-sh/ruff)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json&style=flat-square)](https://github.com/astral-sh/uv)<br>
[![License: GPL-3.0-or-later](https://img.shields.io/badge/license-GPL--3.0--or--later-blue?style=flat-square)](LICENSE)
![Linux](https://img.shields.io/badge/platform-Linux-FCC624?style=flat-square&logo=linux&logoColor=black)
![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776AB?style=flat-square&logo=python&logoColor=white)
![GTK 4 + libadwaita](https://img.shields.io/badge/GTK%204-libadwaita-4A86CF?style=flat-square&logo=gnome&logoColor=white)
![GNOME 46](https://img.shields.io/badge/GNOME%20Shell-46-4A86CF?style=flat-square&logo=gnome&logoColor=white)<br>
![Verified: Alienware m15 R7](https://img.shields.io/badge/verified-Alienware%20m15%20R7-2ea44f?style=flat-square)
![49 community models](https://img.shields.io/badge/community%20models-49-orange?style=flat-square)
![No raw device access](https://img.shields.io/badge/raw%20HID%20access-none-brightgreen?style=flat-square)

[Features](#-features) ·
[Supported machines](#-supported-machines) ·
[Install](#-install) ·
[Usage](#-usage) ·
[Security](#-security-model) ·
[Development](#-development)

<img src="docs/app-keyboard.png" width="88%" alt="The Keyboard page on an Alienware m15 R7: every key drawn to scale, lit as a left-to-right rainbow">

</div>

## ✨ Features

| | |
|---|---|
| ⌨️ **Per-key colors** | The keyboard drawn to scale from its real geometry. Click or Ctrl/Shift-click keys, pick a color, apply. |
| 🌈 **Effects** | 5 firmware effects (breathing, wave, pulse, two-color pulse, sweep) plus 5 rendered by the service (rainbow, spectrum, color morph, starlight, gradient). One effect can go to any mix of keyboard and chassis lights. |
| 💡 **Chassis lights** | Power button, lid logo, rear light strip: static color, pulse, morph. The power button keeps its color asleep and off. |
| 💧 **Typing ripple** | A ring of color from every key you press, over the current effect or a plain color. Adjustable color and speed (2 to 40 keys per second). |
| 🎛️ **Quick Settings** | On/off, brightness, presets and the ripple, straight from the GNOME panel. |
| 🖥️ **Command line** | `alienfix` does everything the app does, for scripts. |
| 🧭 **Any model** | Unknown machine? A generic model is built from the controllers found, and the *“does it blink?”* wizard maps a per-key keyboard in 3 to 5 minutes. |
| 💾 **Persistent** | Settings survive reboots and are re-applied after suspend. |

<table>
<tr>
<td width="50%"><img src="docs/app-effects.png" alt="Effects page: one effect applied to several lighting groups"><br><sub><b>Effects</b>: one effect, applied to any mix of lights.</sub></td>
<td width="50%"><img src="docs/app-ripple.png" alt="Ripple page: enable switch, color, speed and what shows under the ripple"><br><sub><b>Ripple</b>: color, speed and what shows underneath.</sub></td>
</tr>
<tr>
<td width="50%"><img src="docs/app-chassis.png" alt="Chassis page: power button, lid logo and the two rear-strip channels"><br><sub><b>Chassis</b>: power button, lid logo, rear strip.</sub></td>
<td width="50%"><img src="docs/app-machine.png" alt="Machine page: detected model, lighting map source, key map wizard"><br><sub><b>Machine</b>: detected model, key map, wizard.</sub></td>
</tr>
</table>

## 💻 Supported machines

| Status | Machines |
|---|---|
| 🟢 **Verified**<br><sub>checked with a camera, key by key and zone by zone</sub> | **Alienware m15 R7**: per-key keyboard, power button, lid logo, rear strip (two channels). See [docs/m15-r7.md](docs/m15-r7.md). |
| 🟠 **Reported**<br><sub>community mappings from other projects, not verified here</sub> | **49 models**: m15 R1, R3, R4, R6, m16, m17, m18, x17, Area-51m, Aurora R12, Dell G15 5510–5535, G5, G7, and 2010–2017 machines (M11x, M14x, M17x, M18x, 13, 15, 17, Area-51, Aurora R4). Full list: [docs/supported-models.md](docs/supported-models.md). |
| ⚪ **Anything else with these controllers** | Detected by its USB HID descriptor. The service builds a generic model with neutral zone names, and a per-key keyboard can be mapped with the blink wizard. |

> [!NOTE]
> **Reported** models may have wrong zone names or indices; the app says so next to every unverified zone.
> Whether yours works or not, please open an issue with the output of `alienfix models` and `alienfix zones`,
> or send a model file: see [docs/adding-a-model.md](docs/adding-a-model.md).

<details>
<summary><b>Controllers</b></summary>

| Controller | USB | What it drives |
|---|---|---|
| AW-ELC (“API v4”) | `187c:0550`, `187c:0551` | Chassis lights and 4-zone keyboards: static color, pulse, morph. Power buttons are programmed for every power state. |
| Darfon per-key keyboard (“API v5”) | `0d62:*`, FEATURE report `0xCC` | Per-key colors, 5 firmware effects, 5 effects rendered by the service, hardware dimmer, live mode, typing ripple. |
| Legacy AlienFX (“API v2/v3”) | `187c:0511`..`0530` | Zone masks, static color. |
| Kernel `alienware-wmi` | `rgb_zones` sysfs | Pre-2018 desktops (X51, Alpha): static color. |

</details>

<p align="center">
<img src="docs/app-zone-keyboard.png" width="60%" alt="A Dell G15 with a 4-zone keyboard">
<br><sub>A Dell G15: its 4-zone keyboard is listed as zones.</sub>
</p>

## 📦 Install

**1. Dependencies** (Python 3, PyGObject, GTK 4, libadwaita). On Ubuntu 24.04:

```sh
sudo apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1
```

**2. Install** the service, the command line, the app and the GNOME extension:

```sh
./install.sh        # asks for sudo
```

**3. Enable the Quick Settings toggle** (GNOME 46). On X11, reload the shell with <kbd>Alt</kbd>+<kbd>F2</kbd>, `r`, <kbd>Enter</kbd>; on Wayland, log out and back in. Then:

```sh
gnome-extensions enable alienfix@zeecka.github.io
```

To remove everything, `./uninstall.sh` deletes exactly what `install.sh` installed (it keeps a manifest).

## 🚀 Usage

### The app

Open **AlienFX LEDs** from the GNOME menu. The pages follow your machine:

- **Keyboard**: the per-key drawing to scale, or the keyboard zones.
- **Effects**: one effect applied to any mix of lighting groups.
- **Chassis**: one color per zone, applied at once.
- **Ripple**: a ring of color from each key typed on the built-in keyboard, in any window. Color, speed and what shows underneath (the current colors or effect, or a plain color) are saved by the service.
- **Machine**: the detected model, the choice of model, key map import/export and the key map wizard.

The header bar holds the master switch and the brightness slider.

> [!TIP]
> While the ripple is on, firmware effects cannot run under per-key frames, so the service redraws them in software.
> These copies are close but not identical: only the wave period was measured.

### Quick Settings

The **Lighting** tile turns everything on or off. Its menu has the brightness slider, effect presets, the **Typing ripple** switch and a shortcut to the app.

### Command line

```sh
alienfix status | layout | zones | models
alienfix color '#0050ff'                         # whole keyboard
alienfix keys ESC=#ff0000 Q=#00ff00              # per key (names from `alienfix layout`)
alienfix effect wave --tempo 5 '#ff0000' '#0000ff'
alienfix zone lid pulse --tempo 6 '#ff0000'      # zone ids from `alienfix zones`
alienfix model dell-g15-5520 | auto
alienfix brightness 60 | on | off
```

Tempo goes from 1 to 30; higher is slower.

## 🔒 Security model

> [!IMPORTANT]
> The per-key keyboard controller **is** the internal keyboard: its hidraw node carries every keystroke.
> Opening it to the desktop (a udev rule, a group, an ACL) would hand every desktop process a keylogger.
> AlienFX LEDs never does that.

- **One owner.** Only `alienfixd` opens the hidraw nodes. No udev rule opens them to users.
- **Confined service.** `alienfixd` runs as root but with **no capabilities**: empty `CapabilityBoundingSet=`, `DevicePolicy=closed` (hidraw, plus input devices read-only for the ripple), `ProtectSystem=strict`, `PrivateNetwork=yes`, `SystemCallFilter=@system-service`.
- **Narrow API.** The D-Bus interface only has lighting verbs, with no raw report pass-through. Every argument is re-parsed, bounded and rebuilt before it reaches a device ([`validate.py`](src/alienfix/validate.py)). The interface is described in [docs/dbus-api.md](docs/dbus-api.md).
- **polkit.** The active local session may change the lighting. Remote and inactive sessions may not.

> [!WARNING]
> **About the typing ripple.** It is **off by default**, and switching it on goes through polkit like any change.
> While it is on, the service reads key presses of the **built-in keyboard only**: the lighting keyboard's own USB device and the i8042 keyboard.
> External keyboards are never opened, and keys are not intercepted. A key press only becomes a position for the ring:
> it is not stored, logged or sent. When the ripple or the lighting is off, the input devices are closed.

## 🔬 How it was built

The Alienware m15 R7 support was reverse-engineered with a webcam pointed at the keyboard, because a successful command proves nothing about an LED. The full method, dead ends included, is in the write-up on the author's blog.

The other models come from the community mappings of
[alienfx-tools](https://github.com/T-Troll/alienfx-tools) (MIT),
[akbl](https://github.com/rsm-gh/akbl) (GPL-3.0),
[OpenRGB](https://gitlab.com/CalcProgrammer1/OpenRGB) (GPL-2.0) and
[AWCC](https://github.com/tr1xem/AWCC) (GPL-3.0).
Only facts were taken from them (USB ids, light indices and masks, zone names), and [`tools/import_models.py`](tools/import_models.py) records the exact source of each model. Thanks to their authors and contributors. 🙏

## 🛠️ Development

The project uses [uv](https://docs.astral.sh/uv/). PyGObject needs its build headers
(`sudo apt install libgirepository-2.0-dev libcairo2-dev pkg-config python3-dev`), or skip it with
`uv sync --no-install-package pygobject --no-install-package pycairo`: only the service and the app need it, not the tests.

```sh
uv sync                                                     # dev tools: ruff, ty, pylint
uv run python -m unittest discover -s tests                 # validation, encoders, models, ripple
uv run ruff check . && uv run ruff format --check .         # lint and format
uv run ty check                                             # types
uv run pylint src tests tools bin/alienfix bin/alienfixd gui/alienfx-leds

MOCK_MODEL=dell-g15-5520 python3 tests/mock_daemon.py &     # the real service, fake hardware, session bus
ALIENFIX_BUS=session gui/alienfx-leds                       # the app against it
```

Every push and pull request runs these checks in [CI](.github/workflows/ci.yml), plus ShellCheck,
actionlint, zizmor, the GNOME extension and the data files; [CodeQL](.github/workflows/codeql.yml)
scans the Python and JavaScript code weekly. Dependabot keeps the actions and tools up to date.

<details>
<summary><b>Releasing</b></summary>

1. Bump `version` in `pyproject.toml` and `__version__` in `src/alienfix/__init__.py`, commit.
2. Tag and push: `git tag v0.2.0 && git push origin v0.2.0`.

The [release workflow](.github/workflows/release.yml) then builds the sdist and wheel, creates the
GitHub release, attaches a signed [SLSA level 3](https://slsa.dev) provenance (`alienfx-leds.intoto.jsonl`),
and publishes to PyPI with Trusted Publishing (no token) and PEP 740 attestations. Verify a download with
[slsa-verifier](https://github.com/slsa-framework/slsa-verifier):

```sh
slsa-verifier verify-artifact alienfx_leds-0.2.0-py3-none-any.whl \
  --provenance-path alienfx-leds.intoto.jsonl --source-uri github.com/Zeecka/alienfx-leds --source-tag v0.2.0
```

One-time setup: on PyPI, add a trusted publisher for `alienfx-leds` (repository `Zeecka/alienfx-leds`,
workflow `release.yml`, environment `pypi`), and create the `pypi` environment in the repository settings.
The provenance goes to the public Rekor transparency log, so the generator only runs on a public repository.

</details>

<details>
<summary><b>Repository layout</b></summary>

| Path | What |
|---|---|
| [`src/alienfix/`](src/alienfix) | The service: D-Bus API, validation, report encoders, effects, models, key maps |
| [`gui/alienfx-leds`](gui/alienfx-leds) | The GTK 4 / libadwaita app |
| [`bin/alienfix`](bin/alienfix) | The command line |
| [`gnome-extension/`](gnome-extension) | The Quick Settings toggle |
| [`data/models/`](data/models) | One file per machine model |
| [`data/chassis/`](data/chassis), [`data/profiles/`](data/profiles) | Keyboard geometry, and the shipped key maps |
| [`tests/`](tests) | Unit tests and the mock service |
| [`.github/`](.github) | CI, CodeQL, release (SLSA, PyPI), Dependabot |

</details>

## 📄 License

[GPL-3.0-or-later](LICENSE).
