wsljoy mirrors a controller connected to Windows over USB or Bluetooth into WSL2 as a normal Linux joystick device.
Use Python 3.10. The project is pinned to Python 3.10 because the Windows controller dependencies have the most reliable wheel support there.
Install the Windows host package on Windows with the windows extra. This extra installs hidapi and pygame for controller access.
With pip:
py -3.10 -m venv .venv
.venv\Scripts\activate.bat
python -m pip install --upgrade pip
python -m pip install "wsljoy[windows]"
python -m wsljoy list
python -m wsljoy hostWith uv pip:
uv venv --python 3.10
.venv\Scripts\activate.bat
uv pip install "wsljoy[windows]"
python -m wsljoy list
python -m wsljoy hostInstall the WSL2 guest package inside WSL2 Ubuntu. The linux extra is available for symmetry, but currently has no third-party dependencies.
With pip:
python3.10 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "wsljoy[linux]"
python -m wsljoy setup-uinput
python -m wsljoy guestWith uv pip:
uv venv --python 3.10
. .venv/bin/activate
uv pip install "wsljoy[linux]"
python -m wsljoy setup-uinput
python -m wsljoy guestUse these commands when developing from a cloned checkout.
With uv:
uv python install 3.10
uv sync --extra windows
uv run python -m wsljoy list
uv run python -m wsljoy hostWith standard venv and pip:
py -3.10 -m venv .venv
.venv\Scripts\activate.bat
python -m pip install --upgrade pip
python -m pip install -e ".[windows]"
python -m wsljoy list
python -m wsljoy hostWith uv:
uv sync --extra linux
uv run python -m wsljoy setup-uinput
uv run python -m wsljoy guestWith standard venv and pip:
python3.10 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[linux]"
python -m wsljoy setup-uinput
python -m wsljoy guestsetup-uinput first checks whether /dev/uinput is already writable. It only asks for sudo when your user needs to be added to the input group; after that, start a new WSL shell or run newgrp input, then run the guest without sudo.
It creates a virtual Linux input device through /dev/uinput using the Linux uinput API. The /dev/input/event* device appears after the guest receives the first packet from Windows. The /dev/input/js* device appears when the Linux joydev module is available and loaded.
Check the guest side:
ls -l /dev/uinput
groups
python -m wsljoy guestIn a second WSL terminal, after the Windows host is running:
ls -l /dev/input/event* /dev/input/js*If event* exists but js* does not, try:
sudo modprobe joydevBy default the Windows host targets WSL2. On Windows, wsljoy resolves the current WSL2 IP by running wsl.exe hostname -I.
With uv:
uv run python -m wsljoy hostuv run python -m wsljoy guest --listen 0.0.0.0 --port 27414With an activated venv:
python -m wsljoy hostpython -m wsljoy guest --listen 0.0.0.0 --port 27414If you have multiple WSL distros, name the one running the guest:
python -m wsljoy host --wsl-distro Ubuntu-22.04To bypass WSL auto-resolution, pass an explicit IP address:
python -m wsljoy host --target 172.25.121.7The Windows host has two reader backends:
ds4-hid: exact DualShock 4 HID parser for USB and Bluetooth, preferred automatically for DS4 devices.sdl: generic SDL/pygame joystick reader, used for common Xbox, PlayStation, 8BitDo, Nintendo, Logitech, PowerA, Razer, and compatible controllers.
If the same controller is visible through both APIs, list shows the exact backend and hides the duplicate SDL entry. host --backend auto prefers ds4-hid; use host --backend sdl to force SDL.
Auto detection is the default:
python -m wsljoy list
python -m wsljoy host --backend autoForce the generic backend:
python -m wsljoy host --backend sdlThe exact DS4 path supports:
- Sony vendor ID
054c - Product IDs
05c4and09cc - USB reports and Bluetooth reports
The SDL path covers many usual-suspect controllers but depends on the mapping SDL exposes for that device. The WSL side creates a virtual Linux gamepad with the detected vendor/product IDs when available and Linux-style axes/buttons available through /dev/input/event* and /dev/input/js*.
With uv:
uv python install 3.10
uv sync --all-extras --dev
uv run pytestWith standard venv and pip:
python3.10 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
python -m pytest