Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 58 additions & 32 deletions CONTRIBUTING_DEVICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,34 @@ Look at the `reprog_controls` array. Each entry has a `cid` (Control ID) and

Not all CIDs are divertable. Check the `flags` field -- if bit `0x0020` is
set, the control can be intercepted by Mouser.
Directional gesture mappings also require RawXY support (`0x0100` or
`0x0200`) and a successful RawXY divert during connection.

---

## 3. Add the device definition

### a) Edit `core/logi_devices.py`
### a) Add a device catalog entry

Add a new `LogiDeviceSpec` entry to the `KNOWN_LOGI_DEVICES` tuple:
For exact device support, edit `core/logi_device_catalog.py` first. This file
holds Mouser's community-maintained per-device Logitech entries, including the
device image and hotspot coordinates used by the UI.

Add a new dict to `LOGI_DEVICE_SPECS`:

```python
LogiDeviceSpec(
key="mx_ergo", # unique snake_case key
display_name="MX Ergo", # human-readable name
product_ids=(0xB0XX,), # from your dump's product_id
aliases=("Logitech MX Ergo",), # alternative names the device may report
ui_layout="generic_mouse", # or a custom layout key (see step 4)
image_asset="icons/mouse-simple.svg", # or a custom image (see step 4)
supported_buttons=GENERIC_BUTTONS, # adjust to match your mouse
gesture_cids=(0x00C3,), # from gesture_candidates in your dump
dpi_min=200,
dpi_max=4000, # from discovered DPI range, or Logitech specs
),
{
"key": "mx_ergo", # unique snake_case key
"display_name": "MX Ergo", # human-readable name
"product_ids": (0xB0XX,), # from your dump's product_id
"aliases": ("Logitech MX Ergo",), # alternative names the device may report
"ui_layout": "mx_ergo", # exact layout key
"image_asset": "logitech-mice/mx_ergo/mouse.png",
"supported_buttons": GENERIC_BUTTONS, # adjust to match your mouse
"gesture_cids": (0x00C3,), # from gesture_candidates in your dump
"dpi_min": 200,
"dpi_max": 4000, # from discovered DPI range, or vendor specs
},
```

Pick the right button tuple for `supported_buttons`:
Expand All @@ -80,43 +86,55 @@ Pick the right button tuple for `supported_buttons`:
- `GENERIC_BUTTONS` -- middle, back, forward (safe default)
- Or define a new tuple if your mouse has a unique button set.

### b) (Optional) Add an interactive layout
`supported_buttons` is a static fallback. When Mouser connects through HID++
and discovers `REPROG_V4` controls, it may narrow HID++-gated buttons such as
gesture, Smart Shift / mode shift, and DPI switch based on the runtime control
table. Unknown CIDs are intentionally not exposed until Mouser has code that
knows how to handle them. Horizontal scroll remains catalog-driven because
some devices implement it as OS events or side-button + wheel behavior instead
of a standalone reprogrammable control.

Use `core/logi_devices.py` only when you are adding a broader family fallback
without exact art yet.

### b) Add an exact interactive layout

If you want the mouse page to show an interactive diagram with clickable
hotspot dots:
hotspot dots, add a layout dict in `core/logi_device_catalog.py` instead of
growing `core/device_layouts.py`:

1. Create an image of your mouse (top-down PNG or SVG, ~400x350 px).
Place it in `images/`.
2. Add a layout dict in `core/device_layouts.py`:
1. Create a small image set for your mouse and place it in
`images/logitech-mice/<device-key>/`.
2. Add a layout dict to `LOGI_DEVICE_LAYOUTS`:

```python
MY_DEVICE_LAYOUT = {
"key": "my_device",
"label": "My Device family",
"image_asset": "mouse_my_device.svg",
"image_width": 400,
"image_height": 350,
"mx_ergo": {
"key": "mx_ergo",
"label": "MX Ergo",
"image_asset": "logitech-mice/mx_ergo/mouse.png",
"image_width": 260,
"image_height": 400,
"interactive": True,
"manual_selectable": True,
"manual_selectable": False,
"note": "",
"hotspots": [
{
"buttonKey": "middle", # must match a supported_buttons entry
"buttonKey": "middle", # must match a supported_buttons entry
"label": "Middle button",
"summaryType": "mapping", # "mapping", "gesture", or "hscroll"
"summaryType": "mapping", # "mapping", "gesture", or "hscroll"
"normX": 0.50, # 0-1, fraction of image width
"normY": 0.30, # 0-1, fraction of image height
"labelSide": "right", # "left" or "right"
"labelOffX": 150, # pixel offset for the annotation line
"labelOffY": -60,
},
# ... one entry per visible button
],
}
},
```

