The first working Bluetooth LE client for the Nintendo Switch 2 Pro Controller on macOS.
A Python menubar app that connects to the Switch 2 Pro Controller via BLE and translates inputs to keyboard presses for use with emulators like Ryujinx.
This is NOT a system driver. It won't make your controller appear in System Preferences or work natively with games.
This IS:
- โ A BLE client that reads controller inputs via Bluetooth Low Energy
- โ A keyboard bridge that converts inputs to key presses for Ryujinx
- โ A reference implementation for the Switch 2 Pro Controller BLE protocol
- โ Full button mapping โ all buttons, triggers, D-pad working
- โ Analog sticks โ read with 12-bit precision (converted to 8 directions, see Limitations)
- โ Grip buttons โ Switch 2 exclusive GL/GR buttons supported
- โ Ryujinx compatible โ keyboard bridge for emulator support
- โ No pairing required โ bypasses macOS Bluetooth limitations
- โ Auto-reconnect โ if the controller sleeps or drops, the bridge retries for 60 s
- โ C button โ the Switch 2's new C button can be mapped (experimental)
- โ DSU server (cemuhook) โ true analog sticks in Dolphin, Cemu & other DSU clients, no driver needed
- โ Start at Login โ one click in the menubar (bundled .app, macOS 13+)
The Nintendo Switch 2 Pro Controller (Product ID: 0x2069) doesn't work with macOS natively:
| Method | Status | Problem |
|---|---|---|
| USB | โ | Firmware blocks non-Switch connections |
| Bluetooth Classic | โ | macOS can't discover/pair with it |
| Bluetooth LE | โ | Works with custom BLE client (this project) |
This bridge connects via BLE using the bleak library, reads the raw input data, and converts it to keyboard presses that Ryujinx can use.
- macOS Ventura (13.0) or later
- Python 3.9+
- Nintendo Switch 2 Pro Controller
git clone https://github.com/mlstr0m/switch2bridge-macos.git
cd switch2bridge-macos
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python Switch2Bridge.pymacOS will ask for two permissions:
- Accessibility โ prompted at first launch (to simulate keyboard input)
- Bluetooth โ prompted the first time you click Connect Controller (not at launch!)
System Settings โ Privacy & Security โ Bluetooth, then relaunch. The app detects a denied permission and offers to open the right settings pane.
chmod +x build_dmg.sh
./build_dmg.shThe installer lands at dist/Switch2Bridge-Installer.dmg. Manual build:
python setup_app.py py2app
# โ dist/Switch2 Bridge.app- Launch the app โ a ๐ฎ appears in the menu bar
- Click โ Connect Controller
- Wait for ๐ข (connected)
- Open Ryujinx โ Options โ Settings โ Input
- Input Device: Keyboard
- Controller Type: Pro Controller
- Map keys using the table below
Default mapping:
| Button | Key | Button | Key | |
|---|---|---|---|---|
| A | Z | L | Q | |
| B | X | R | E | |
| X | C | ZL | 1 | |
| Y | V | ZR | 3 | |
| + | P | LS (click) | F | |
| - | M | RS (click) | G | |
| Home | H | GL (grip) | 9 | |
| Capture | O | GR (grip) | 0 |
| D-Pad | Key | Stick | Keys | |
|---|---|---|---|---|
| Up | โ | Left Stick | WASD | |
| Down | โ | Right Stick | IJKL | |
| Left | โ | |||
| Right | โ |
The first time you launch the app it writes a JSON config to:
~/Library/Application Support/Switch2Bridge/mappings.json
Edit it to remap any button or stick direction, then Reload mappings from the menubar (or restart the app). The menubar also has Edit mappings fileโฆ which reveals the file in Finder.
Each value is either a single character ("a", "5", "."), null to leave a button unmapped, or a named key in angle brackets: <up>, <down>, <left>, <right>, <space>, <enter>, <esc>, <tab>, <backspace>, <delete>, <home>, <end>, <pageup>, <pagedown>, <shift>, <ctrl>, <alt>, <cmd>, and <f1> โฆ <f20>.
The Switch 2's new C button is supported as "C" (unmapped by default โ set it to any key to use it).
The "ble" section holds one advanced setting, input_char: leave it null to auto-detect the controller's input characteristic (the bridge fills it in itself once detected), or set a 128-bit UUID to force one. See BLE Characteristics.
Invalid JSON falls back to defaults and the menubar surfaces the parse error. Unknown button names or stick directions (typos) are reported via a notification instead of being silently ignored. Two inputs may share the same key: the key is only released once both are released.
The app runs a cemuhook/DSU server (default 127.0.0.1:26760), which exposes the controller as a full gamepad over UDP โ analog sticks included, bypassing the keyboard bridge's 8-direction limitation.
- Dolphin โ Options โ Controller Settings โ Alternate Input Sources โ enable DSU Client, add
127.0.0.1:26760. The pad then appears as an input device with analog axes. - Cemu โ Input settings โ add a DSUController with the same address.
- Ryujinx โ uses DSU for motion only (Settings โ Input โ enable Motion โ Use CemuHook compatible motion). Buttons/sticks still go through the keyboard bridge. Motion data itself is not decoded yet (sent as zeros).
Configure in mappings.json:
"dsu": { "enabled": true, "host": "127.0.0.1", "port": 26760 }or toggle it from the menubar (DSU server item โ the checkmark shows it's listening). Button mapping on the DSU side is positional: AโCircle, BโCross, XโTriangle, YโSquare, โโShare, +โOptions, HomeโPS, CaptureโTouch. GL/GR/C have no DSU equivalent.
switch2bridge-macos/
โโโ Switch2Bridge.py # Menubar app (BLE client + keyboard bridge)
โโโ dsu_server.py # DSU (cemuhook) server โ analog output for emulators
โโโ setup_app.py # py2app configuration
โโโ build_dmg.sh # Automated build script (.app + DMG)
โโโ requirements.txt # Python dependencies
โโโ tests/
โ โโโ test_bridge.py # Headless tests (mappings, key dispatch, BLE lifecycle)
โโโ AppIcon.icns # Application icon (used by py2app)
โโโ LICENSE
โโโ README.md
โโโโโโโโโโโโโโโโโโโ BLE โโโโโโโโโโโโโโโโโโโ pynput โโโโโโโโโโโโโโโโโโโ
โ Switch 2 Pro โ โโโโโโโโโโโถ โ Python Bridge โ โโโโโโโโโโโถ โ Ryujinx โ
โ Controller โ (bleak) โ โ (keyboard) โ (Keyboard) โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
- BLE Connection โ uses
bleakto connect directly via Bluetooth LE - Input Parsing โ decodes the proprietary Nintendo protocol
- Keyboard Simulation โ uses
pynputto simulate key presses - Ryujinx โ reads keyboard input as if from a physical keyboard
| UUID | Purpose |
|---|---|
7492866c-ec3e-4619-8258-32755ffcc0f9 |
Input reports (notifications) |
7492866c-ec3e-4619-8258-32755ffcc0f8 |
Output (LED, rumble โ not working) |
Not every controller exposes that input UUID (see #15), so the bridge treats it as a first guess only:
- the UUID pinned in
mappings.json(ble.input_char), if it is present on the device; - otherwise the documented UUID above;
- otherwise it probes every other notifiable characteristic โ vendor UUIDs first, SIG-assigned ones last โ subscribing for 3 s each and keeping the first one that streams reports of at least 11 bytes (enough to decode buttons and both sticks). The winner is written back to
ble.input_char, so later connections skip the probing.
Every connection also logs the full GATT table (GATT: N characteristic(s): โฆ) to ~/Library/Logs/Switch2Bridge/bridge.log โ that line is what a bug report needs when a controller can't be identified.
- The controller never appears in System Settings โ Bluetooth โ that's expected, and not a failure. This bridge is a BLE client: there is no system-level pairing, so macOS will never list the controller. The only place to watch is the app's menubar icon (๐ โ ๐ข).
- "Controller not found" โ make sure the controller is not paired with a console nearby (unpair it or put the console to sleep far away). Click Connect Controller first โ the search now runs for 30 s โ then hold the small pair button on the back until the LEDs sweep back and forth.
- No Bluetooth prompt ever appeared (run-from-source) โ the permission belongs to Terminal/Python, not the app. Check
System Settings โ Privacy & Security โ Bluetoothand enable Terminal, then relaunch. Without it, scans silently find nothing. - "Characteristic โฆ was not found" / "no readable input characteristic" โ the controller connected but its input-report characteristic isn't where the bridge expects it. Since v1.2.4 the bridge probes the alternatives automatically and remembers what worked, so retry once โ and move the sticks while it says it is identifying the controller, in case that revision only reports on change. If it still gives up, the log now contains a
GATT:line listing every service and characteristic of your controller โ attach it to an issue. You can also pin a UUID yourself:"ble": { "input_char": "โฆ" }inmappings.json. - Menubar says ๐ข connected but inputs don't reach the emulator โ macOS Accessibility permission is missing. Grant it in System Settings โ Privacy & Security โ Accessibility, then relaunch the app. (The app should also pop an alert about this on first launch.)
- Logs โ written to
~/Library/Logs/Switch2Bridge/bridge.log. Open a terminal andtail -fit to watch what's happening in real time.
| Feature | Status | Notes |
|---|---|---|
| Buttons | โ Working | All buttons mapped |
| C button | ๐งช Experimental | Parsed as byte 4, bit 0x02 โ please report if it doesn't fire |
| Analog Sticks | โ Analog via DSU | Full 12-bit analog through the DSU server (Dolphin/Cemu). The keyboard bridge remains digital: thresholded (with hysteresis) to 8 directions (WASD/IJKL). |
| LED Control | โ Not working | Output characteristic doesn't respond |
| Rumble | โ Not working | Same issue |
| Motion/Gyro | DSU motion fields are sent (as zeros) โ the gyro bytes in the BLE report are not decoded yet | |
| Native HID | โ Not possible | Would require DriverKit (kernel-level) |
Contributions welcome! Areas that need work:
- LED/Rumble โ figure out the output protocol (likely a Joy-Con-style handshake)
- Motion controls โ decode gyro/accelerometer data
- True analog โ virtual HID device via DriverKit
- Cross-platform โ Linux/Windows ports
- Aurรฉlien Desert โ reverse engineering & implementation
- Claude (Anthropic) โ development assistance
- Inspired by SPro2Win (Windows)
- Protocol reference from Nintendo Switch Reverse Engineering
MIT License โ see LICENSE for details.
โญ Star this repo if it helped you!
First macOS BLE bridge for Switch 2 Pro Controller โ January 2026