Linux Input/HID development
 help / color / mirror / Atom feed
From: Bastien Nocera <hadess@hadess.net>
To: Elliot Douglas <edouglas7358@gmail.com>, linux-input@vger.kernel.org
Cc: lains@riseup.net, jikos@kernel.org, bentiss@kernel.org,
	 linux-kernel@vger.kernel.org
Subject: Re: [PATCH v3 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
Date: Thu, 13 Aug 2026 00:26:45 +0200	[thread overview]
Message-ID: <72ffd65c9bda6e22bb2939c4e07917c5eff20f51.camel@hadess.net> (raw)
In-Reply-To: <20260812195842.77724-2-edouglas7358@gmail.com>

On Wed, 2026-08-12 at 12:58 -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 arrays so adding support for
> another mouse does not change the evdev capabilities advertised by
> already-supported devices.
> 
> 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@gmail.com>

My earlier
Reviewed-by: Bastien Nocera <hadess@hadess.net>
was for both patches in the patch set :)

Thanks for v3.

> ---
>  drivers/hid/hid-logitech-hidpp.c | 205
> +++++++++++++++++++++++++++++++
>  1 file changed, 205 insertions(+)
> 
> diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-
> logitech-hidpp.c
> index 70ba1a5e40d8..f9189e14fb78 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
> @@ -178,6 +179,8 @@ struct hidpp_scroll_counter {
>  	unsigned long long last_time;
>  };
>  
> +struct hidpp_reprog_control_mapping;
> +
>  struct hidpp_device {
>  	struct hid_device *hid_dev;
>  	struct input_dev *input;
> @@ -205,6 +208,8 @@ struct hidpp_device {
>  	struct hidpp_scroll_counter vertical_wheel_counter;
>  
>  	u8 wireless_feature_index;
> +	u8 reprog_controls_feature_index;
> +	const struct hidpp_reprog_control_mapping *reprog_controls;
>  
>  	int hires_wheel_multiplier;
>  	u8 hires_wheel_feature_index;
> @@ -3601,6 +3606,195 @@ 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;
> +};
> +
> +static const struct hidpp_reprog_control_mapping *
> +hidpp20_reprog_controls_get_mappings(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_control_mapping *mapping;
> +	u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
> +		   HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
> +
> +	if (!(hidpp->quirks &
> HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
> +		return;
> +
> +	if (!hidpp->reprog_controls)
> +		return;
> +
> +	if (hidpp_root_get_feature(hidpp,
> HIDPP_PAGE_REPROG_CONTROLS_V4,
> +				   &hidpp-
> >reprog_controls_feature_index))
> +		return;
> +
> +	for (mapping = hidpp->reprog_controls; mapping->control;
> mapping++) {
> +		if (!hidpp20_reprog_controls_find_control(hidpp,
> mapping->control))
> +			continue;
> +
> +		hidpp20_reprog_controls_set_control_reporting(hidpp,
> +							     
> mapping->control,
> +							     
> flags);
> +	}
> +}
> +
> +static int hidpp20_reprog_controls_raw_event(struct hidpp_device
> *hidpp,
> +					     u8 *data, int size)
> +{
> +	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 ||
> +	    hidpp->reprog_controls_feature_index == 0xff)
> +		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 (mapping = hidpp->reprog_controls; mapping->control;
> mapping++) {
> +		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_control_mapping *mapping;
> +
> +	if (!hidpp->reprog_controls)
> +		return;
> +
> +	for (mapping = hidpp->reprog_controls; mapping->control;
> mapping++)
> +		input_set_capability(input_dev, EV_KEY, mapping-
> >code);
> +}
> +
>  static void hidpp10_extra_mouse_buttons_populate_input(
>  			struct hidpp_device *hidpp, struct input_dev
> *input_dev)
>  {
> @@ -3859,6 +4053,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 +4168,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 +4465,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 +4639,8 @@ 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;
> +	hidpp->reprog_controls =
> hidpp20_reprog_controls_get_mappings(hidpp);
>  	hid_set_drvdata(hdev, hidpp);
>  
>  	ret = hid_parse(hdev);

  parent reply	other threads:[~2026-08-12 22:26 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-12 19:58 [PATCH v3 0/2] HID: logitech-hidpp: fix Signature M650 side button timing Elliot Douglas
2026-08-12 19:58 ` [PATCH v3 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support Elliot Douglas
2026-08-12 20:13   ` sashiko-bot
2026-08-12 22:26   ` Bastien Nocera [this message]
2026-08-12 19:58 ` [PATCH v3 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650 Elliot Douglas
2026-08-12 20:14   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=72ffd65c9bda6e22bb2939c4e07917c5eff20f51.camel@hadess.net \
    --to=hadess@hadess.net \
    --cc=bentiss@kernel.org \
    --cc=edouglas7358@gmail.com \
    --cc=jikos@kernel.org \
    --cc=lains@riseup.net \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox