Re: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
From: Bastien Nocera <hadess@hadess.net>
Date: 2026-06-17 10:28:36
Also in:
lkml
On Sat, 2026-06-13 at 10:51 -0700, Elliot Douglas wrote:
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 hunk ↗ jump to hunk
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.
+ 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, ¶ms[0]);
+ params[2] = flags;
+ put_unaligned_be16(control, ¶ms[3]);
+
+ return hidpp_send_fap_command_sync(hidpp,
+ hidpp->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 hunk ↗ jump to hunk
+ if (!profile) + return; + + if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4, + &hidpp->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->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->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->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);