m693ctl 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. m693ctl-1.3.0/LICENSE +28 -0
  2. m693ctl-1.3.0/PKG-INFO +371 -0
  3. m693ctl-1.3.0/README.md +347 -0
  4. m693ctl-1.3.0/m693/__init__.py +6 -0
  5. m693ctl-1.3.0/m693/cli.py +690 -0
  6. m693ctl-1.3.0/m693/device.py +431 -0
  7. m693ctl-1.3.0/m693/keys.py +415 -0
  8. m693ctl-1.3.0/m693/lighting.py +321 -0
  9. m693ctl-1.3.0/m693/macro.py +371 -0
  10. m693ctl-1.3.0/m693/presets.py +93 -0
  11. m693ctl-1.3.0/m693/profile.py +539 -0
  12. m693ctl-1.3.0/m693/stock-profile.bin +0 -0
  13. m693ctl-1.3.0/m693/transfer.py +103 -0
  14. m693ctl-1.3.0/m693ctl.egg-info/PKG-INFO +371 -0
  15. m693ctl-1.3.0/m693ctl.egg-info/SOURCES.txt +53 -0
  16. m693ctl-1.3.0/m693ctl.egg-info/dependency_links.txt +1 -0
  17. m693ctl-1.3.0/m693ctl.egg-info/entry_points.txt +5 -0
  18. m693ctl-1.3.0/m693ctl.egg-info/requires.txt +3 -0
  19. m693ctl-1.3.0/m693ctl.egg-info/top_level.txt +2 -0
  20. m693ctl-1.3.0/m693gui/__init__.py +4 -0
  21. m693ctl-1.3.0/m693gui/app.py +310 -0
  22. m693ctl-1.3.0/m693gui/artwork.py +45 -0
  23. m693ctl-1.3.0/m693gui/assets/arrow-down.svg +3 -0
  24. m693ctl-1.3.0/m693gui/assets/m693.svg +8 -0
  25. m693ctl-1.3.0/m693gui/assets/theme.qss +237 -0
  26. m693ctl-1.3.0/m693gui/backend.py +101 -0
  27. m693ctl-1.3.0/m693gui/library.py +91 -0
  28. m693ctl-1.3.0/m693gui/model.py +406 -0
  29. m693ctl-1.3.0/m693gui/tabs/__init__.py +0 -0
  30. m693ctl-1.3.0/m693gui/tabs/dpi.py +254 -0
  31. m693ctl-1.3.0/m693gui/tabs/keys.py +388 -0
  32. m693ctl-1.3.0/m693gui/tabs/lighting.py +456 -0
  33. m693ctl-1.3.0/m693gui/tabs/macro.py +467 -0
  34. m693ctl-1.3.0/m693gui/tabs/placeholder.py +72 -0
  35. m693ctl-1.3.0/m693gui/widgets/__init__.py +0 -0
  36. m693ctl-1.3.0/m693gui/widgets/chrome.py +185 -0
  37. m693ctl-1.3.0/m693gui/widgets/controls.py +157 -0
  38. m693ctl-1.3.0/m693gui/widgets/lighting.py +323 -0
  39. m693ctl-1.3.0/m693gui/widgets/mouseview.py +136 -0
  40. m693ctl-1.3.0/m693gui/widgets/recorder.py +134 -0
  41. m693ctl-1.3.0/m693gui/worker.py +232 -0
  42. m693ctl-1.3.0/pyproject.toml +59 -0
  43. m693ctl-1.3.0/setup.cfg +4 -0
  44. m693ctl-1.3.0/tests/test_battery.py +233 -0
  45. m693ctl-1.3.0/tests/test_dpi_tab.py +141 -0
  46. m693ctl-1.3.0/tests/test_inspect_installer.py +91 -0
  47. m693ctl-1.3.0/tests/test_keys_tab.py +218 -0
  48. m693ctl-1.3.0/tests/test_lighting_tab.py +193 -0
  49. m693ctl-1.3.0/tests/test_macro_tab.py +309 -0
  50. m693ctl-1.3.0/tests/test_model.py +179 -0
  51. m693ctl-1.3.0/tests/test_pairing_and_cleanup.py +284 -0
  52. m693ctl-1.3.0/tests/test_retry.py +150 -0
  53. m693ctl-1.3.0/tests/test_shell.py +196 -0
  54. m693ctl-1.3.0/tests/test_unknowns.py +128 -0
  55. m693ctl-1.3.0/tests/test_zones.py +410 -0