3. Register it in the `DEVICE_LAYOUTS` dict at the bottom of the file.
4. Set `ui_layout` in your `LogiDeviceSpec` to match the layout key.
`core/device_layouts.py` still owns shared manual family layouts such as
`mx_master`, `mx_anywhere`, and `mx_vertical`. Keep those family entries
manual-selectable; keep exact per-device layouts auto-detected only.

### Estimating hotspot coordinates

Expand All @@ -125,6 +143,14 @@ cursor X by image width and cursor Y by image height to get `normX`/`normY`.
The label offset values control where the annotation text appears relative to
the dot -- experiment with positive/negative values until it looks right.

### Keep it small

- Prefer focused, reviewable device entries over large multi-device changes.
- Keep image assets and hotspot data close to what the UI actually uses.
- Prefer exact per-device entries for hardware that has been checked in-app.
- If the device is only partially understood, add a family fallback first and
leave the exact layout for a follow-up contribution.

---

## 4. Test your changes
Expand Down
8 changes: 5 additions & 3 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ Intercepted events are either **blocked** (hook returns `1`) and replaced with a

### Device catalog & layout registry

- [`core/logi_devices.py`](core/logi_devices.py) resolves known product IDs and model aliases into a `ConnectedDeviceInfo` record with display name, DPI range, preferred gesture CIDs, and default UI layout key.
- [`core/device_layouts.py`](core/device_layouts.py) stores image assets, hotspot coordinates, layout notes, and whether a layout is interactive or only a generic fallback. `_FAMILY_FALLBACKS` maps per-model keys (`mx_master_4`, `mx_anywhere_3s`, …) to family layout keys until a dedicated overlay exists.
- [`core/logi_device_catalog.py`](core/logi_device_catalog.py) holds Mouser's curated per-device Logitech specs, image assets, and hotspot coordinates for dedicated control surfaces.
- [`core/logi_devices.py`](core/logi_devices.py) resolves known product IDs and model aliases into a `ConnectedDeviceInfo` record with display name, DPI range, preferred gesture CIDs, supported buttons, and default UI layout key.
- [`core/device_layouts.py`](core/device_layouts.py) stores built-in family layouts plus catalog layouts, layout notes, and whether a layout is interactive or only a generic fallback. `_FAMILY_FALLBACKS` maps per-model keys to family layout keys until a dedicated overlay exists.
- [`ui/backend.py`](ui/backend.py) combines auto-detected device info with any persisted per-device layout override and exposes the effective layout to QML.

### Gesture button detection
Expand Down Expand Up @@ -185,7 +186,7 @@ Two pages accessible from a slim sidebar in [`ui/qml/Main.qml`](ui/qml/Main.qml)
### Mouse & profiles

- **Left panel** — list of profiles. The "Default (All Apps)" profile is always present. Per-app profiles show the app icon and name. Selecting a profile binds it as the active editing target.
- **Right panel** — device-aware mouse view. MX Master family devices get clickable hotspot dots on the image; unsupported layouts fall back to a generic device card with an experimental "try another supported map" picker.
- **Right panel** — device-aware mouse view. MX Master and MX Anywhere family devices get clickable hotspot dots on the image; unsupported layouts fall back to a generic device card with an experimental "try another supported map" picker.
- **Add profile** — combo box at the bottom lists known apps (Chrome, Edge, VS Code, VLC, etc.). Click `+` to create a per-app profile.

### Point & scroll
Expand Down Expand Up @@ -225,6 +226,7 @@ mouser/
│ ├── key_simulator.py # Platform-specific action simulator
│ ├── linux_permissions.py # hidraw / event / uinput permission report
│ ├── log_setup.py # Rotating file log + stdout redirection
│ ├── logi_device_catalog.py # Curated Logitech specs, assets, and hotspots
│ ├── logi_devices.py # Known Logitech device catalog + connected-device metadata
│ ├── mouse_hook.py # Platform dispatcher façade
│ ├── mouse_hook_base.py # Shared base class
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ English | [中文文档](README_CN.md)

