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
18 changes: 12 additions & 6 deletions native-hid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ for that brand's vendor id(s) and tries each candidate class directly:
throw is treated as correct, and `setDpi()`/`setPollingRate()` are called on
it.

The Attack Shark X11 family is the exception. Its settings channel is not
The Attack Shark X11 family is the partial exception. Its settings channel is not
visible through WebHID, so `apply.mjs` first checks for VID `0x1d57`, PIDs
`0xfa55`/`0xfa60`/`0xfa61`, interface 2 and sends its verified polling feature
report directly through `node-hid`. This keeps the normal operating-system
HID driver in place; WinUSB and Zadig are not required. Native X11 DPI support
is deliberately rejected until its six stages can be read and preserved.
`0xfa55`/`0xfa60`/`0xfa61`, interface 2: polling-only requests are sent as the
verified polling feature report directly through `node-hid`. This keeps the normal operating-system
HID driver in place; WinUSB and Zadig are not required. Requests involving DPI
fall through to the generic `AttackSharkHidClient` path, which drives the 0x04
six-stage table for model `0x55` (mouse-protocol #174), preserving the stages
the request does not touch.

## Exit code contract (Bridge depends on this — see `src/drivers/native_hid.rs`)

Expand Down Expand Up @@ -93,7 +95,11 @@ runtime first and falls back to `node` on `PATH`, so local development

- Attack Shark X11 polling was hardware-verified on Windows with the wireless
PID `0xfa60`, interface 2, using the standard Microsoft HID driver. Switching
between 500 Hz and 1000 Hz succeeded without WinUSB or Zadig.
between 500 Hz and 1000 Hz succeeded without WinUSB or Zadig. DPI goes
through the generic `AttackSharkHidClient` 0x04 path (six-stage table,
model `0x55`); the reporter in mouse-protocol #161 confirmed DPI and
polling controls working after that fix, with a read-back write round-trip
still to be captured.
- Other adapter and dispatch paths are structurally tested (module resolution,
exit codes and control flow) and still require validation on their respective
hardware.
Expand Down
5 changes: 3 additions & 2 deletions native-hid/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions native-hid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@
"node": ">=20"
},
"dependencies": {
"@openmouse/protocol": "github:OpenMouse-Project/mouse-protocol#v0.10.0",
"@openmouse/keyboard-protocol": "github:OpenMouse-Project/keyboard-protocol#v1.1.0",
"@openmouse/protocol": "github:OpenMouse-Project/mouse-protocol#v0.42.1",
"node-hid": "^3.1.0"
},
"allowScripts": {
"github:OpenMouse-Project/keyboard-protocol#v1.1.0": true,
"github:OpenMouse-Project/keyboard-protocol#cae3d7a74e8256a9a3cc5c7fe2311db1425bb977": true,
"github:OpenMouse-Project/mouse-protocol#v0.10.0": true,
"github:OpenMouse-Project/mouse-protocol#v0.42.1": true,
"node-hid@3.4.0": true,
"github:OpenMouse-Project/mouse-protocol#b69cd44b2498dcaaa65fd6f77ba10bf109e49322": true
"github:OpenMouse-Project/mouse-protocol#b69cd44b2498dcaaa65fd6f77ba10bf109e49322": true,
"github:OpenMouse-Project/mouse-protocol#de188133bace18b1f75cd7bfd8360f17197222a5": true,
"github:OpenMouse-Project/mouse-protocol#c3b1bc20866fbef3a27b37ad020887607d6dbbf2": true
}
}
11 changes: 7 additions & 4 deletions native-hid/src/apply.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,14 @@ async function main() {
process.exit(EXIT_NO_DRIVER);
}

// The Attack Shark X11 family's settings channel is not visible through
// WebHID, but it is reachable over native HID. Polling-only requests take
// the verified direct feature-report path below; anything involving DPI
// falls through to the generic driver path, where AttackSharkHidClient
// drives the 0x04 six-stage table (enabled for model 0x55 in
// mouse-protocol #174), preserving the stages the request does not touch.
if (normalizedBrand === "attack shark" && x11DeviceInfos().length > 0) {
if (Number.isFinite(input.dpi)) {
throw new Error("Attack Shark X11 native DPI control is not implemented yet; polling-only requests are supported.");
}
if (Number.isFinite(input.pollingRateHz)) {
if (Number.isFinite(input.pollingRateHz) && !Number.isFinite(input.dpi)) {
applyX11PollingRate(input.pollingRateHz);
process.exit(EXIT_APPLIED);
}
Expand Down
6 changes: 6 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub fn router(service: BridgeService, origins: &[String]) -> Router {
.route("/v1/native/settings", put(apply_native_settings))
.route("/v1/battery", put(record_battery))
.route("/v1/autostart", put(set_autostart))
.route("/v1/panel", put(show_panel))
.merge(hid_route)
.layer(SetResponseHeaderLayer::if_not_present(
axum::http::HeaderName::from_static("access-control-allow-private-network"),
Expand Down Expand Up @@ -101,6 +102,11 @@ async fn status(State(service): State<BridgeService>) -> Json<crate::service::Br
Json(service.snapshot().await)
}

async fn show_panel(State(service): State<BridgeService>) -> Json<ApiResult> {
service.request_show_panel();
Json(ApiResult { ok: true })
}

async fn handshake(State(service): State<BridgeService>) -> Json<ApiResult> {
service.record_client_heartbeat().await;
Json(ApiResult { ok: true })
Expand Down
6 changes: 6 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub struct BridgeConfig {
/// user who later turns it off keeps it off.
#[serde(default)]
pub autostart_configured: bool,
/// Whether the tray panel has been shown once. Bridge stays in the tray
/// after that; the first launch opens the panel so a window actually appears.
#[serde(default)]
pub tray_hint_shown: bool,
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
Expand Down Expand Up @@ -126,6 +130,7 @@ impl Default for BridgeConfig {
default_profile: None,
allowed_origins: default_origins(),
autostart_configured: false,
tray_hint_shown: false,
}
}
}
Expand Down Expand Up @@ -302,6 +307,7 @@ mod tests {
default_profile: None,
allowed_origins: Vec::new(),
autostart_configured: false,
tray_hint_shown: false,
}
.normalized();
assert_eq!(config.battery_threshold_percent, 100);
Expand Down
Loading
Loading