Skip to content

Commit e8a51fe

Browse files
authored
Merge pull request #218 from BanerjeeArjun/master
feat(catalog): add Logitech M650 Signature support
2 parents 4578ce4 + 77197e5 commit e8a51fe

2 files changed

Lines changed: 97 additions & 0 deletions

File tree

core/logi_device_catalog.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@
4242
"hscroll_right",
4343
)
4444

45+
# M650 Signature family: no horizontal scroll, no mode-shift, no dedicated gesture button.
46+
# Exposes a Virtual Gesture Button (CID 0x00D7) via REPROG_CONTROLS_V4 but no physical
47+
# gesture key. Middle click, back, and forward side buttons are the configurable controls.
48+
M650_BUTTONS = (
49+
"middle",
50+
"xbutton1",
51+
"xbutton2",
52+
)
53+
4554

4655
def _hotspot(
4756
button_key: str,
@@ -192,6 +201,36 @@ def _layout(
192201
"supported_buttons": MX_ANYWHERE_BUTTONS,
193202
"dpi_max": 4000,
194203
},
204+
# -- M650 Signature family ------------------------------------------------
205+
# Compact wireless mouse (middle, back, forward buttons). Connects via Logi
206+
# Bolt receiver or Bluetooth LE. HID++ reports device name "Signature M650".
207+
# Confirmed via live HID++ probe: REPROG_CONTROLS_V4 (slot 2 on Bolt receiver),
208+
# LOWRES_WHEEL, ADJUSTABLE_DPI (200–4000 DPI), UNIFIED_BATTERY.
209+
# Bluetooth LE product ID confirmed in issue #215 as 0xB02A; keep the
210+
# common HID/OS name variants as fallbacks because some platforms only
211+
# surface the product string.
212+
{
213+
"key": "m650",
214+
"display_name": "M650 Signature",
215+
"product_ids": (0xB02A,),
216+
"aliases": (
217+
"Signature M650",
218+
"Logi M650",
219+
"Logitech Signature M650",
220+
"M650",
221+
"M650 Signature",
222+
"Logitech M650 Signature",
223+
"M650 L",
224+
"M650 L Signature",
225+
"Signature M650 L",
226+
"M650 Signature for Business",
227+
),
228+
"ui_layout": "m650",
229+
"image_asset": "icons/mouse-simple.svg",
230+
"supported_buttons": M650_BUTTONS,
231+
"dpi_min": 200,
232+
"dpi_max": 4000,
233+
},
195234
# -- G502 family ----------------------------------------------------------
196235
# Product IDs verified against Solaar's device descriptors. Wireless
197236
# variants list both the wired USB PID and the Lightspeed receiver WPID.
@@ -259,6 +298,23 @@ def _layout(
259298

260299

261300
LOGI_DEVICE_LAYOUTS = {
301+
# M650 Signature: no device art yet; shows generic silhouette with the
302+
# three-button layout. Interactive hotspot diagram can be added once
303+
# mouse artwork is sourced and product_ids are confirmed.
304+
"m650": {
305+
"key": "m650",
306+
"label": "M650 Signature",
307+
"image_asset": "icons/mouse-simple.svg",
308+
"image_width": 220,
309+
"image_height": 220,
310+
"interactive": False,
311+
"manual_selectable": True,
312+
"note": (
313+
"M650 Signature — middle click, back, and forward side buttons "
314+
"are all configurable. No gesture button or horizontal scroll."
315+
),
316+
"hotspots": [],
317+
},
262318
# Shared placeholder for the G502 family: no device art has been
263319
# contributed yet, so the page shows the generic silhouette with the
264320
# G502 button list instead of an interactive hotspot diagram.

tests/test_logi_devices.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,47 @@ def test_resolve_mx_anywhere_3_promoted_pids(self):
8585
"logitech-mice/mx_anywhere_3/mouse.png",
8686
)
8787

88+
def test_resolve_m650_by_bluetooth_pid(self):
89+
device = resolve_device(product_id=0xB02A)
90+
91+
self.assertIsNotNone(device)
92+
self.assertEqual(device.key, "m650")
93+
self.assertEqual(device.ui_layout, "m650")
94+
95+
def test_resolve_m650_by_common_name_variants(self):
96+
for product_name in (
97+
"Signature M650",
98+
"Logi M650",
99+
"Logitech Signature M650",
100+
):
101+
with self.subTest(product_name=product_name):
102+
device = resolve_device(product_name=product_name)
103+
104+
self.assertIsNotNone(device)
105+
self.assertEqual(device.key, "m650")
106+
self.assertEqual(device.ui_layout, "m650")
107+
108+
def test_build_m650_connected_device_info_uses_catalog_layout_and_buttons(self):
109+
info = build_connected_device_info(
110+
product_id=0xB02A,
111+
product_name="Signature M650",
112+
reprog_controls=[
113+
{"cid": 0x0052},
114+
{"cid": 0x0053},
115+
{"cid": 0x0056},
116+
{"cid": 0x00D7},
117+
],
118+
gesture_cids=(0x00D7,),
119+
)
120+
121+
self.assertEqual(info.key, "m650")
122+
self.assertEqual(info.display_name, "M650 Signature")
123+
self.assertEqual(info.ui_layout, "m650")
124+
self.assertEqual(info.image_asset, "icons/mouse-simple.svg")
125+
self.assertEqual(info.supported_buttons, ("middle", "xbutton1", "xbutton2"))
126+
self.assertEqual(info.dpi_min, 200)
127+
self.assertEqual(info.dpi_max, 4000)
128+
88129
def test_mx_anywhere_3s_uses_exact_catalog_layout(self):
89130
info = build_connected_device_info(product_id=0xB037)
90131

0 commit comments

Comments
 (0)