Conversation
RetroArch on macOS has no SDL input driver, so the SDL bridge can't reach it. Add an optional ControllerOutputSink that publishes controller state over localhost UDP in the libretro remote-gamepad format, which RetroArch's built-in Network Gamepad consumes: one port per player (base port + slot, default 55400-55403), with a Configuration toggle and base-port field. Off by default. Naming is generic (sink, settings keys, UI) with RetroArch as a hint on the toggle and in the README directions. The receiver drains one datagram per player per frame, so the sink sends diffs only, paced at 60/s, button edges before analog, and re-asserts held state every 2 s so a receiver launched mid-game converges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
|
vialoh
marked this pull request as ready for review
August 23, 2026 08:48
Only diffs are ever sent, so a datagram dropped by a transient sendto failure (ENOBUFS etc.) was lost for good — a lost button release left the direction held until the button was pressed again. Commit sent-state only on success and retry on the next tick, logging failures at most once a second.
The refresh cleared sent-bits with sent &= want, which also cleared the bit of a button released since the previous tick — so its release was never sent and the direction stayed held in the receiver until the button was pressed again. Clear only the bits of currently held buttons (sent &= ~want) so pending releases still go out.
The periodic refresh forced a resend of held buttons by clearing their sent-bits — but sent-bits are also the only record of what the receiver currently holds. A button released between the refresh and the resend then looked already-released (want 0, sent 0), so no release was sent and the direction stayed held. Keep sent* as the true receiver model and track pending re-asserts in separate refresh* masks, sent after edges and analog changes.
Owner
|
I am just getting back to this project. Will review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hey @Peterksharma! Sorry about Claude's verbosity in the original post (below).
Long story short, yesterday I asked Claude (Fable) if it could help me get
switch2macworking with various emulators and eventually arrived at using RetroArch's network gamepad option. This makes it super easy to use it with any of the 200+ emulator cores supported by RetroArch while you/we wait for Apple's approval on thecom.apple.developer.hid.virtual.deviceentitlement.I actually haven't bothered building the Swift version of this as I'm currently on a semi-fresh MacOS install and haven't set up that dev environment yet, but this PR is based on a Python script that Fable one-shotted for me that converts your original SDL UDP implementation to RetroArch's network gamepad input in real-time... and it works perfectly, so I assume Fable's proper Swift code works perfectly as well. I'm happy to iterate on this further if necessary!
I appreciate you open sourcing your work on this. You made it super easy to get things working and allowed me to reminisce with some old consoles using a proper controller. Thanks!
What
Adds an optional "network gamepad" output sink that publishes controller
state over localhost UDP in the libretro remote-gamepad format — which
RetroArch's built-in Network Gamepad (a.k.a. Remote RetroPad) consumes
— with a toggle and base-port field in the dashboard's Configuration
section. Off by default.
RetroArch is probably the most common emulator people will want to use
these controllers with, but its macOS build has no SDL input driver (only
cocoa,mfiandhid), so the patched-SDL bridge can't reach it — andsince OpenEmu is also IOKit-only, it can't either. RetroArch does, however,
ship a network input path on every platform: it listens on UDP ports
55400+N for
struct remote_messagepackets and injects them as RetroPadstate for player N. This sink speaks that protocol.
How it works
Output/NetworkGamepadSink.swiftimplementingControllerOutputSink,modelled on
UDPHub(same Darwin socket + dispatch-queue style).struct remote_messagefrominput/input_driver.h(20 bytes LE:i32 port, device, index, id; u16 state; 2 pad). Verified against RetroArch master.both sticks as analog (Y inverted to RetroPad's +Y-down). GameCube-style
analog triggers (
lt/rt≥ 128) also register as L2/R2. Home, Capture,C, GL, GR have no RetroPad slot and are left to the existing remapper.
rest, so the sink sends diffs only, paced at 60/s per player, button
edges before analog updates, with axes quantized so stick jitter can't
starve button events. Held state is re-asserted every 2 s so a
RetroArch launched mid-hold converges; a disconnect (or toggling the
feature off) releases everything. The timer only runs while something
is pending or held, so the sink is free when idle or disabled.
AppConfiggainsnetworkGamepadEnabled/networkGamepadBasePort(UserDefaults, read on the sink's queue like the other settings).
(RetroArch)") — the protocol is libretro's, but nothing else in the code
is tied to one consumer; RetroArch is named in the README directions and
as a hint on the toggle.
architecture diagram updated.
Limitations
authentication (noted in the README).