Re: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
From: Bastien Nocera
Date: Wed Jun 17 2026 - 06:29:22 EST
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.
>
> 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 <edouglas7358@xxxxxxxxx>
> ---
> 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?
> + 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);