From: Elliot Douglas <edouglas7358@gmail.com>
To: linux-input@vger.kernel.org
Cc: lains@riseup.net, hadess@hadess.net, jikos@kernel.org,
bentiss@kernel.org, linux-kernel@vger.kernel.org,
edouglas7358@gmail.com
Subject: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
Date: Sat, 13 Jun 2026 10:51:08 -0700 [thread overview]
Message-ID: <20260613175109.44365-2-edouglas7358@gmail.com> (raw)
In-Reply-To: <20260613175109.44365-1-edouglas7358@gmail.com>
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.
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>
---
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;
+ 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);
+ 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);
--
2.54.0
next prev parent reply other threads:[~2026-06-13 17:51 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-13 17:51 [PATCH 0/2] HID: logitech-hidpp: fix Signature M650 side button timing Elliot Douglas
2026-06-13 17:51 ` Elliot Douglas [this message]
2026-06-17 10:28 ` [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support Bastien Nocera
2026-06-18 1:16 ` Elliot Douglas
2026-07-01 22:32 ` Elliot Douglas
2026-07-03 12:53 ` Bastien Nocera
2026-06-13 17:51 ` [PATCH 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650 Elliot Douglas
2026-06-17 10:28 ` Bastien Nocera
2026-07-04 23:10 ` [PATCH v2 0/2] HID: logitech-hidpp: fix Signature M650 side button timing Elliot Douglas
2026-07-04 23:10 ` [PATCH v2 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support Elliot Douglas
2026-07-04 23:23 ` sashiko-bot
2026-07-04 23:10 ` [PATCH v2 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650 Elliot Douglas
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=20260613175109.44365-2-edouglas7358@gmail.com \
--to=edouglas7358@gmail.com \
--cc=bentiss@kernel.org \
--cc=hadess@hadess.net \
--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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.