Thread (12 messages) 12 messages, 3 authors, 2d ago

Re: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

From: Bastien Nocera <hadess@hadess.net>
Date: 2026-07-03 12:53:46
Also in: lkml

On Wed, 2026-07-01 at 15:32 -0700, Elliot Douglas wrote:
Just wanted to poke on this thread again, Benjamin or Bastien, what
is needed to
push this forward or should I send the v2 at this point?
You're definitely better off sending a new version when there's no
feedback incoming, it acts as a gentle reminder and avoids another back
and forth if there's no further comments (apart from the ones in the
original review).
Thanks,
Elliot

On Wed, Jun 17, 2026 at 6:16 PM Elliot Douglas
[off-list ref] wrote:
quoted
Thanks, that makes sense.

For Solaar, this is not continuously forced. The kernel only
programs
temporary diversion when the device connects. Solaar can still
issue HID++
commands through hidraw, so if Solaar changes reporting for the
same controls
afterwards, the last writer wins.

If Solaar takes over those controls for custom actions, the kernel
would stop
receiving the diverted button notifications for normal evdev
reporting until
the kernel diverts the controls again, for example after reconnect.
While the
controls remain diverted, hidraw clients should still receive the
raw HID++
reports.

I have addressed the inline comments locally for v2:
- replaced the profile/count wrapper with NULL-terminated mapping
arrays
- cached the selected mapping pointer in struct hidpp_device

I'll wait for Benjamin's input to send Patch v2.


On Wed, Jun 17, 2026 at 3:28 AM Bastien Nocera [off-list ref]
wrote:
quoted
On Sat, 2026-06-13 at 10:51 -0700, Elliot Douglas wrote:
quoted
Some Logitech HID++ 2.0 mice can report diverted reprogrammable
controls through HID++ feature 0x1b04, SpecialKeysMseButtons /
REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Add a quirk-gated event path for those controls. The handler
temporarily
diverts verified per-product controls, parses
divertedButtonsEvent as the
current pressed-control list, and reports the corresponding
evdev key state
for every mapped control.

Keep the control mappings in per-product profiles so adding
support for
another mouse does not change the evdev capabilities advertised
by
already-supported devices.
How does this forced setting work/clash with the programmable
buttons
in Solaar?

I've added some inline comments below.
quoted
Documentation for feature 0x1b04 describes divertedButtonsEvent
as a list
of currently pressed diverted buttons, which is the event
format handled
here.

Link:
https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html

Signed-off-by: Elliot Douglas <redacted>
---
 drivers/hid/hid-logitech-hidpp.c | 215
+++++++++++++++++++++++++++++++
 1 file changed, 215 insertions(+)
diff --git a/drivers/hid/hid-logitech-hidpp.c
b/drivers/hid/hid-logitech-hidpp.c
index 70ba1a5e40d8..24c9cfaa4f37 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_QUIRK_HI_RES_SCROLL_1P0                BIT(28)
 #define HIDPP_QUIRK_WIRELESS_STATUS          BIT(29)
 #define HIDPP_QUIRK_RESET_HI_RES_SCROLL              BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS       BIT(31)

 /* These are just aliases for now */
 #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -205,6 +206,7 @@ struct hidpp_device {
      struct hidpp_scroll_counter vertical_wheel_counter;

      u8 wireless_feature_index;
+     u8 reprog_controls_feature_index;

      int hires_wheel_multiplier;
      u8 hires_wheel_feature_index;
@@ -3601,6 +3603,209 @@ static int
hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device
*hidpp,
      return 1;
 }

+/* -----------------------------------------------------------
--------------- */
+/* HID++2.0 reprogrammable
controls                                           */
+/* -----------------------------------------------------------
--------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4                       
0x1b04
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT                     
0x00
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO           0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING  0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE             BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT            BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED     BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT       
BIT(1)
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED         0x00
+
+struct hidpp_reprog_control_mapping {
+     u16 control;
+     u16 code;
+};
+
+struct hidpp_reprog_controls_profile {
+     const struct hidpp_reprog_control_mapping *mappings;
probably needs a __counted_by(), or maybe as it's static, it
might be
better to not require an intermediate struct, and return a NULL-
terminated array instead.
quoted
+     unsigned int mapping_count;
+};
+
+static const struct hidpp_reprog_controls_profile *
+hidpp20_reprog_controls_get_profile(struct hidpp_device
*hidpp)
+{
+     return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct
hidpp_device *hidpp)
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index,
cmd, NULL, 0,
+                                       &response);
+     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;
+
+     return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct
hidpp_device *hidpp,
+                                             u8 index, u16
*control,
+                                             u8 *flags)
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index,
cmd, &index,
+                                       sizeof(index),
&response);
+     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;
+
+     *control = get_unaligned_be16(&response.fap.params[0]);
+     *flags = response.fap.params[4];
+
+     return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct
hidpp_device *hidpp,
+                                              u16 control)
+{
+     int count, ret;
+     u16 cid;
+     u8 flags;
+     int i;
+
+     count = hidpp20_reprog_controls_get_count(hidpp);
+     if (count < 0)
+             return false;
+
+     for (i = 0; i < count; i++) {
+             ret = hidpp20_reprog_controls_get_cid_info(hidpp,
i, &cid,
+                                                       
&flags);
+             if (ret)
+                     return false;
+
+             if (cid == control)
+                     return (flags &
HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+                            (flags &
HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+     }
+
+     return false;
+}
+
+static int
hidpp20_reprog_controls_set_control_reporting(struct
hidpp_device *hidpp,
+                                                      u16
control, u8 flags)
+{
+     struct hidpp_report response;
+     u8 params[5];
+
+     put_unaligned_be16(control, &params[0]);
+     params[2] = flags;
+     put_unaligned_be16(control, &params[3]);
+
+     return hidpp_send_fap_command_sync(hidpp,
+                                        hidpp-
quoted
reprog_controls_feature_index,
+                                       
HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+                                        params,
sizeof(params), &response);
+}
+
+static void hidpp20_reprog_controls_connect(struct
hidpp_device *hidpp)
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+                HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+     unsigned int i;
+
+     if (!(hidpp->quirks &
HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+             return;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
Could the profile be cached in the hidpp_device struct?
quoted
+     if (!profile)
+             return;
+
+     if (hidpp_root_get_feature(hidpp,
HIDPP_PAGE_REPROG_CONTROLS_V4,
+                                &hidpp-
quoted
reprog_controls_feature_index))
+             return;
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             u16 control = profile->mappings[i].control;
+
+             if (!hidpp20_reprog_controls_find_control(hidpp,
control))
+                     continue;
+
+            
hidpp20_reprog_controls_set_control_reporting(hidpp, control,
flags);
+     }
+}
+
+static int hidpp20_reprog_controls_raw_event(struct
hidpp_device *hidpp,
+                                          u8 *data, int size)
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     const struct hidpp_reprog_control_mapping *mapping;
+     struct hidpp_report *report = (struct hidpp_report
*)data;
+     u16 controls[4];
+     bool pressed;
+     unsigned int i, j;
+
+     if (!(hidpp->quirks &
HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+         !hidpp->input ||
+         hidpp->reprog_controls_feature_index == 0xff)
+             return 0;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return 0;
+
+     if (size < HIDPP_REPORT_LONG_LENGTH ||
+         report->fap.feature_index != hidpp-
quoted
reprog_controls_feature_index ||
+         report->fap.funcindex_clientid !=
HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+             return 0;
+
+     for (i = 0; i < ARRAY_SIZE(controls); i++)
+             controls[i] = get_unaligned_be16(&report-
quoted
fap.params[i * 2]);
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             mapping = &profile->mappings[i];
+             pressed = false;
+
+             for (j = 0; j < ARRAY_SIZE(controls); j++) {
+                     if (controls[j] == mapping->control) {
+                             pressed = true;
+                             break;
+                     }
+             }
+
+             input_report_key(hidpp->input, mapping->code,
pressed);
+     }
+
+     input_sync(hidpp->input);
+
+     return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct
hidpp_device *hidpp,
+                                                struct
input_dev *input_dev)
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     unsigned int i;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return;
+
+     for (i = 0; i < profile->mapping_count; i++)
+             input_set_capability(input_dev, EV_KEY, profile-
quoted
mappings[i].code);
+}
+
 static void hidpp10_extra_mouse_buttons_populate_input(
                      struct hidpp_device *hidpp, struct
input_dev *input_dev)
 {
@@ -3859,6 +4064,9 @@ static void hidpp_populate_input(struct
hidpp_device *hidpp,

      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
              hidpp10_extra_mouse_buttons_populate_input(hidpp,
input);
+
+     if (hidpp->quirks &
HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+             hidpp20_reprog_controls_populate_input(hidpp,
input);
 }

 static int hidpp_input_configured(struct hid_device *hdev,
@@ -3971,6 +4179,10 @@ static int hidpp_raw_hidpp_event(struct
hidpp_device *hidpp, u8 *data,
                      return ret;
      }

+     ret = hidpp20_reprog_controls_raw_event(hidpp, data,
size);
+     if (ret != 0)
+             return ret;
+
      if (hidpp->quirks &
HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_raw_event(hidpp,
data, size);
              if (ret != 0)
@@ -4264,6 +4476,8 @@ static void hidpp_connect_event(struct
work_struct *work)
                      return;
      }

+     hidpp20_reprog_controls_connect(hidpp);
+
      if (hidpp->quirks &
HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_connect(hidpp);
              if (ret)
@@ -4436,6 +4650,7 @@ static int hidpp_probe(struct hid_device
*hdev, const struct hid_device_id *id)
      hidpp->hid_dev = hdev;
      hidpp->name = hdev->name;
      hidpp->quirks = id->driver_data;
+     hidpp->reprog_controls_feature_index = 0xff;
      hid_set_drvdata(hdev, hidpp);

      ret = hid_parse(hdev);
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help