hidws is a lightweight C daemon that bridges WebSocket clients to USB HID
devices, letting web apps talk to HID devices remotely over the network
without direct USB access.
It also ships hid-list, a small diagnostic tool that lists all USB HID
devices and probes the reports they support.
Both programs link against the hidapi libusb backend and do not
require kernel HID support (/dev/hidraw*), so they work even on systems
without a HID/INPUT kernel subsystem.
sudo apt install libhidapi-dev libwebsockets-devThe programs link with -lhidapi-libusb at build time. libhidapi-dev must
ship the precompiled libhidapi-libusb.so backend (usbfs, libusb).
To read/write HID devices as a regular user:
sudo usermod -aG plugdev $USERLog out and back in for the change to take effect.
Create /etc/udev/rules.d/99-hid.rules:
# Permissions for HID and USB devices (covers both hidraw and libusb backends)
SUBSYSTEM=="hidraw", MODE="0666"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0664", GROUP="plugdev"
Then reload:
sudo udevadm control --reload-rules && sudo udevadm triggermake
sudo make install # PREFIX=/usr/localThis builds both hidws and hid-list.
MIPS/uClibc note: on the Freetz-EVO MIPS toolchain (GCC 13.4.0, uClibc-ng,
-march=34kc -msoft-float), compilinghidwswith-O1or higher miscompiles the reader thread and crashes with a NULL deref insidehid_read_timeout(only-O0is stable there). On other platforms the default flags are fine. If you hit aSegmentation faultright after[hid] Reader thread started, rebuild with-O0.
./hidws [port]Default port is 9001. Point your WebSocket client to ws://<host>:9001. The HID device is released automatically when the client disconnects.
./hid-listPrints every USB HID device (path, VID/PID, manufacturer, product, serial) and probes its feature and input reports.
All messages are JSON over WebSocket.
| Command | Payload |
|---|---|
list |
{"cmd":"list"} |
open |
{"cmd":"open","vendorId":<int>,"productId":<int>} |
send_report |
{"cmd":"send_report","reportId":<int>,"data":[<bytes>]} |
send_feature_report |
{"cmd":"send_feature_report","reportId":<int>,"data":[<bytes>]} |
close |
{"cmd":"close"} |
| Type | Payload |
|---|---|
device_list |
{"type":"device_list","devices":[{"vendorId":...,"productId":...,"productName":"..."}]} |
opened |
{"type":"opened","vendorId":...,"productId":...,"productName":"...","usagePage":...,"usage":...,"interfaceNumber":...,"busType":...,"releaseNumber":...,"serialNumber":"...","reportDescriptor":[...]} |
input_report |
{"type":"input_report","reportId":<int>,"data":[<bytes>]} |
ok |
{"type":"ok"} |
error |
{"type":"error","message":"..."} |
closed |
{"type":"closed"} |
openedalso carries the hidapi device info (usagePage,usage,interfaceNumber,busType,releaseNumber,serialNumber) and the raw HID report descriptor as a byte array (reportDescriptor), so clients can render the same report collections (input/output/feature with report IDs and items) as a local WebHID connection.
- The KT02H20 family (FiiO JA11 etc.) uses OUTPUT reports; frontends must send data with
send_report(report ID 0x02), not feature reports. input_reportdata received over WebSocket includes the report-ID byte as the first element for numbered input reports; WebHID strips it, so remote frontends must strip it to match.
European Union Public Licence 1.2 (EUPL-1.2). See LICENSE.