m693ctl-1.3.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Raj singh chauhan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ The Redragon name, the M693 product artwork, and the contents of the Windows
26
+ installer are the property of their respective owners. None of it is included
27
+ in this repository: tools/extract_assets.py copies the mouse renders out of a
28
+ copy of the installer that you supply yourself.
m693ctl-1.3.0/PKG-INFO ADDED
@@ -0,0 +1,371 @@
1
+ Metadata-Version: 2.4
2
+ Name: m693ctl
3
+ Version: 1.3.0
4
+ Summary: Userspace driver and configuration GUI for the Redragon M693-RGB mouse
5
+ Author: Raj Singh Chauhan
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/rajchauhan28/redragon-mouse-linux
8
+ Project-URL: Repository, https://github.com/rajchauhan28/redragon-mouse-linux
9
+ Project-URL: Issues, https://github.com/rajchauhan28/redragon-mouse-linux/issues
10
+ Project-URL: Protocol, https://github.com/rajchauhan28/redragon-mouse-linux/blob/master/PROTOCOL.md
11
+ Keywords: redragon,m693,gaming-mouse,linux,hidraw,areson,compx,dpi,rgb,wayland
12
+ Classifier: Environment :: Console
13
+ Classifier: Environment :: X11 Applications :: Qt
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: gui
22
+ Requires-Dist: PySide6>=6.5; extra == "gui"
23
+ Dynamic: license-file
24
+
25
+ # m693ctl — Redragon M693-RGB on Linux
26
+
27
+ [![CI](https://github.com/rajchauhan28/redragon-mouse-linux/actions/workflows/ci.yml/badge.svg)](https://github.com/rajchauhan28/redragon-mouse-linux/actions/workflows/ci.yml)
28
+ [![Release](https://img.shields.io/github/v/release/rajchauhan28/redragon-mouse-linux)](https://github.com/rajchauhan28/redragon-mouse-linux/releases)
29
+ [![PyPI](https://img.shields.io/pypi/v/m693ctl)](https://pypi.org/project/m693ctl/)
30
+ [![License: MIT](https://img.shields.io/github/license/rajchauhan28/redragon-mouse-linux)](https://github.com/rajchauhan28/redragon-mouse-linux/blob/master/LICENSE)
31
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B%20(zero%20deps)-blue)](https://github.com/rajchauhan28/redragon-mouse-linux/blob/master/pyproject.toml)
32
+
33
+ > **Disclaimer:** This project is not from the actual Redragon company. Use it at your own risk. This is an open-source project created only to make life easier for Redragon mouse users on Linux. For other mouse types, I'll be happy to discuss further if anyone with another Redragon mouse is willing to participate in testing.
34
+
35
+ <img width="1860" height="1195" alt="screenshot_20260925_000601" src="https://github.com/user-attachments/assets/1741298d-3df3-442f-bdd2-e0054c6f812c" />
36
+ <img width="1857" height="1191" alt="screenshot_20260925_000611" src="https://github.com/user-attachments/assets/ba84686b-25f2-47a2-9bd8-7f6d23ebc52c" />
37
+ <img width="1860" height="1200" alt="screenshot_20260925_000622" src="https://github.com/user-attachments/assets/7c06d9f3-7f34-440a-b7b5-358986093ff0" />
38
+ <img width="1857" height="1193" alt="screenshot_20260925_000629" src="https://github.com/user-attachments/assets/10156695-a7d2-43c2-a8d6-e6d0e838dafa" />
39
+
40
+
41
+
42
+ A userspace driver for the Redragon M693-RGB gaming mouse, reimplementing what
43
+ the Windows-only `OemDrv.exe` configuration tool does. No kernel module, no
44
+ Wine — it speaks the vendor HID protocol directly over `hidraw`.
45
+
46
+ See [PROTOCOL.md](https://github.com/rajchauhan28/redragon-mouse-linux/blob/master/PROTOCOL.md) for how the protocol was recovered and what
47
+ every byte of the mouse's configuration memory means.
48
+
49
+ Pure Python 3.9+, no third-party dependencies.
50
+
51
+ ## Install
52
+
53
+ On Arch and derivatives (EndeavourOS, CachyOS, Manjaro), build the package — or grab `m693ctl-*.pkg.tar.zst` from [Releases](https://github.com/rajchauhan28/redragon-mouse-linux/releases):
54
+
55
+ ```sh
56
+ makepkg -si # driver, CLI, GUI, udev rule, desktop entry
57
+ ```
58
+
59
+ `pyside6` is an optional dependency, so `m693ctl` works on a machine without
60
+ Qt; install it if you want `m693-gui`.
61
+
62
+ On Ubuntu, Fedora, Debian, openSUSE, NixOS, or any other distro:
63
+
64
+ ```sh
65
+ tools/install.sh # udev rule, input group, desktop entry
66
+ pipx install '.[gui]' # or: pip install --user '.[gui]'
67
+ ```
68
+
69
+ Or install directly with `pipx` (then drop [`99-redragon-m693.rules`](https://github.com/rajchauhan28/redragon-mouse-linux/blob/master/99-redragon-m693.rules) into `/etc/udev/rules.d/`):
70
+
71
+ ```sh
72
+ pipx install 'm693ctl[gui]' # from PyPI (use plain m693ctl for CLI-only)
73
+ # or latest master from GitHub:
74
+ pipx install 'git+https://github.com/rajchauhan28/redragon-mouse-linux.git#egg=m693ctl[gui]'
75
+ ```
76
+
77
+ Re-plug the receiver afterwards, then verify with:
78
+
79
+ ```sh
80
+ m693ctl list # or ./m693ctl list, straight from a checkout
81
+ ```
82
+
83
+ `install.sh` only does three things and prints each of them, so read it first if
84
+ you would rather do them by hand: it copies `99-redragon-m693.rules` into
85
+ `/etc/udev/rules.d/`, adds you to the `input` group, and drops a `.desktop`
86
+ entry and icon under `~/.local/share`.
87
+
88
+ Everything works from a checkout without installing anything — `./m693ctl` and
89
+ `./m693-gui` are runnable in place. The driver and CLI have **no dependencies**;
90
+ PySide6 is needed only for the GUI, which is why it is an extra.
91
+
92
+ ## Usage
93
+
94
+ ```sh
95
+ ./m693ctl info # everything the mouse currently holds
96
+ ./m693ctl battery # charge, and whether it is charging
97
+ ./m693ctl battery -q # just the number, for a status bar
98
+ ./m693ctl pair # re-pair the mouse with its 2.4G USB receiver
99
+
100
+ ./m693ctl rate 1000 # polling rate: 125 / 250 / 500 / 1000 Hz
101
+ ./m693ctl dpi 800 1600 3200 # replace the DPI stages
102
+ ./m693ctl dpi 1600x800 # independent X/Y
103
+ ./m693ctl dpi --active 2 # switch to stage 2
104
+ ./m693ctl dpi --color 1 00ff00 # stage 1 indicator colour
105
+
106
+ ./m693ctl led --mode breathing --brightness 200 --speed 5 --color ff0080
107
+ ./m693ctl led --zone wheel --mode steady --color 00ff00 # just the wheel
108
+ ./m693ctl led --zone all --mode streaming --speed 8 # both zones
109
+
110
+ ./m693ctl preset list # built-in and saved lighting looks
111
+ ./m693ctl preset apply Ember
112
+ ./m693ctl preset save "my look" # captures every zone as it stands now
113
+ ./m693ctl set --debounce 4 --lod 2 --angle-snap off
114
+ ./m693ctl set --motion-sync on --ripple off
115
+ ./m693ctl set --dormancy 300 # sleep timer, seconds
116
+
117
+ ./m693ctl button 5 dpi-loop # remap a button slot
118
+ ./m693ctl button 5 mute # any multimedia or web function
119
+ ./m693ctl button 5 key:ctrl+shift+t # a keyboard shortcut
120
+ ./m693ctl button 5 fire:10,3 # fire key: 10 ms apart, 3 shots
121
+ ./m693ctl button 5 dpi-lock:2 # hold at DPI stage 3 while pressed
122
+ ./m693ctl button 5 disable
123
+
124
+ ./m693ctl export my-profile.json # everything, readable and restorable
125
+ ./m693ctl import my-profile.json
126
+
127
+ ./m693ctl macro 9 # what a button's macro contains
128
+ ./m693ctl macro 9 --load gg.jmm --repeat 1
129
+ ./m693ctl macro 9 --clear
130
+ ```
131
+
132
+ `button` with an unknown action prints the whole catalogue. Slots are 0-based;
133
+ `info` lists what each currently holds.
134
+
135
+ LED modes: `steady`, `breathing`, `streaming`, `neon`, `single-color-flow`,
136
+ `colorful-breathing`, `off`. The cycling modes (`streaming`, `neon`,
137
+ `colorful-breathing`) ignore the stored colour, so set a static mode as well if
138
+ you want `--color` to be visible.
139
+
140
+ ### Back up before experimenting
141
+
142
+ ```sh
143
+ ./m693ctl dump -o m693-backup.bin # 192 bytes of configuration memory
144
+ ./m693ctl restore m693-backup.bin
145
+ ```
146
+
147
+ `dump` with no `-o` hex-dumps instead, and `--addr` / `--length` let you look
148
+ anywhere (e.g. `--addr 0x100` for the macro area). `raw` writes arbitrary bytes
149
+ without adding checksums, for protocol work.
150
+
151
+ ### Waybar / status-bar battery module
152
+
153
+ Because the mouse does not expose a standard HID Battery Service, UPower and
154
+ desktop applets never list it. Poll `m693ctl --wait 0 battery -q` (`--wait 0`
155
+ exits immediately while the mouse is asleep instead of blocking your bar):
156
+
157
+ ```jsonc
158
+ "custom/mouse-battery": {
159
+ "exec": "p=$(m693ctl --wait 0 battery -q 2>/dev/null) && echo \"󰍽 ${p}%\"",
160
+ "interval": 60,
161
+ "tooltip": false
162
+ }
163
+ ```
164
+
165
+ ## Graphical interface
166
+
167
+ ```sh
168
+ ./m693-gui # against the real mouse
169
+ ./m693-gui --fake # simulated mouse, no hardware needed
170
+ ./m693-gui --native-decorations # use your WM's title bar
171
+ ```
172
+
173
+ It is fully keyboard-drivable, which matters more here than in most settings
174
+ dialogs: this is the window you use when a remap has gone wrong and the mouse
175
+ is the one input device you cannot trust.
176
+
177
+ | Key | Action |
178
+ | --- | --- |
179
+ | `Ctrl+1` … `Ctrl+4` | jump to a section |
180
+ | `Ctrl+Tab` / `Ctrl+Shift+Tab`, or ← → on the nav bar | next/previous section |
181
+ | `Tab` | next control; focus is always visibly ringed |
182
+ | `Ctrl+R` | re-read everything from the mouse |
183
+ | `F5` | wake a sleeping mouse |
184
+ | `Ctrl+E` / `Ctrl+I` | export / import a profile |
185
+ | `Ctrl+W`, `Ctrl+Q` | quit |
186
+
187
+ Optionally pull in the mouse renders used for the button callouts and the
188
+ lighting preview. They are Redragon's artwork and are not shipped with this
189
+ repository, so supply your own copy of the installer:
190
+
191
+ ```sh
192
+ tools/extract_assets.py "REDRAGON M693-RGB Setup ….exe"
193
+ ```
194
+
195
+ They go into the checkout if you are working in one, otherwise into
196
+ `~/.local/share/m693/device/` — which is where they need to be for an
197
+ installed package, since site-packages belongs to root.
198
+
199
+ **Status: complete.** All four tabs — **Key Settings**, **DPI Settings**,
200
+ **Macro** and **Lighting** — are implemented, and the app is packaged and
201
+ keyboard-navigable. See [PLAN.md](https://github.com/rajchauhan28/redragon-mouse-linux/blob/master/PLAN.md).
202
+
203
+ The Lighting tab has two sync modes, as radio buttons: **Side strips + palm
204
+ logo** leaves the wheel with its own effect and the drop-down picks which zone
205
+ you are editing; **All three zones together** drives the lot from one set of
206
+ controls. There is no third option because the strips and the logo are one
207
+ channel — they are always in step, whichever mode you are in. Presets store every zone's
208
+ effect, colour, brightness and speed under a name in
209
+ `~/.config/m693/presets.json`, shared with `m693ctl preset`. There is no
210
+ programmable effect engine on this mouse and no live-lighting command, so a
211
+ custom animation would mean writing to its configuration flash tens of times a
212
+ second; presets combine the seven firmware effects instead, and cost nothing to
213
+ leave applied.
214
+
215
+ The Macro tab keeps a library in `~/.config/m693/macros.json`, records
216
+ keystrokes with their real timing, and lets you add mouse-button and
217
+ multimedia steps by hand. Macros exported from the Windows tool import
218
+ directly: `.jmm` is a documented format now.
219
+
220
+ Key Settings gives each of the seven buttons the stock tool exposes a full
221
+ function menu — mouse buttons, DPI actions, multimedia and web keys, keyboard
222
+ shortcuts, fire keys, DPI lock — with the numbered callouts landing on the
223
+ leader lines already drawn in Redragon's own render. Profiles export to JSON
224
+ and import back.
225
+
226
+ The DPI sliders snap to the sensor's real ladder — 100 DPI steps to 4000, 200
227
+ above — so the number on screen is always a value the mouse can actually store.
228
+ The stock Windows tool lets you pick values it then silently quantises.
229
+
230
+ Edits never write straight through to the mouse. They mutate an in-memory copy
231
+ of its configuration, and a 250 ms timer merges the changed checksummed groups
232
+ into as few write commands as the protocol allows, on a background thread.
233
+ Dragging a slider therefore costs one write, not one per pixel. If the mouse
234
+ falls asleep mid-edit the changes are held and applied when it wakes.
235
+
236
+ ```sh
237
+ python3 tests/test_model.py # no hardware required
238
+ python3 tests/test_dpi_tab.py
239
+ python3 tests/test_lighting_tab.py
240
+ python3 tests/test_keys_tab.py
241
+ python3 tests/test_shell.py
242
+ python3 tests/test_macro_tab.py
243
+ python3 tests/test_unknowns.py
244
+ python3 tests/test_battery.py # protocol half runs without Qt
245
+ python3 tests/test_pairing_and_cleanup.py
246
+ python3 tests/test_inspect_installer.py
247
+ ```
248
+
249
+ ## As a library
250
+
251
+ ```python
252
+ from m693 import Device, Profile
253
+ from m693 import profile as P
254
+
255
+ with Device() as dev:
256
+ dev.set_driver_status(True)
257
+ dev.wait_online()
258
+
259
+ prof = Profile.read_from(dev)
260
+ print(prof.polling_rate, prof.dpi_stages, prof.dpi_colors)
261
+
262
+ P.set_dpi_stages(dev, [400, 800, 1600, 3200])
263
+ P.set_led_color(dev, (0xFF, 0x00, 0x80))
264
+ ```
265
+
266
+ ## Other wireless Redragon mice (`25a7:faXX`)
267
+
268
+ Many wireless Redragon mice — including the M913 Impact Elite, M810 Pro, M686
269
+ Vampire Elite, M724, and M612 Pro — are built on the same Compx / Areson
270
+ `25a7:faXX` platform and ship the same `OemDrv.exe` + `Cfg.ini` Windows
271
+ configuration tool.
272
+
273
+ If you own another wireless Redragon mouse and want Linux support:
274
+
275
+ 1. Check your USB IDs (plug in both the 2.4 GHz receiver and USB-C cable if possible):
276
+ ```sh
277
+ lsusb -d 25a7:
278
+ ```
279
+ 2. Extract its capability table from Redragon's Windows `.exe` installer (no Windows needed, only `innoextract`):
280
+ ```sh
281
+ python3 tools/inspect_installer.py "REDRAGON_Mxxx_Setup.exe"
282
+ ```
283
+ 3. Open a [**Device Support** issue](https://github.com/rajchauhan28/redragon-mouse-linux/issues/new?template=new-mouse-model.yml) with both outputs. Do **not** run write commands against an untested model before backing up its flash.
284
+
285
+ ## Notes and caveats
286
+
287
+ * **Changes apply instantly** and persist across re-plugs — the settings live
288
+ in the mouse, not in a background daemon. There is no apply step.
289
+ * **A sleeping mouse ignores configuration commands.** It dozes off within
290
+ about a minute of being left alone. `m693ctl` handles this itself: it waits
291
+ for the mouse indefinitely, resumes a command that was interrupted part-way,
292
+ and says so on stderr while it waits. `--wait SECONDS` puts a limit on that,
293
+ and `--wait 0` fails immediately instead. Only silence is retried — an
294
+ explicit refusal from the device is reported straight away.
295
+
296
+ Re-announcing the driver usually wakes the mouse on its own, so in practice
297
+ the wait is a second or two rather than "until you touch it". Note that the
298
+ 2.4 GHz receiver keeps answering status queries even while the mouse is
299
+ asleep, so "the dongle is present" is not the same as "the mouse is
300
+ reachable".
301
+ * **The wireless link is lossy**; every command is retried automatically.
302
+ * **The mouse reports its own charge** over command `0x04`, as a whole
303
+ percentage plus a charging flag — the same `GetPower` the Windows tool's
304
+ battery icon uses. It has to be polled: the mouse pushes no notification
305
+ when the charge changes, and it advertises no HID battery usage, so your
306
+ desktop's power applet will never list it. The GUI polls it about every
307
+ 30 seconds and shows it in the status bar.
308
+ * Both the wired product id (`25a7:fa7b`) and the 2.4 GHz receiver
309
+ (`25a7:fa7c`) are recognised, and both have now been exercised.
310
+ **Plugging the cable in switches the mouse to wired mode**: it enumerates in
311
+ its own right as `fa7b` while the receiver stays on the bus with nothing
312
+ linked to it. That idle receiver still answers — it reports the mouse
313
+ offline and hands back the *last battery figure it saw*, which looks exactly
314
+ like a live one. So a directly connected mouse is always preferred, and a
315
+ charge is refused outright unless the link says the mouse is really there.
316
+ * **The mouse holds one macro per button**, not a library — a macro lives at
317
+ the address matching the button it is bound to. The library on disk is ours,
318
+ not the mouse's. Up to 70 steps per macro.
319
+ * Macro recording captures **keystrokes only**. Capturing mouse buttons would
320
+ mean swallowing the clicks you need to stop the recording, so they are added
321
+ from the step list instead — which is also the only way to write a press
322
+ without its release.
323
+ * `0x06`, `0x08`, `0x52`, `0x5E` and `0xA7` are spare: zero, correctly
324
+ checksummed, and never read by the stock tool either.
325
+ * `0xB5` and `0xB7` are real settings the Windows tool can write, but this
326
+ model has never written them — they read `0xFF` and form no valid group — so
327
+ they are left alone rather than guessed at.
328
+ * The mouse lights three places but has only **two** channels: `0xA0` drives
329
+ the side strips *and* the palm logo together, `0x4C` drives the scroll wheel.
330
+ All three are listed separately, because that is how the mouse looks, but
331
+ the strips and the logo are one channel — either entry moves both, and both
332
+ say so. There is no way to split them on this model.
333
+ * The wheel's colour is the **DPI stage colour table**, not a field of its own,
334
+ so giving the wheel a colour overwrites the per-stage colour coding. The CLI
335
+ says so and the GUI asks first.
336
+ * `0x54`–`0x5F` is a second effect block whose colour bytes are ignored; its
337
+ mode byte at `0x58` reaches the wheel, which `0x4C` gates. Nothing exposes it.
338
+ * In the travelling effects the two side strips are **offset in time** — they
339
+ are one LED chain running down one side and back up the other. There is no
340
+ phase or direction control: the stock tool has none, and writing to every
341
+ spare byte in the map changes nothing.
342
+ * The stored **speed byte is a delay** — higher is slower — and the firmware
343
+ accepts the whole byte, well past the stock tool's limit of 10. The 1–10
344
+ slider is mapped onto that full range, so 1 is far slower than the Windows
345
+ tool could go. The two zones do not run at the same rate for the same value.
346
+ * The mouse **silently ignores** any field group whose checksum does not
347
+ validate — the write is acknowledged and reads back, but nothing changes.
348
+ `raw` bypasses checksum handling, so use it deliberately.
349
+ * Verified against firmware `2910-B TX V3 CX52850+P3104`. Other M693 revisions
350
+ very likely share this protocol — it is a common Areson/Compx design — but
351
+ take a backup first.
352
+
353
+ ## Layout
354
+
355
+ ```
356
+ m693ctl CLI entry point
357
+ m693/device.py discovery, framing, checksums, retries
358
+ m693/profile.py memory map, encoders, DPI tables
359
+ m693/keys.py button catalogue, combo-key records
360
+ m693/transfer.py profile export/import
361
+ m693/macro.py macro records and .jmm files
362
+ m693/cli.py argument parsing and subcommands
363
+ m693/stock-profile.bin factory configuration, for Restore and the simulator
364
+ m693gui/ the Qt front-end (app, model, worker, tabs, widgets)
365
+ 99-redragon-m693.rules udev rule for non-root access
366
+ packaging/ .desktop entry
367
+ tools/install.sh udev rule + desktop entry
368
+ tools/extract_assets.py pulls the mouse renders out of the Windows installer
369
+ tools/inspect_installer.py extracts VID/PID, sensor & DPI tables for other models
370
+ PROTOCOL.md the reverse-engineering write-up
371
+ ```