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

From: Bastien Nocera

Date: Fri Jul 03 2026 - 09:09:08 EST


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
> <edouglas7358@xxxxxxxxx> wrote:
> >
> > 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 <hadess@xxxxxxxxxx>
> > wrote:
> > >
> > > 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, &params[0]);
> > > > +     params[2] = flags;
> > > > +     put_unaligned_be16(control, &params[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);