diff --git a/src/libhidpp/CMakeLists.txt b/src/libhidpp/CMakeLists.txt
index c096cb1..10e2fa2 100644
--- a/src/libhidpp/CMakeLists.txt
+++ b/src/libhidpp/CMakeLists.txt
@@ -55,6 +55,7 @@ set(LIBHIDPP_SOURCES
hidpp20/ITouchpadRawXY.cpp
hidpp20/ILEDControl.cpp
hidpp20/IBatteryLevelStatus.cpp
+ hidpp20/IIllumination.cpp
hidpp20/ProfileDirectoryFormat.cpp
hidpp20/ProfileFormat.cpp
hidpp20/MemoryMapping.cpp
diff --git a/src/libhidpp/hidpp20/IIllumination.cpp b/src/libhidpp/hidpp20/IIllumination.cpp
new file mode 100644
index 0000000..7b8b944
--- /dev/null
+++ b/src/libhidpp/hidpp20/IIllumination.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2024 Bastien Nocera
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include
+#include
+
+#include
+
+#include
+
+using namespace HIDPP20;
+
+constexpr uint16_t IIllumination::ID;
+
+IIllumination::IIllumination (Device *dev):
+ FeatureInterface (dev, ID, "Illumination")
+{
+}
+
+bool IIllumination::getIllumination(void)
+{
+ std::vector params (16), results;
+ results = call (GetIllumination, params);
+ return readLE (results, 0) != 0;
+}
+
+void IIllumination::setIllumination(bool state)
+{
+ std::vector params (16);
+ writeLE (params, 0, state);
+ call (SetIllumination, params);
+}
+
+IIllumination::Info IIllumination::getBrightnessInfo(void)
+{
+ std::vector params (16), results;
+ results = call (GetBrightnessInfo, params);
+ return Info {
+ (uint8_t)(readLE (results, 0) & 0x0f), // capabilities
+ readBE (results, 1), // min
+ readBE (results, 3), // max
+ readBE (results, 5), // res
+ (uint8_t)(readLE (results, 7) & 0x0f), // maxLevels
+ };
+}
+
+uint16_t IIllumination::getBrightness(void)
+{
+ uint16_t value;
+ std::vector params (16), results;
+ results = call (GetBrightness, params);
+ value = readBE (results, 0);
+ return value;
+}
+
+uint16_t IIllumination::getBrightnessEffectiveMax(void)
+{
+ uint16_t value;
+ std::vector params (16), results;
+ try {
+ results = call (GetBrightnessEffectiveMax, params);
+ value = readBE (results, 0);
+ }
+ catch (HIDPP20::Error &e) {
+ if (e.errorCode () == HIDPP20::Error::InvalidFunctionID)
+ value = 0;
+ else throw e;
+ }
+ return value;
+}
+
+void IIllumination::setBrightness(uint16_t value)
+{
+ std::vector params (16);
+ writeBE (params, 0, value);
+ call (SetBrightness, params);
+}
+
+IIllumination::Info IIllumination::getColorTemperatureInfo(void)
+{
+ std::vector params (16), results;
+ results = call (GetColorTemperatureInfo, params);
+ return Info {
+ (uint8_t)(readLE (results, 0) & 0x0f), // capabilities
+ readBE (results, 1), // min
+ readBE (results, 3), // max
+ readBE (results, 5), // res
+ (uint8_t)(readLE (results, 7) & 0x0f), // maxLevels
+ };
+}
+
+uint16_t IIllumination::getColorTemperature(void)
+{
+ uint16_t value;
+ std::vector params (16), results;
+ results = call (GetColorTemperature, params);
+ value = readBE (results, 0);
+ return value;
+}
+
+void IIllumination::setColorTemperature(uint16_t value)
+{
+ std::vector params (16);
+ writeBE (params, 0, value);
+ call (SetColorTemperature, params);
+}
+
+bool IIllumination::illuminationChangeEvent (const HIDPP::Report &event)
+{
+ assert (event.function () == IIllumination::IlluminationChangeEvent);
+ auto params = event.parameterBegin ();
+ return readLE (params) != 0;
+}
+
+uint16_t IIllumination::brightnessChangeEvent (const HIDPP::Report &event)
+{
+ assert (event.function () == IIllumination::BrightnessChangeEvent);
+ auto params = event.parameterBegin ();
+ return readBE (params);
+}
+
+uint16_t IIllumination::colorTemperatureChangeEvent (const HIDPP::Report &event)
+{
+ assert (event.function () == IIllumination::ColorTemperatureChangeEvent);
+ auto params = event.parameterBegin ();
+ return readBE (params);
+}
+
+uint16_t IIllumination::brightnessEffectiveMaxChangeEvent (const HIDPP::Report &event)
+{
+ assert (event.function () == IIllumination::BrightnessEffectiveMaxChangeEvent);
+ auto params = event.parameterBegin ();
+ return readBE (params);
+}
+
+uint16_t IIllumination::brightnessClampedEvent (const HIDPP::Report &event)
+{
+ assert (event.function () == IIllumination::BrightnessClampedEvent);
+ auto params = event.parameterBegin ();
+ return readBE (params);
+}
diff --git a/src/libhidpp/hidpp20/IIllumination.h b/src/libhidpp/hidpp20/IIllumination.h
new file mode 100644
index 0000000..2789847
--- /dev/null
+++ b/src/libhidpp/hidpp20/IIllumination.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2024 Bastien Nocera
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifndef LIBHIDPP_HIDPP20_IILLUMINATION_H
+#define LIBHIDPP_HIDPP20_IILLUMINATION_H
+
+#include
+
+namespace HIDPP20
+{
+
+/**
+ * Control non-RGB LED features.
+ */
+class IIllumination: public FeatureInterface
+{
+public:
+ static constexpr uint16_t ID = 0x1990;
+
+ enum Function {
+ GetIllumination = 0,
+ SetIllumination = 1,
+ GetBrightnessInfo = 2,
+ GetBrightness = 3,
+ SetBrightness = 4,
+ GetBrightnessLevels = 5,
+ SetBrightnessLevels = 6,
+ GetColorTemperatureInfo = 7,
+ GetColorTemperature = 8,
+ SetColorTemperature = 9,
+ GetColorTemperatureLevels = 10,
+ SetColorTemperatureLevels = 11,
+ GetBrightnessEffectiveMax = 12,
+ };
+
+ enum Event {
+ IlluminationChangeEvent = 0,
+ BrightnessChangeEvent = 1,
+ ColorTemperatureChangeEvent = 2,
+ BrightnessEffectiveMaxChangeEvent = 3,
+ BrightnessClampedEvent = 4,
+ };
+
+ enum Flags {
+ hasEvents = 1 << 0,
+ hasLinearLevels = 1 << 1,
+ hasNonLinearLevels = 1 << 2,
+ hasDynamicMaximum = 1 << 3,
+ };
+
+ struct Info {
+ uint8_t flags;
+ uint16_t min;
+ uint16_t max;
+ uint16_t res;
+ unsigned int maxLevels : 4;
+ };
+
+ IIllumination (Device *dev);
+
+ /**
+ * Get the current Illumination state.
+ */
+ bool getIllumination(void);
+
+ /**
+ * Set the current Illumination state.
+ */
+ void setIllumination(bool state);
+
+ /**
+ * Get information about brightness.
+ */
+ Info getBrightnessInfo(void);
+
+ /**
+ * Get the current Illumination brightness.
+ */
+ uint16_t getBrightness(void);
+
+ /**
+ * Get the maximum brightness based on hardware limits,
+ * 0 means the max value from getBrightnessInfo().
+ */
+ uint16_t getBrightnessEffectiveMax(void);
+
+ /**
+ * Set the Illumination brightness.
+ */
+ void setBrightness(uint16_t value);
+
+ /**
+ * Get information about color temperature.
+ */
+ Info getColorTemperatureInfo(void);
+
+ /**
+ * Get the current Illumination color temperature.
+ */
+ uint16_t getColorTemperature(void);
+
+ /**
+ * Set the Illumination color temperature.
+ */
+ void setColorTemperature(uint16_t value);
+
+ /**
+ * Parse an illumination change event.
+ */
+ static bool illuminationChangeEvent (const HIDPP::Report &event);
+
+ /**
+ * Parse a brightness change event.
+ */
+ static uint16_t brightnessChangeEvent (const HIDPP::Report &event);
+
+ /**
+ * Parse a color temperature change event.
+ */
+ static uint16_t colorTemperatureChangeEvent (const HIDPP::Report &event);
+
+ /**
+ * Parse a change in the effective maximum brightness.
+ */
+ static uint16_t brightnessEffectiveMaxChangeEvent (const HIDPP::Report &event);
+
+ /**
+ * Parse a notification of a recent request to set the
+ * brightness to a value larger than the current effective
+ * maximum brightness.
+ */
+ static uint16_t brightnessClampedEvent (const HIDPP::Report &event);
+};
+
+}
+
+#endif
+
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index ffda9de..b7c18fa 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -32,6 +32,7 @@ set(TOOLS
hidpp20-onboard-profiles-get-description
hidpp20-reprog-controls
hidpp20-led-control
+ hidpp20-illumination-light-control
hidpp20-dump-page
hidpp20-write-page
hidpp20-write-data
diff --git a/src/tools/hidpp-list-features.cpp b/src/tools/hidpp-list-features.cpp
index 8d36e97..658d370 100644
--- a/src/tools/hidpp-list-features.cpp
+++ b/src/tools/hidpp-list-features.cpp
@@ -59,6 +59,7 @@ static const std::map HIDPP20Features = {
{ 0x1981, "Backlight" },
{ 0x1982, "Backlight" },
{ 0x1983, "Backlight" },
+ { 0x1990, "Illumination Light control" },
{ 0x1a00, "Presenter control" },
{ 0x1a01, "3D sensor" },
{ 0x1b00, "Reprog controls" },
diff --git a/src/tools/hidpp20-illumination-light-control.cpp b/src/tools/hidpp20-illumination-light-control.cpp
new file mode 100644
index 0000000..0a2fdfe
--- /dev/null
+++ b/src/tools/hidpp20-illumination-light-control.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2024 Bastien Nocera
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "common/common.h"
+#include "common/Option.h"
+#include "common/CommonOptions.h"
+#include "common/EventQueue.h"
+
+extern "C" {
+#include
+#include
+#include
+}
+
+using namespace HIDPP20;
+
+class EventHandler
+{
+public:
+ virtual const HIDPP20::FeatureInterface *feature () const = 0;
+ virtual void handleEvent (const HIDPP::Report &event) = 0;
+};
+
+class IlluminationEventHandler: public EventHandler
+{
+ HIDPP20::IIllumination _ill;
+ bool _state;
+ uint16_t _brightness;
+ uint16_t _temperature;
+ uint16_t _eff_max;
+public:
+ IlluminationEventHandler (HIDPP20::Device *dev):
+ _ill (dev),
+ _state (_ill.getIllumination ()),
+ _brightness (_ill.getBrightness()),
+ _temperature (_ill.getColorTemperature()),
+ _eff_max (_ill.getBrightnessEffectiveMax())
+ {
+ printf ("Light is %s\n", _state ? "on" : "off");
+ }
+
+ const HIDPP20::FeatureInterface *feature () const
+ {
+ return &_ill;
+ }
+
+ void handleEvent (const HIDPP::Report &event)
+ {
+ bool new_state;
+ uint16_t value;
+
+ switch (event.function ()) {
+ case IIllumination::IlluminationChangeEvent:
+ new_state = IIllumination::illuminationChangeEvent (event);
+ if (new_state != _state) {
+ printf ("Light turned %s\n", new_state ? "on" : "off");
+ _state = new_state;
+ }
+ break;
+ case IIllumination::BrightnessChangeEvent:
+ value = IIllumination::brightnessChangeEvent (event);
+ if (value != _brightness) {
+ printf ("Brightness changed from %u to %u\n", _brightness, value);
+ _brightness = value;
+ }
+ break;
+ case IIllumination::ColorTemperatureChangeEvent:
+ value = IIllumination::colorTemperatureChangeEvent (event);
+ if (value != _temperature) {
+ printf ("Color temperature changed from %u to %u\n", _temperature, value);
+ _temperature = value;
+ }
+ break;
+ case IIllumination::BrightnessEffectiveMaxChangeEvent:
+ value = IIllumination::brightnessEffectiveMaxChangeEvent (event);
+ if (value != _eff_max) {
+ printf ("Effective max brightness changed from %u to %u\n", _eff_max, value);
+ _eff_max = value;
+ }
+ break;
+ case IIllumination::BrightnessClampedEvent:
+ value = IIllumination::brightnessClampedEvent (event);
+ printf ("Brightness clamped to %u\n", value);
+ break;
+ }
+ }
+};
+
+class EventListener
+{
+ HIDPP::Dispatcher *dispatcher;
+ HIDPP::DeviceIndex index;
+ std::map> handlers;
+ std::map iterators;
+public:
+ EventListener (HIDPP::Dispatcher *dispatcher, HIDPP::DeviceIndex index):
+ dispatcher (dispatcher),
+ index (index)
+ {
+ }
+
+ virtual ~EventListener ()
+ {
+ removeEventHandlers ();
+ }
+
+ virtual void addEventHandler (std::unique_ptr &&handler)
+ {
+ uint8_t feature = handler->feature ()->index ();
+ EventHandler *ptr = handler.get ();
+ handlers.emplace (feature, std::move (handler));
+ auto it = dispatcher->registerEventHandler (index, feature, [ptr] (const HIDPP::Report &report) {
+ ptr->handleEvent (report);
+ return true;
+ });
+ iterators.emplace (feature, it);
+ }
+
+ virtual void removeEventHandlers ()
+ {
+ for (const auto &p: iterators)
+ dispatcher->unregisterEventHandler (p.second);
+ handlers.clear ();
+ iterators.clear ();
+ }
+
+ virtual void start () = 0;
+ virtual void stop () = 0;
+
+protected:
+ virtual bool event (EventHandler *handler, const HIDPP::Report &report) = 0;
+};
+
+class SimpleListener: public EventListener
+{
+ HIDPP::SimpleDispatcher *dispatcher;
+public:
+ SimpleListener (HIDPP::SimpleDispatcher *dispatcher, HIDPP::DeviceIndex index):
+ EventListener (dispatcher, index),
+ dispatcher (dispatcher)
+ {
+ }
+
+ virtual void start ()
+ {
+ dispatcher->listen ();
+ }
+
+ virtual void stop ()
+ {
+ dispatcher->stop ();
+ }
+
+protected:
+ virtual bool event (EventHandler *handler, const HIDPP::Report &report)
+ {
+ handler->handleEvent (report);
+ return true;
+ }
+};
+
+EventListener *listener;
+
+void sigint (int)
+{
+ listener->stop ();
+}
+
+
+
+int main (int argc, char *argv[])
+{
+ static const char *args = "device_path state|toggle|brightness|temp [params...]";
+ HIDPP::DeviceIndex device_index = HIDPP::DefaultDevice;
+
+ std::vector