Skip to content
Merged
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
36 changes: 36 additions & 0 deletions captures/attackshark-x11/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Attack Shark X11 fixtures

Reference material for the X11 path in `src/drivers/attackshark/hid.ts`.

Polled from one user's Windows machine (Firefox 157, OpenMouse webapp
`BETA · v2.0.c81` through OpenMouse Bridge, `ws://127.0.0.1:17846`) and posted
in [issue #161](https://github.com/OpenMouse-Project/mouse-protocol/issues/161).
The receiver is `0x1d57:0xfa60`; no serial string and no personal data are
included.

## What Bridge lists for the receiver

Seven collections are merged into one device, and every one of them reports no
feature report — the same rebuilt shape `captures/delux-m600-pro/windows-bridge.txt`
shows:

```
usage 0x1:6 feat[none] usage 0x1:80 feat[none]
usage 0xc:1 feat[none] usage 0xa:0 feat[none]
usage 0x1:2 feat[none] usage 0xb:0 feat[none]
usage 0x1:6 feat[none]
```

The `0x0b` config collection therefore declares nothing, which is exactly the
`x11RebuiltConfigCollection` shape. No receiver message (`03 <model> 40 01
<pct>`) was captured in this session, so the status read returned
`settingsReady: false` and no battery.

## Why the X11 is enabled on this path

`X11_REBUILT_CONFIG_MODELS` accepts model id `0x55` on top of the
hardware-verified M600 Pro (`0x20`): the same receiver, protocol and rebuilt
transport, with the receiver's documented 56-byte DPI frame for `0xfa60`
(`x11UsesShortDpiReport` returns the long form). The write itself has not been
observed on X11 hardware yet — the reporter was asked to confirm DPI, polling
and battery on a build, per issue #161.
33 changes: 31 additions & 2 deletions src/drivers/attackshark/hid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,16 +477,45 @@ test("Bridge on Windows: the M600 Pro is named by its unnumbered receiver messag
assert.deepEqual(reads, []);
});

test("Bridge on Windows: other models are named but stay read-only", async () => {
test("Bridge on Windows: the X11 is named by its receiver message and becomes writable", async () => {
resetAttackSharkX11DpiState();
resetAttackSharkX11RuntimeState();
const { unit, sent, push } = windowsBridgeX11Receiver();
const { unit, sent, reads, push } = windowsBridgeX11Receiver();
const client = new AttackSharkHidClient(unit, { batteryWaitMs: 0, receiverWriteGapMs: 0 });
await client.open();

// Issue #161: a genuine X11 (model id 0x55) over the same rebuilt collection.
push([0x03, 0x55, 0x40, 0x01, 0x50]);
const status = await client.readStatus();
assert.equal(status.name, "Attack Shark X11");
assert.equal(status.batteryPercent, 80);
assert.equal(status.ui?.settingsReady, true);
assert.equal(status.ui?.statusNote, undefined);
assert.deepEqual(status.supportedPollingRates, [125, 250, 500, 1000]);
assert.deepEqual(reads, []);

// Same 0x06 packet as the verified M600 Pro write.
assert.equal(await client.setPollingRate(500), 500);
assert.deepEqual(sent, [{ reportId: 0x06, data: [0x09, 0x01, 0x02, 0xfd, 0, 0, 0, 0] }]);
sent.length = 0;
assert.equal(await client.setDpi(800), 800);
assert.equal(sent[0].reportId, 0x04);
// The receiver's 56-byte DPI frame the reference driver documents, minus the
// report id — not the M600 Pro's 52-byte one.
assert.equal(sent[0].data.length, 55);
assert.deepEqual(reads, []);
});

test("Bridge on Windows: models with no established frame shape still stay read-only", async () => {
resetAttackSharkX11DpiState();
resetAttackSharkX11RuntimeState();
const { unit, sent, push } = windowsBridgeX11Receiver();
const client = new AttackSharkHidClient(unit, { batteryWaitMs: 0, receiverWriteGapMs: 0 });
await client.open();
// The R1 (0x10) uses a different DPI map per the reference driver.
push([0x03, 0x10, 0x40, 0x01, 0x05]);
const status = await client.readStatus();
assert.equal(status.name, "Attack Shark R1");
assert.equal(status.ui?.settingsReady, false);
await assert.rejects(() => client.setDpi(800), /not reachable from a browser/);
assert.deepEqual(sent, []);
Expand Down
11 changes: 8 additions & 3 deletions src/drivers/attackshark/hid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ const attackShark = (model: string, battery?: ReceiverModel["battery"]): Receive
* Models identified by the model id their receiver messages carry. Attack
* Shark ids: HarukaYamamoto0/attack-shark-x11-driver src/core/devices.ts.
*/
const X11_MODEL_ID = 0x55;
const X11_RECEIVER_MODELS: ReadonlyMap<number, ReceiverModel> = new Map([
[0x55, attackShark("X11", "percent")],
[X11_MODEL_ID, attackShark("X11", "percent")],
// xb-bx/attack-shark-r1-driver reports charge as p2 * 10.
[0x10, attackShark("R1", "tenths")],
[0x85, attackShark("X6", "tenths")],
Expand Down Expand Up @@ -253,8 +254,12 @@ const X11_DPI_PIDS: ReadonlySet<number> = new Set([0xfa55, 0xfa60]);
// A rebuilt config collection (see x11RebuiltConfigCollection) carries no
// report sizes and no hint of which firmware is behind it, so writes through
// it are only enabled once a receiver message names a model checked on that
// path (docs/delux-m600-pro-testing.md).
const X11_REBUILT_CONFIG_MODELS: ReadonlySet<number> = new Set([DELUX_M600_PRO_MODEL_ID]);
// path (docs/delux-m600-pro-testing.md). The M600 Pro was captured through
// Bridge on Windows; the genuine X11 (model id 0x55 — issue #161) rides the
// same receiver and protocol and resolves to the receiver's 56-byte DPI
// frame, so it is enabled too. Models whose frame shape is not established
// (R1 0x10, X3 0x4d/0x4e, X6 0x85, …) stay read-only.
const X11_REBUILT_CONFIG_MODELS: ReadonlySet<number> = new Set([DELUX_M600_PRO_MODEL_ID, X11_MODEL_ID]);

// The firmware has no cheap "current DPI" command, so — exactly like the
// reference driver — the last full six-stage table this process wrote (or
Expand Down
Loading