libvirtualhid is a planned cross-platform C++ library for creating virtual HID
input devices for remote streaming hosts and similar low-latency input
applications.
The primary target is gamepad input. Keyboard and mouse support are secondary goals once the gamepad model, descriptor handling, and output report plumbing are stable.
- Provide the same public C++ API on Windows, Linux, and eventually macOS.
- Hide platform-specific virtual HID details behind backend implementations.
- Prefer user-mode platform facilities and avoid custom kernel-mode drivers.
- Build with CMake and support direct consumption through
add_subdirectory,FetchContent, installed CMake packages, or vendored source. - Keep Sunshine as the first target consumer and validate the library against Sunshine's current input lifecycle, controller profiles, and packaging needs.
- Keep network transport out of scope. Consumers such as streaming hosts own network input collection and feed local reports into this library.
- Use the MIT license.
- No anti-cheat bypass or stealth device hiding.
- No replication of controller authentication chips or private vendor secrets.
- No Windows kernel-mode driver.
- No built-in network protocol.
The initial design is informed by these projects:
- cgutman/WinUHid: Windows virtual HID device emulation with a UMDF-oriented driver/package shape.
- hifihedgehog/HIDMaestro: Windows user-mode UMDF2 game controller emulation, profile-driven controller identity, output callbacks, hot-plug behavior, and no custom kernel driver.
- games-on-whales/inputtino:
Linux C++ virtual input library built around
uinput,evdev, anduhid, including gamepad, keyboard, mouse, and output-event handling. - LizardByte C++ project structure references:
tray and
libdisplaydevice, especially
their CMake option shape, top-level-only test/doc setup,
third-partysubmodule layout, and GoogleTest wiring.
Windows should use a UMDF2 HID minidriver and a C++ client library/backend. The driver remains user-mode, but it is still a Windows driver package and must be installed and trusted on the host machine.
That means a consuming application can compile the C++ library as part of its own build, but compiling the library alone is not enough to create virtual HID devices on Windows. The project should provide:
- A CMake-built C++ client library for consumers.
- A Windows driver package containing the INF, signed catalog, UMDF driver DLL, and any helper/control component needed by the backend.
- Install/uninstall helpers suitable for developer machines and application installers.
- A path for projects to either build the driver package themselves with the Windows SDK/WDK or redistribute an official prebuilt, signed package.
The public API should not expose these details. Consumers should create a runtime, create devices, submit input state, and receive output reports the same way they do on Linux.
The Windows C++ client library should support both MSVC and MinGW/UCRT64 where the code only depends on normal Win32 or C++ APIs. MinGW support matters for consumers that already build their application with that toolchain. The UMDF2 driver package is different: it should be treated as a Windows SDK/WDK build artifact and built with the Microsoft driver toolchain, such as Visual Studio, MSBuild, or EWDK. The boundary between the library and driver should therefore be compiler-neutral: prefer a stable C ABI, named pipe, device interface IOCTL, or similar control channel over passing C++ STL types across that boundary.
Linux should compile directly into the consuming project and use standard kernel user-space interfaces:
uhidfor descriptor-driven HID gamepads where the raw HID identity and output reports matter.uinputfor keyboard, mouse, and simpler evdev-style devices.libevdevfor uinput device construction where it reduces direct ioctl handling and preserves the same public API.- X11/XTest as a last-resort keyboard and mouse fallback when
uinputcannot be opened and an X11 session is available.
Linux deployment should be documentation and permissions focused: users need
access to /dev/uinput and/or /dev/uhid, usually through udev rules or group
membership. No out-of-tree kernel module should be required.
The current Linux MVP uses uhid and uinput for
BackendKind::platform_default. When /dev/uhid is readable and writable, the
backend reports gamepad and output-report support. When /dev/uinput is
readable and writable, it reports keyboard and mouse support. When a required
node is missing or permission is denied, the same backend remains selectable
but reports the affected capability as unavailable and returns
backend_unavailable from that device creation path.
The Linux backend uses libevdev internally to construct uinput keyboard,
mouse, touchscreen, trackpad, and pen tablet devices. Consumers still use the
same platform-neutral C++ API; libevdev is a Linux build dependency, not a
public API dependency.
The Linux packaging model needs /dev/uinput and /dev/uhid access. Install a udev rules file such
as /etc/udev/rules.d/60-libvirtualhid.rules with:
# Allows libvirtualhid consumers to access /dev/uinput
KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", GROUP="input", MODE="0660", TAG+="uaccess"
# Allows libvirtualhid consumers to access /dev/uhid
KERNEL=="uhid", GROUP="input", MODE="0660", TAG+="uaccess"
Consuming applications may also install name-matched rules for their stable
virtual device names when generated hidraw or input nodes must be
accessible to the session user:
KERNEL=="hidraw*", ATTRS{name}=="Your App Controller*", GROUP="input", MODE="0660", TAG+="uaccess"
SUBSYSTEMS=="input", ATTRS{name}=="Your App Controller*", GROUP="input", MODE="0660", TAG+="uaccess"
For uhid gamepad support, install a modules-load entry such as
/etc/modules-load.d/60-libvirtualhid.conf containing:
uhid
After installing the rules, load uhid, reload udev, and trigger the device
nodes:
sudo modprobe uhid
sudo udevadm control --reload-rules
sudo udevadm trigger --property-match=DEVNAME=/dev/uinput
sudo udevadm trigger --property-match=DEVNAME=/dev/uhidIf input still does not work, add the user running the consuming application to
the input group, then log out and back in:
sudo usermod -aG input $USERThe Linux UHID smoke test creates a real virtual gamepad and fails when the
current user cannot open /dev/uhid.
The Linux uinput smoke test creates real keyboard and mouse devices and fails
when the current user cannot open /dev/uinput.
The Linux consumer integration tests create real virtual devices and validate them through in-process consumer libraries. SDL2 must see the UHID gamepad and observe button/axis input. libinput must see the uinput keyboard and mouse and observe key, pointer motion, and button events. These tests fail when the Linux device nodes or consumer development libraries are unavailable.
The XTest fallback should not be treated as a gamepad backend. It can cover
keyboard and mouse injection on X11, but it does not create virtual HID devices,
does not help on Wayland, and should not replace uhid/uinput for gamepads.
It is enabled automatically when LIBVIRTUALHID_ENABLE_XTEST is ON and CMake
finds X11/XTest development files.
commit 8227e8f8 added the XTest input fallback, and commit f57aee90 removed
src/platform/linux/input/legacy_input.cpp when Sunshine moved fully to
inputtino.
macOS is a later target. The first planning milestone is to validate whether the
backend should use IOHIDUserDevice, DriverKit/HIDDriverKit, or a combination
of both, then document the entitlement, signing, and distribution requirements.
The public API should already be shaped so the macOS backend can plug in without
breaking Windows or Linux consumers.
The exact names may change during implementation, but the API should center on portable concepts instead of platform concepts:
#include <libvirtualhid/libvirtualhid.hpp>
auto runtime = lvh::Runtime::create();
auto created = runtime->create_gamepad(lvh::profiles::xbox_360());
if (!created) {
return;
}
auto &gamepad = *created.gamepad;
gamepad.set_output_callback([](const lvh::GamepadOutput &output) {
// Route rumble, LED, or trigger feedback back to the physical controller.
});
lvh::GamepadState state;
state.buttons.set(lvh::GamepadButton::a, true);
state.left_stick = {0.25f, -0.5f};
state.right_trigger = 1.0f;
gamepad.submit(state);Expected core types:
Runtime: owns platform backend discovery, initialization, and shutdown.VirtualDevice: common lifecycle for create, destroy, and hot-plug.Gamepad: gamepad-specific state submission and output callbacks.Keyboard: key press/release and UTF-8 text submission.Mouse: relative motion, absolute motion, button, vertical scroll, and horizontal scroll submission.Touchscreen: direct multi-touch contacts for touch displays.Trackpad: indirect multi-touch contacts and click state for touchpads.PenTablet: tablet tool, pressure, distance, tilt, and pen button state.DeviceProfile: VID/PID, product strings, bus type, HID descriptor, report layout, and platform capability metadata.DeviceNode: platform-reported device nodes and sysfs paths for consumers that must hand created devices to SDL, libinput, HIDAPI, or diagnostics.GamepadState: normalized buttons, axes, triggers, hats, motion sensors, and optional touchpad data.GamepadOutput: normalized rumble, haptics, LEDs, adaptive triggers, and raw output reports when a profile needs them.BackendCapabilities: runtime capability query for platform/backend limits, such assupports_virtual_hid,supports_output_reports,supports_keyboard,supports_mouse,supports_xtest_fallback, andrequires_installed_driver.
Streaming hosts are the first consumer class to design against. The initial implementation should cover the behavior Sunshine needs first, while keeping the requirements expressed in terms that apply to other consumers:
- CMake consumption must work as a vendored dependency under a consuming
project's
third-partytree. - The API must support multiple client-relative and global gamepad indexes so streaming hosts can preserve stable controller lifecycles across arrival, update, feedback, and removal events.
- Built-in profiles should cover common streaming controller choices: automatic selection, Xbox One-style, DualSense-style, and Switch Pro-style devices. Xbox 360 can remain useful as a compatibility profile and test target.
- Controller metadata must be rich enough for streaming-host selection rules: client controller type, motion sensor capability, touchpad capability, RGB LED support, battery state, and per-controller identity data.
- Output callbacks must carry rumble first, then RGB LED, adaptive trigger, and raw output report data where the selected profile supports it.
- Keyboard and mouse APIs should map cleanly to common relative mouse, absolute mouse, buttons, scroll, horizontal scroll, keyboard scancode, and Unicode paths.
- Linux keyboard support must include configurable auto-repeat for held keys so streaming hosts can preserve input behavior previously covered by inputtino.
- Linux devices must expose created device nodes and relevant sysfs paths for consumers and diagnostics that need to inspect or pass those paths onward.
- Linux fallback behavior should match streaming-host operational
expectations:
prefer real virtual devices through
uhid/uinput; only use XTest for keyboard/mouse when virtual device creation fails and X11 is available. - Linux gamepad support must reach inputtino parity before replacement: real DualSense UHID descriptors, GET_REPORT replies, periodic input reports, touchpad, motion, battery, RGB LED, adaptive trigger callbacks, CRC handling, and equivalent output-report feedback behavior for the UHID gamepad path.
- Linux pointer support must cover touchscreen, trackpad, and pen tablet virtual devices with libinput-observable behavior.
- The library must not own a consumer's network protocol, client packet parsing, configuration system, or feedback queue. It should expose the device primitives consumers need to keep that ownership in their applications.
- Use CMake as the only build system for the core library.
- Follow the LizardByte
trayandlibdisplaydevicepattern: top-level-onlyBUILD_TESTSandBUILD_DOCSoptions, reusable library targets, and tests that do not force themselves on parent projects. - Put all submodules under
third-party. - Add GoogleTest as a submodule at
third-party/googletest; do not download it during configure. - Add the LizardByte Doxygen configuration as a submodule at
third-party/doxyconfigand use it for local docs and Read the Docs builds. - Expose
libvirtualhid::libvirtualhidas the main CMake target. - Keep the public headers under
src/include/libvirtualhidand the implementation split into shared core code plus platform-specific backends. - Add Windows CI coverage for the client library with MSVC and MinGW/UCRT64.
- Add Linux CI coverage for GCC and Clang, with integration tests requiring
/dev/uinput,/dev/uhid, SDL2, libinput, and X11/XTest where applicable. - Add separate WDK/MSVC validation for the driver package once driver sources exist.
The intended project layout is:
src/include/libvirtualhid/ Public C++ headers
src/core/ Shared profile, descriptor, and report logic
src/platform/windows/ Windows client backend and UMDF control channel
src/platform/linux/ Linux uhid/uinput backend
src/platform/macos/ Future macOS backend
drivers/windows/ UMDF2 driver package sources
profiles/ Built-in gamepad profiles
examples/ Minimal consumers and platform smoke tests
tests/ Unit and integration tests
cmake/ Package config and helper modules
docs/ Project Doxygen configuration
third-party/doxyconfig/ LizardByte Doxygen configuration submodule
third-party/googletest/ GoogleTest submodule
- Add CMake project scaffolding and exported target
libvirtualhid::libvirtualhid. - Define the public C++ API, error model, device lifecycle, and ownership rules.
- Add a fake in-memory backend so API tests can run on every platform.
- Add GoogleTest as a submodule under
third-party/googletestand wire tests using the same top-level-only pattern astrayandlibdisplaydevice. - Add Doxygen documentation wiring with
third-party/doxyconfig, a projectdocs/Doxyfile, and Read the Docs configuration. - Add CI using the
libdisplaydeviceworkflow pattern for Linux GCC, Linux Clang, macOS, Windows MinGW/UCRT64, and Windows MSVC configure/build/test coverage. - Add descriptor/profile models for at least Xbox 360, Xbox Series, DualSense, and a generic HID gamepad.
- Add unit tests for state normalization and HID report packing.
- Add a streaming-host-oriented example or adapter test that exercises controller arrival, state updates, output feedback, and removal without depending on consumer internals.
- Implement gamepad creation over
uhidfor descriptor-driven controllers. - Add
uinputsupport for keyboard and mouse once the gamepad path is stable. - Support output report callbacks for rumble and profile-specific feedback.
- Add X11/XTest fallback support for keyboard and mouse only.
- Add examples and integration tests that validate virtual device visibility through SDL2 for generic gamepad input, DualSense USB input, and DualSense Bluetooth controller discovery, plus libinput for keyboard/mouse.
- Document required Linux permissions and sample udev rules.
- Replace the generic DualSense USB profile behavior with a descriptor-driven DualSense report descriptor and 64-byte input report packing.
- Add Bluetooth DualSense descriptor parity, CRC handling, and Bluetooth input report framing.
- Add DualSense UHID GET_REPORT replies for calibration, pairing, and firmware reports, including MAC/uniq identity handling.
- Add periodic DualSense input reports for consumers that expect steady sensor and touchpad updates.
- Add DualSense input state for motion sensors, touchpad contacts, battery state, and profile-specific buttons without leaking Linux-specific details into consumers.
- Parse DualSense output reports into rumble, RGB LED, adaptive trigger, and raw-report callbacks.
- Expose created device nodes and sysfs paths through the platform-neutral public API.
- Add configurable keyboard auto-repeat for held keys.
- Add touchscreen, trackpad, and pen tablet public device types and Linux direct-uinput backend implementations.
- Keep gamepad feedback on UHID output reports. There is no uinput-backed gamepad path in this library; if one is added later, it must implement Linux force-feedback upload, erase, playback, and gain handling.
- Expand Linux consumer tests so SDL2 validates generic joystick input, DualSense USB controller input, and DualSense Bluetooth controller discovery, and libinput validates keyboard, mouse, touchscreen, trackpad, and pen tablet events.
- Prefer libevdev for uinput device construction where it removes fragile direct ioctl setup, while keeping the public API unchanged.
- Build a UMDF2 HID minidriver package with CMake/WDK integration.
- Implement the Windows backend and control channel between the C++ library and the UMDF driver.
- Keep the client library buildable with MSVC and MinGW/UCRT64. Keep the driver package on the Microsoft WDK toolchain.
- Add install/uninstall tooling for developer workflows.
- Support hot-plug, multi-controller instances, and output report callbacks.
- Validate visibility through DirectInput, XInput where applicable, SDL/HIDAPI, Windows.Gaming.Input/GameInput, and browser Gamepad API.
- Keep one API surface across Windows and Linux, with capability queries for platform limitations instead of platform-specific methods.
- Add installed CMake package support and
FetchContentdocumentation. - Add CI for formatting, static analysis, CMake configure/build, unit tests, and platform smoke tests.
- Defer C, Python, and Rust bindings until after the platform API is stable, likely after macOS support lands.
- Decide whether official Windows releases should ship signed driver packages in addition to source. (Yes, we should ship signed driver packages/installers from this repo's releases)
- Prototype macOS virtual HID creation and report submission.
- Document signing, entitlement, and installer constraints.
- Add macOS backend behind the existing public API.
- Add macOS discovery and smoke-test coverage.
- Unit test descriptor generation, report packing, axis scaling, button mapping, and output report parsing.
- Run lifecycle tests for create, submit, output callback, destroy, repeated hot-plug, and process shutdown cleanup.
- Validate multi-controller behavior and stable ordering.
- Test against real consumers where practical: Sunshine, SDL, HIDAPI, browser Gamepad API, DirectInput/XInput/GameInput on Windows, and evdev/libinput libraries on Linux.
libvirtualhid is licensed under the MIT License. See LICENSE.