A lightweight, open-source, fully local alternative to **Logitech Options+** for
remapping Logitech HID++ mice. The current best experience is on the **MX Master**
family, with detection and fallback UI support for additional Logitech models.
and **MX Anywhere** families, with detection and fallback UI support for additional
Logitech models.

**No telemetry. No cloud. No Logitech account required.**

Expand Down Expand Up @@ -128,7 +129,7 @@ That's it. The app opens, drops a tray / menu-bar icon, and starts remapping imm
- **Bluetooth and Logi Bolt** — both transports are supported on all three platforms; the UI labels the live connection (`Logi Bolt` only when the receiver PID is positively identified).
- **Auto-reconnection** — Mouser watches for power-off / on cycles and rebinds HID++ + the OS mouse hook without a restart; SmartShift settings are replayed on every reconnect (including wake-from-sleep).
- **Live connection status** — real-time Connected / Not Connected badge, model name, and active layout in the UI.
- **Device-aware UI** — interactive MX Master diagram with clickable hotspots; generic fallback card for other models, with an experimental layout-override picker.
- **Device-aware UI** — interactive MX Master and MX Anywhere diagrams with clickable hotspots; generic fallback card for other models, with an experimental layout-override picker.

### Multi-language UI

Expand All @@ -148,12 +149,12 @@ That's it. The app opens, drops a tray / menu-bar icon, and starts remapping imm

| Family / model | Detection + HID++ probing | UI support |
|---|---|---|
| MX Master 4 / 3S / 3 / 2S / MX Master | Yes | Dedicated interactive `mx_master` layout |
| MX Anywhere 3S / 3 / 2S | Yes | Generic fallback card, experimental manual override |
| MX Master 4 / 3S / 3 / 2S / MX Master | Yes | Dedicated interactive per-model layouts |
| MX Anywhere 3S / 3 / 2S | Yes | Dedicated interactive per-model layouts |
| MX Vertical | Yes | Generic fallback card (with DPI switch button support) |
| Unknown Logitech HID++ mice | Best effort by PID/name | Generic fallback card |

> Only the MX Master family currently has a dedicated visual overlay. Other devices are still detected, show their model name, and can opt into an experimental layout override — button positions just may not line up until a real overlay lands. See [CONTRIBUTING_DEVICES.md](CONTRIBUTING_DEVICES.md) to add yours.
> MX Master and MX Anywhere devices have dedicated visual overlays. Other devices are still detected, show their model name, and can opt into an experimental layout override — button positions just may not line up until a real overlay lands. See [CONTRIBUTING_DEVICES.md](CONTRIBUTING_DEVICES.md) to add yours.

---

Expand Down Expand Up @@ -300,7 +301,7 @@ For project layout, the architecture diagram, the HID++ gesture detector, the En

## Roadmap

- [ ] **Dedicated overlays for more devices** — real hotspot maps and artwork for MX Anywhere, MX Vertical, and other Logitech families
- [ ] **Dedicated overlays for more devices** — real hotspot maps and artwork for MX Vertical and other Logitech families
- [ ] **True per-device config** — separate mappings cleanly when multiple Logitech mice are used on the same machine
- [ ] **Dynamic button inventory** — build button lists from discovered `REPROG_CONTROLS_V4` controls instead of the current fixed sets
- [ ] **Improved scroll inversion** — explore driver-level or interception-driver approaches
Expand Down
12 changes: 6 additions & 6 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

中文文档|[English README](README.md)

一个轻量、开源、**完全本地运行** 的 **Logitech Options+** 替代品,用于对罗技 HID++ 鼠标进行按键 / 手势重映射。当前对 **MX Master** 系列体验最佳,并对更多罗技型号提供识别与通用回退 UI。
一个轻量、开源、**完全本地运行** 的 **Logitech Options+** 替代品,用于对罗技 HID++ 鼠标进行按键 / 手势重映射。当前对 **MX Master** 与 **MX Anywhere** 系列体验最佳,并对更多罗技型号提供识别与通用回退 UI。

**零遥测,零云端,无需罗技账号。**

