jc2mouse is a Linux userspace tool that connects to Nintendo Switch 2 Joy-Con 2 controllers over BLE without pairing/bonding, enables the optical sensor stream, and exposes virtual input devices via uinput.
It supports:
- Single Joy-Con mode: mouse + compact gamepad mode
- Combined mode (Left + Right): one full virtual Xbox-style controller
- Mouse overlay in combined mode: press C on the Right Joy-Con to turn only the Right Joy-Con into a mouse while the Left Joy-Con continues as the left half of the gamepad
Joy-Con 2 will disconnect if Linux/BlueZ tries to enforce normal pairing/security behavior for LE connections.
This repo uses a patched bluetoothd in a safe, reversible session mode:
- stop
bluetooth.service - start
jc2-bluetooth.service(patched bluetoothd) - run
jc2mouse - restore stock Bluetooth on exit
The session swap is controlled by a helper script installed as:
/usr/local/sbin/jc2-session
Right Joy-Con 2
- Mouse: optical motion ->
REL_X/REL_Y - Clicks: (default) R = left click, ZR = right click, R3 = middle click
- Scroll: stick Y deflection (smooth)
- Toggle mouse/gamepad: C
Left Joy-Con 2
- Mouse (optional): optical + clicks + stick scroll
- Toggle mouse/gamepad: hold L + ZL
- Gamepad mode uses an Xbox-style mapping (Steam/xpad compat handled)
- One virtual Xbox-style uinput controller:
- Left stick ->
ABS_X/ABS_Y - Right stick ->
ABS_RX/ABS_RY - D-pad ->
BTN_DPAD_* - ABXY -> Xbox positions (South=A, East=B, West=X, North=Y)
- LB/LT from Left, RB/RT from Right
- Start/Select/Guide mapped sensibly
- Left stick ->
- Right mouse overlay in combined mode
- Press C on the Right Joy-Con:
- Right becomes mouse (optical + buttons + scroll)
- Right half of the gamepad is suppressed
- Left continues driving the left half of the combined controller
- Press C again to return to full combined controller
- Press C on the Right Joy-Con:
src/jc2mouse/driver.py— BLE (BlueZ D-Bus) + optical + uinput logicsrc/jc2mouse/cli.py— CLI entrypoint + auto discovery + session integrationscripts/build_bluez.sh— builds patched BlueZ bluetoothd into/opt/jc2mouse/bluezscripts/setup.sh— installs systemd unit + session helperscripts/jc2-session.sh— start/stop/status session modesystemd/jc2-bluetooth.service— systemd unit for patched bluetoothd
This installs:
/etc/systemd/system/jc2-bluetooth.service/usr/local/sbin/jc2-session
Run:
sudo scripts/setup.shBuild script downloads BlueZ (default 5.72), applies patches, and installs into:
/opt/jc2mouse/bluez
Run:
sudo scripts/build_bluez.shThe jc2-bluetooth.service unit uses:
ExecStart=/opt/jc2mouse/bluez/libexec/bluetooth/bluetoothd -E -n -d
From repo root:
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -e .sudo jc2-session start
sudo jc2-session status
sudo jc2-session stopsudo usually won’t see your venv PATH. Use one of these patterns:
A) Explicit venv path
sudo -E .venv/bin/jc2mouse run --autoB) Use which while the venv is activated
sudo -E "$(which jc2mouse)" run --autosudo -E .venv/bin/jc2mouse run --autoFilter by side:
sudo -E .venv/bin/jc2mouse run --auto --side left
sudo -E .venv/bin/jc2mouse run --auto --side rightsudo -E .venv/bin/jc2mouse run --auto --combinedTips:
- Hold PAIR on Left until it connects, then hold PAIR on Right.
- Some systems require a retry or two due to unpaired BLE timing.
Usually means the Joy-Con wasn’t connectable at the moment BlueZ attempted the connect.
Try:
- Hold the PAIR button during the connection attempt.
- Don’t press other buttons while connecting.
- Retry the command; combined mode may need a retry for the second Joy-Con.
If stock Bluetooth is masked, session restore may fail.
Fix:
sudo systemctl unmask bluetooth.service
sudo systemctl enable --now bluetooth.servicesystemctl status jc2-bluetooth.service --no-pager
systemctl status bluetooth.service --no-pager- LED management (player LEDs / mode indicator)
- GUI frontend (mode notifications / smoother UX)
- IMU decode (gyro + accel) and DualShock/DualSense-style emulation mode
- Battery level reporting (investigate feasibility without bonding)
- Rumble
- Windows port + Android/Winlator Cmod integration
Open to contributions and issue reports. Please include logs and device info (distro, kernel, BlueZ version) when reporting bugs.