Skip to content

Commit 613b984

Browse files
committed
feat(native): capture light feedback reports
1 parent 8db46d2 commit 613b984

22 files changed

Lines changed: 529 additions & 120 deletions

File tree

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ The Linux native package adds:
295295
The Windows native package adds:
296296

297297
- VHF-ready HID descriptor, input report helpers, Switch/PlayStation report
298-
profiles, and rumble feedback contract
298+
profiles, and rumble/light feedback contract
299299
- INF, WDK C source, host bridge C source, C-array asset generators, and a host bridge adapter factory for a maintained Windows VHF driver path
300300
- generated VHF source templates that capture HID output reports and emit native bridge feedback JSONL
301301
- setup helper for staging reviewed VHF driver/host source files and install/test commands without privileged changes
@@ -306,10 +306,11 @@ The Windows native package adds:
306306
The macOS native package adds:
307307

308308
- DriverKit-ready HID descriptor, input report helpers, Switch/PlayStation
309-
report profiles, and rumble output report codecs
309+
report profiles, and rumble/light output report codecs
310310
- Info.plist and entitlement templates for a virtual HID gamepad dext
311311
- C++ DriverKit source and byte-array asset generation, including `setReport`
312-
rumble capture and a `copyRumbleReport` hook for signed host bridges
312+
rumble/light capture plus `copyRumbleReport` and `copyLightReport` hooks for
313+
signed host bridges
313314
- host bridge adapter factory for a signed DriverKit host process
314315
- setup helper for staging reviewed DriverKit/host source files and activation/test commands without privileged changes
315316
- `opencontroller-macos-driverkit-doctor`
@@ -560,8 +561,8 @@ const controller = await createController({
560561

561562
The default macOS bridge path is
562563
`~/Library/Application Support/OpenController/bin/OpenControllerDriverKitHostBridge`.
563-
The generated DriverKit source stores HID rumble output reports so a signed host
564-
bridge can publish `opencontroller.bridge.feedback` JSONL back to
564+
The generated DriverKit source stores HID rumble and light output reports so a
565+
signed host bridge can publish `opencontroller.bridge.feedback` JSONL back to
565566
`controller.onFeedback(...)`.
566567

567568
For application code that should run on whichever native backend is installed

bun.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/macos-driverkit.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ descriptor/report for Switch motion vectors.
123123

124124
The generated C++ source is a DriverKit starting point for an `IOUserHIDDevice`
125125
subclass. It returns the selected OpenController report descriptor with rumble
126-
output support, publishes the virtual gamepad description, keeps a neutral input
127-
report sized to the chosen profile, accepts host output reports through `setReport`, and
128-
exposes `updateInputReport` plus `copyRumbleReport` entry points for a signed
129-
host app/user-client bridge.
126+
and light output support, publishes the virtual gamepad description, keeps a
127+
neutral input report sized to the chosen profile, accepts host output reports
128+
through `setReport`, and exposes `updateInputReport`, `copyRumbleReport`, and
129+
`copyLightReport` entry points for a signed host app/user-client bridge.
130130

131131
## Host Bridge Adapter
132132

@@ -164,11 +164,12 @@ to the intended DriverKit service. It also passes
164164
`OPENCONTROLLER_CONTROLLER_ID` when `controllerId` is provided so the host bridge
165165
can ignore other controllers in a shared multi-agent stream.
166166

167-
For haptics, the generated DriverKit source stores the latest 5-byte HID rumble
168-
output report. A signed host bridge can poll `copyRumbleReport`, encode the
169-
bytes as the shared `"hid-gamepad-rumble"` feedback payload, and write
170-
`opencontroller.bridge.feedback` JSONL to stdout so `controller.onFeedback(...)`
171-
receives the event.
167+
For feedback, the generated DriverKit source stores the latest 5-byte HID rumble
168+
output report and 7-byte light/player-indicator output report. A signed host
169+
bridge can poll `copyRumbleReport` and `copyLightReport`, encode the bytes as
170+
the shared `"hid-gamepad-rumble"` and `"hid-gamepad-lights"` feedback payloads,
171+
and write `opencontroller.bridge.feedback` JSONL to stdout so
172+
`controller.onFeedback(...)` receives the events.
172173

173174
## Current Limitations
174175

docs/release-notes.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
- Added HID light/player-indicator output reports and native bridge light
66
feedback messages so hosts can surface lightbar and player LED state through
77
`controller.onFeedback(...)`.
8+
- Windows VHF and macOS DriverKit generated templates now capture HID light
9+
output reports alongside rumble reports and expose `"hid-gamepad-lights"`
10+
feedback paths for native host bridges.
811
- Added `bun run npm:status` to compare local OpenController package versions
912
against the live npm registry before and after publish.
1013
- README, docs, and demo-facing labels now consistently present the project as
@@ -37,7 +40,7 @@
3740
encoded as combined cardinal D-pad state and report bits.
3841
- D-pad helper commands now honor disabled-button, disabled-combo, and
3942
max-button-hold safety policies through their underlying `DPAD_*` buttons.
40-
- Prepared workspace package manifests for the next `0.1.8` npm patch release.
43+
- Prepared workspace package manifests for the next `0.1.9` npm patch release.
4144
- Added a guarded `publish:npm` helper that publishes packages in dependency
4245
order and supports npm two-factor `--otp` codes.
4346
- `CODE_OF_CONDUCT.md` added for open-source community participation.

docs/windows-virtual-gamepad.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,22 @@ opencontroller-windows-vhf-setup --report-profile switch
151151
```
152152

153153
The generated driver source is a WDK/KMDF starting point that wires the shared
154-
descriptor with rumble output into `VHF_CONFIG_INIT`, creates and starts a VHF
155-
device, accepts an OpenController HID input report through a buffered
156-
IOCTL, and submits it with `VhfReadReportSubmit`. It also registers
154+
descriptor with rumble and light output into `VHF_CONFIG_INIT`, creates and
155+
starts a VHF device, accepts an OpenController HID input report through a
156+
buffered IOCTL, and submits it with `VhfReadReportSubmit`. It also registers
157157
`EvtVhfAsyncOperationWriteReport` so host HID output reports can be captured as
158-
5-byte rumble packets and exposed to the user-mode host bridge through a read
159-
IOCTL. It still needs a signed driver package and a reviewed user-mode host path
160-
before installation.
158+
5-byte rumble packets and 7-byte light/player-indicator packets, then exposed to
159+
the user-mode host bridge through read IOCTLs. It still needs a signed driver
160+
package and a reviewed user-mode host path before installation.
161161

162162
The generated host bridge C source reads OpenController native bridge JSONL from
163163
stdin, prefers direct `hidReportBase64` payloads, falls back to converting
164164
legacy `reportBase64` XInput packets, opens the VHF driver with `CreateFileA`,
165165
and writes HID reports through `DeviceIoControl`. A background feedback
166-
thread polls the driver's rumble IOCTL and emits `opencontroller.bridge.feedback`
167-
JSONL on stdout so `NativeProcessBridgeAdapter` can surface host haptics through
168-
`controller.onFeedback(...)`. Set `OPENCONTROLLER_CONTROLLER_ID` or pass
166+
thread polls the driver's rumble and light IOCTLs and emits
167+
`opencontroller.bridge.feedback` JSONL on stdout so
168+
`NativeProcessBridgeAdapter` can surface host haptics and light/player-indicator
169+
changes through `controller.onFeedback(...)`. Set `OPENCONTROLLER_CONTROLLER_ID` or pass
169170
`--controller-id` when the host bridge is reading a shared stream so each virtual
170171
device only reacts to its assigned controller.
171172

@@ -184,6 +185,7 @@ const controller = await createController({
184185
adapter: createWindowsVhfHostBridgeAdapter({
185186
controllerId: "player-1",
186187
supportsRumble: true,
188+
supportsLights: true,
187189
hostBridgePath: "C:\\OpenController\\OpenControllerVhfHostBridge.exe",
188190
devicePath: "\\\\.\\OpenControllerVhfGamepad"
189191
}),
@@ -194,6 +196,9 @@ controller.onFeedback((event) => {
194196
if (event.type === "rumble") {
195197
console.log(event.weakMotor, event.strongMotor);
196198
}
199+
if (event.type === "lights") {
200+
console.log(event.red, event.green, event.blue, event.playerLightMask);
201+
}
197202
});
198203
```
199204

@@ -217,6 +222,6 @@ emits for native bridge processes.
217222
commands only
218223
- generated host bridge source still needs a Windows build project and signed
219224
device install verification
220-
- rumble feedback requires the generated VHF driver and host bridge to be built
225+
- feedback requires the generated VHF driver and host bridge to be built
221226
together so their submit/pop IOCTL function codes match
222227
- legacy ViGEmBus diagnostics are compatibility-only

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencontroller",
3-
"version": "0.1.8",
3+
"version": "0.1.9",
44
"private": true,
55
"description": "OpenController packages for AI-agent virtual controller control, overlays, safety, and replay logs.",
66
"type": "module",

packages/cli/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opencontroller/cli",
3-
"version": "0.1.8",
3+
"version": "0.1.9",
44
"description": "Command line tools for OpenController.",
55
"type": "module",
66
"license": "MIT",
@@ -34,12 +34,12 @@
3434
"access": "public"
3535
},
3636
"dependencies": {
37-
"@opencontroller/core": "^0.1.8",
38-
"@opencontroller/native": "^0.1.8",
39-
"@opencontroller/native-linux-uinput": "^0.1.8",
40-
"@opencontroller/native-macos-driverkit": "^0.1.8",
41-
"@opencontroller/native-windows-virtual-gamepad": "^0.1.8",
42-
"@opencontroller/overlay": "^0.1.8"
37+
"@opencontroller/core": "^0.1.9",
38+
"@opencontroller/native": "^0.1.9",
39+
"@opencontroller/native-linux-uinput": "^0.1.9",
40+
"@opencontroller/native-macos-driverkit": "^0.1.9",
41+
"@opencontroller/native-windows-virtual-gamepad": "^0.1.9",
42+
"@opencontroller/overlay": "^0.1.9"
4343
},
4444
"devDependencies": {
4545
"@types/bun": "^1.2.15",

packages/core/README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ const hid = new HidGamepadReportAdapter({
186186
console.log("hid", bytes);
187187
},
188188
onFeedback(event) {
189-
console.log("hid rumble", event.weakMotor, event.strongMotor);
189+
console.log("hid feedback", event.type);
190190
},
191191
});
192192

@@ -209,8 +209,8 @@ payload, and `hid-playstation-extended-report` for the 47-byte PlayStation
209209
profile payload that carries touchpad contacts and motion vectors.
210210
`hid-switch-extended-report` carries the common gamepad payload plus Switch
211211
motion vectors in a 31-byte profile report. The HID report adapters also accept
212-
the shared 5-byte rumble output report through
213-
`adapter.receiveOutputReport(bytes)` and surface it through
212+
the shared 5-byte rumble and 7-byte light/player-indicator output reports
213+
through `adapter.receiveOutputReport(bytes)` and surface it through
214214
`controller.onFeedback(...)`.
215215

216216
## Capability Metadata
@@ -222,9 +222,12 @@ console.log(capabilities.supportedProfiles);
222222
console.log(capabilities.outputFormats);
223223
console.log(capabilities.reportFormats);
224224

225-
if (capabilities.feedbackTypes.includes("rumble")) {
225+
if (
226+
capabilities.feedbackTypes.includes("rumble") ||
227+
capabilities.feedbackTypes.includes("lights")
228+
) {
226229
controller.onFeedback((event) => {
227-
console.log(event.weakMotor, event.strongMotor);
230+
console.log(event.type, event.reportFormat);
228231
});
229232
}
230233
```

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opencontroller/core",
3-
"version": "0.1.8",
3+
"version": "0.1.9",
44
"description": "Core OpenController controller runtime for AI-agent virtual controller control.",
55
"type": "module",
66
"license": "MIT",

packages/core/src/hid/playstation.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ControllerState, ControllerTouchpadContact } from "../types";
22
import {
33
createHidGamepadReport,
4+
hidGamepadLightOutputReportDescriptor,
45
hidGamepadRumbleOutputReportDescriptor,
56
} from "./hid-gamepad";
67

@@ -133,6 +134,14 @@ export const hidPlayStationExtendedReportDescriptorWithRumble = Uint8Array.from(
133134
],
134135
);
135136

137+
export const hidPlayStationExtendedReportDescriptorWithFeedback =
138+
Uint8Array.from([
139+
...hidPlayStationExtendedReportDescriptor.slice(0, -1),
140+
...hidGamepadRumbleOutputReportDescriptor,
141+
...hidGamepadLightOutputReportDescriptor,
142+
0xc0, // End Collection
143+
]);
144+
136145
export function createHidPlayStationExtendedReport(
137146
state: ControllerState,
138147
): HidPlayStationExtendedReport {

0 commit comments

Comments
 (0)