Expand Down Expand Up @@ -126,7 +126,7 @@
- **蓝牙与 Logi Bolt** — 三个平台都支持两种连接方式;UI 实时显示当前连接类型(仅在确认接收器 PID 时才显示 `Logi Bolt`)。
- **自动重连** — Mouser 监听断电 / 上电循环,无需重启即可重新绑定 HID++ 与系统鼠标 hook;每次重连(包括从睡眠唤醒)都会回放 SmartShift 设置。
- **实时连接状态** — UI 显示 Connected / Not Connected 徽标、设备型号和当前布局。
- **设备感知 UI** — MX Master 系列提供带可点击热区的交互示意图;其他型号使用通用回退卡片,并支持实验性的布局覆盖选择器。
- **设备感知 UI** — MX Master 与 MX Anywhere 系列提供带可点击热区的交互示意图;其他型号使用通用回退卡片,并支持实验性的布局覆盖选择器。

### 多语言 UI

Expand All @@ -146,12 +146,12 @@

| 系列 / 型号 | 识别 + HID++ 探测 | UI 支持 |
|---|---|---|
| MX Master 4 / 3S / 3 / 2S / MX Master | 是 | 专用交互布局 `mx_master` |
| MX Anywhere 3S / 3 / 2S | 是 | 通用回退卡片,支持实验性手动覆盖 |
| MX Master 4 / 3S / 3 / 2S / MX Master | 是 | 专用的逐型号交互布局 |
| MX Anywhere 3S / 3 / 2S | 是 | 专用的逐型号交互布局 |
| MX Vertical | 是 | 通用回退卡片(含 DPI 切换按键支持) |
| 其他罗技 HID++ 鼠标 | 按 PID / 名称尽力识别 | 通用回退卡片 |

> 目前只有 MX Master 系列拥有专用的可视化覆盖层。其他设备同样可被识别、显示型号名,并可启用实验性布局覆盖;但在专用覆盖层加入前,按键热区位置可能不够精确。要为你的设备添加支持,请见 [CONTRIBUTING_DEVICES.md](CONTRIBUTING_DEVICES.md)。
> MX Master 与 MX Anywhere 系列拥有专用的可视化覆盖层。其他设备同样可被识别、显示型号名,并可启用实验性布局覆盖;但在专用覆盖层加入前,按键热区位置可能不够精确。要为你的设备添加支持,请见 [CONTRIBUTING_DEVICES.md](CONTRIBUTING_DEVICES.md)。

---

Expand Down Expand Up @@ -298,7 +298,7 @@ pyinstaller Mouser-linux.spec --noconfirm

## 路线图

- [ ] **更多设备的专用覆盖层** — 为 MX Anywhere、MX Vertical 及其他罗技系列添加真实热区图与示意图素材
- [ ] **更多设备的专用覆盖层** — 为 MX Vertical 及其他罗技系列添加真实热区图与示意图素材
- [ ] **真正的每设备配置** — 当一台机器接入多只罗技鼠标时,干净地分离各自的映射
- [ ] **动态按键清单** — 基于发现的 `REPROG_CONTROLS_V4` 控件构建按键列表,而不是依赖当前的固定按键集合
- [ ] **更好的滚动反转** — 探索驱动级或拦截驱动方案
Expand Down
3 changes: 3 additions & 0 deletions core/device_layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from copy import deepcopy

from core.logi_device_catalog import LOGI_DEVICE_LAYOUTS


MX_MASTER_LAYOUT = {
"key": "mx_master",
Expand Down Expand Up @@ -211,6 +213,7 @@
"mx_anywhere": MX_ANYWHERE_LAYOUT,
"mx_vertical": MX_VERTICAL_LAYOUT,
"generic_mouse": GENERIC_MOUSE_LAYOUT,
**LOGI_DEVICE_LAYOUTS,
}

# Maps a device-specific key like "mx_master_3s" to its family layout key.
Expand Down
3 changes: 3 additions & 0 deletions core/hid_gesture.py
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,9 @@ def _direct_device_first(info):
transport=actual_transport,
source=source,
gesture_cids=self._gesture_candidates,
reprog_controls=controls,
active_gesture_cid=self._gesture_cid,
gesture_rawxy_enabled=self._rawxy_enabled,
)
return True
continue # divert failed — try next receiver slot
Expand Down
Loading
Loading