Merge pull request #432 from outis1one/claude/wolf-pair-port-conflict-7nz8qg

Claude/wolf pair port conflict 7nz8qg
This commit is contained in:
Outis
2026-09-03 11:51:29 -04:00
committed by GitHub
+219 -1
View File
@@ -323,6 +323,8 @@ EOF
echo " symlinked to Dolphin_Emulator.AppImage so ES-DE's own find-rules can see it)"
echo " - Offer AntiMicroX (gamepad -> keyboard/mouse remapping), scoped to just TI-99/4A"
echo " and Wii U (Cemu) in ES-DE via a second, opt-in 'Alternative emulators' command"
echo " - Expose Wolf's REST API socket to the host (WOLF_SOCKET_PATH + /var/run/wolf mount)"
echo " so './manage.sh controllers' can force distinct pad types per controller slot"
return 0
fi
@@ -1496,11 +1498,21 @@ services:
- WOLF_INTERNAL_MAC=${WOLF_MAC}
- WOLF_RENDER_NODE=${WOLF_RENDER_NODE}
- LD_LIBRARY_PATH=/usr/nvidia/lib:/usr/nvidia/lib32
# Exposes Wolf's REST API socket at this same path on the HOST (matches
# Wolf's own docs' recommended pattern exactly) — without this it only
# exists inside the container at its default \$XDG_RUNTIME_DIR-relative
# path, unreachable from manage.sh. Used by 'manage.sh controllers' to
# set per-client controllers_override (see that command's own comments
# for why this exists — distinguishing multiple same-model virtual
# gamepads, e.g. two Wii U Pro Controllers, so games like Cemu can
# actually tell them apart).
- WOLF_SOCKET_PATH=/var/run/wolf/wolf.sock
volumes:
- \${WOLF_STATE_DIR}:\${WOLF_STATE_DIR}:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
- /dev/:/dev/:rw
- /run/udev:/run/udev:rw
- /var/run/wolf:/var/run/wolf:rw
- nvidia-driver-vol:/usr/nvidia:rw
devices:
- /dev/dri
@@ -3277,7 +3289,7 @@ PYEOF
cat > "$COMP_FILE" << 'COMPEOF'
_manage_wolf_complete() {
local cur="${COMP_WORDS[COMP_CWORD]}"
local commands="start stop restart logs status pin update apps cores reorder
local commands="start stop restart logs status pin controllers update apps cores reorder
add-web ge-proton games setup-swbf2 fix-ea-game wait-ea-app
install-ea-app diagnose-ea fix-perms install-completion backup"
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
@@ -3292,6 +3304,123 @@ COMPEOF
echo "Open a new terminal (or run: source \"$COMP_FILE\"), then:"
echo " ./manage.sh <TAB><TAB> shows all commands"
;;
controllers)
# Fixes a real, confirmed root cause — not a bug in Wolf or in any
# single emulator/game: every virtual gamepad Wolf creates of the
# SAME type (e.g. two Wii U Pro Controllers) gets the IDENTICAL SDL
# GUID, because an SDL GUID identifies a controller MODEL, not a
# physical instance — completely normal, expected behavior (two
# real identical physical controllers behave the exact same way).
# Some apps' own controller-picker UI just doesn't reliably tell
# apart two same-GUID devices though (confirmed with Cemu: the
# first controller ends up driving every player). Wolf's own
# per-client controllers_override setting (its docs' "Override the
# default joypad mapping" section) sidesteps this at the root:
# force each controller SLOT to a DIFFERENT pad type (e.g. slot 1 =
# Xbox, slot 2 = PlayStation) so their vendor/product IDs — and
# therefore their SDL GUIDs — genuinely differ. This isn't
# Cemu-specific: it fixes controller disambiguation the same way
# for every system/app that reads SDL joystick GUIDs.
#
# Needs Wolf's REST API socket, which is why the wolf service now
# sets WOLF_SOCKET_PATH and bind-mounts /var/run/wolf to the host
# (Wolf's own docs' recommended pattern for host access). Endpoint
# shapes below are confirmed against Wolf's real OpenAPI schema
# (docs/modules/dev/partials/spec.json in its own repo) — NOT the
# docs PAGE's own example curl command, which is stale: it shows
# "PATCH .../clients/<id>/settings", but the real, current endpoint
# is "POST /api/v1/clients/settings" with client_id in the JSON
# body, not the URL path.
SOCK="/var/run/wolf/wolf.sock"
if [ ! -S "$SOCK" ]; then
echo "Wolf's API socket isn't up at $SOCK yet."
echo " sudo ./setup.sh wolf # regenerates docker-compose.yml with the socket mount"
echo " docker compose up -d # recreates the wolf container so it takes effect"
exit 1
fi
CLIENTS_JSON=$(curl -fsS --unix-socket "$SOCK" http://localhost/api/v1/clients 2>/dev/null)
if [ -z "$CLIENTS_JSON" ]; then
echo "Could not reach Wolf's API — is Wolf running? (./manage.sh status)"
exit 1
fi
CLIENT_IDS=()
while IFS= read -r cid; do CLIENT_IDS+=("$cid"); done < <(echo "$CLIENTS_JSON" | python3 -c "
import json, sys
for c in json.load(sys.stdin)['clients']:
print(c['client_id'])
")
if [ "${#CLIENT_IDS[@]}" -eq 0 ]; then
echo "No paired Moonlight clients yet — pair one first (./manage.sh pin), then re-run this."
exit 1
fi
echo ""
echo "Paired clients:"
echo "$CLIENTS_JSON" | python3 -c "
import json, sys
d = json.load(sys.stdin)
for i, c in enumerate(d['clients']):
ov = c['settings'].get('controllers_override') or []
print(f\" {i+1}) {c['client_id']} (current: {ov if ov else 'none - auto-detect'})\")
"
if [ "${#CLIENT_IDS[@]}" -eq 1 ]; then
CLIENT_ID="${CLIENT_IDS[0]}"
echo ""
echo "Only one paired client — using it."
else
echo ""
read -r -p "Which client [1-${#CLIENT_IDS[@]}]: " PICK
if ! [[ "$PICK" =~ ^[0-9]+$ ]] || [ "$PICK" -lt 1 ] || [ "$PICK" -gt "${#CLIENT_IDS[@]}" ]; then
echo "Invalid selection."
exit 1
fi
CLIENT_ID="${CLIENT_IDS[$((PICK-1))]}"
fi
echo ""
read -r -p "How many controller slots to force a type for [2]: " NSLOTS
NSLOTS="${NSLOTS:-2}"
if ! [[ "$NSLOTS" =~ ^[0-9]+$ ]] || [ "$NSLOTS" -lt 1 ]; then
echo "Invalid number."
exit 1
fi
OVERRIDE_TYPES=()
for i in $(seq 1 "$NSLOTS"); do
read -r -p " Slot $i type (AUTO/XBOX/PS/NINTENDO) [AUTO]: " T
T="${T:-AUTO}"
T="$(echo "$T" | tr '[:lower:]' '[:upper:]')"
case "$T" in
AUTO|XBOX|PS|NINTENDO) ;;
*) echo " Unrecognized '$T' — using AUTO."; T="AUTO" ;;
esac
OVERRIDE_TYPES+=("$T")
done
BODY=$(python3 - "$CLIENT_ID" "${OVERRIDE_TYPES[@]}" << 'BODYPY'
import json, sys
client_id = sys.argv[1]
overrides = sys.argv[2:]
print(json.dumps({"client_id": client_id, "app_state_folder": None,
"settings": {"controllers_override": overrides}}))
BODYPY
)
RESP=$(curl -fsS --unix-socket "$SOCK" -X POST http://localhost/api/v1/clients/settings \
-H 'Content-Type: application/json' -d "$BODY")
echo "$RESP" | python3 -c "
import json, sys
try:
d = json.load(sys.stdin)
print('Updated.' if d.get('success') else 'Failed: ' + json.dumps(d))
except Exception:
print('Unexpected response from Wolf API.')
"
echo ""
echo "Reconnect (or fully restart) the Moonlight stream for this to take effect —"
echo "it's applied when Wolf creates each controller's virtual pad, not retroactively"
echo "to one that already exists in an open session."
;;
pin)
# Wolf logs: "Insert pin at http://SOMEIP:47989/pin/#HEXHASH"
# Extract just the hash fragment and build URLs for every interface
@@ -3329,6 +3458,7 @@ COMPEOF
echo " ./manage.sh logs - Follow Wolf logs"
echo " ./manage.sh status - Show Wolf + app containers"
echo " ./manage.sh pin - Show recent Moonlight pairing PIN link"
echo " ./manage.sh controllers - Force distinct pad types per controller slot (multi-controller fix)"
echo " ./manage.sh update - Pull latest Wolf image and restart"
echo " ./manage.sh apps - Add / update game launchers in Wolf"
echo " ./manage.sh cores [all|common] - Download/refresh RetroArch cores (retro ROMs)"
@@ -3869,6 +3999,7 @@ cd $WOLF_DIR
./manage.sh restart # restart
./manage.sh logs # live logs
./manage.sh status # container status
./manage.sh controllers # force distinct pad types per controller slot (multi-controller fix)
./manage.sh update # pull latest image and restart
./manage.sh apps # add / update game launchers
./manage.sh cores # download/refresh RetroArch cores (retro ROMs)
@@ -3886,6 +4017,81 @@ cd $WOLF_DIR
Run \`./manage.sh install-completion\` once, then re-open your shell (or
\`source ~/.bashrc\`). After that, \`./manage.sh <TAB><TAB>\` lists all commands.
## Multiple controllers (same game/emulator can't tell them apart)
**Symptom:** two or more controllers connected through the same Moonlight
session, but the game/emulator only ever sees one — the first controller
ends up driving every player, or a second controller's own binding just
doesn't do anything (first reported with Cemu/Wii U, but this isn't
Cemu-specific — it affects any system/app that reads SDL joystick GUIDs).
**Why.** Every virtual gamepad Wolf creates of the *same type* (e.g. two
Wii U Pro Controllers) gets the **identical SDL GUID** — a GUID identifies
a controller *model*, not a physical instance, so this is actually normal,
expected SDL behavior (two real identical physical controllers behave the
exact same way). Some apps' own controller-picker UI just doesn't reliably
tell apart two same-GUID devices though.
**Fix: force each controller slot to a *different* pad type**, so their
vendor/product IDs — and therefore their SDL GUIDs — genuinely differ:
\`\`\`bash
cd $WOLF_DIR && ./manage.sh controllers
\`\`\`
Picks your paired Moonlight client (auto-selected if there's only one),
then asks how many controllers to configure and what type to force each
one to. There are only **3 concrete types** (confirmed against Wolf's own
virtual-pad source, \`inputtino\`) — not one per real controller brand:
- \`XBOX\` → an **Xbox One** controller specifically (not Series/360)
- \`PS\` → a **PlayStation 5 DualSense** specifically (no separate PS4 option)
- \`NINTENDO\` → a **Switch Pro Controller**
- \`AUTO\` → auto-detects from whatever the physical controller reports
(this is what causes the collision in the first place — e.g. every
8BitDo pad set to "Switch mode" reports as Nintendo, so AUTO gives all
of them the identical GUID)
e.g. controller 1 = \`NINTENDO\` (matches an 8BitDo pad's native Switch
mode), controller 2 = \`XBOX\`, controller 3 = \`PS\`. This calls Wolf's own
REST API (\`controllers_override\`, per Wolf's own docs' "Override the
default joypad mapping" section) rather than hand-editing \`config.toml\`.
Forcing a non-matching type doesn't break any buttons — Moonlight still
translates your controller's actual button presses onto whichever virtual
pad type you pick — it just means that controller's on-screen button
prompts (e.g. "press ✕") won't visually match what's printed on the
physical pad. Purely cosmetic.
**Reconnect (or fully restart) the Moonlight stream afterward** — this
applies when Wolf creates each controller's virtual pad for a *new*
session, not retroactively to one already open.
If \`./manage.sh controllers\` says the API socket isn't up yet, re-run
\`sudo ./setup.sh wolf\` (regenerates \`docker-compose.yml\` with the socket
mount this command needs) and then \`docker compose up -d\` to recreate the
Wolf container.
### 4 controllers (Cemu / Wii U games)
Wii U hardware itself tops out at 4 local players, and everything upstream
supports at least that many: Cemu's own Input Settings allows up to 8
controller slots (confirmed against Cemu's own wiki — well above what any
Wii U game actually uses), and Wolf's wire protocol tracks controller
slots via a bitmask with no hardcoded 4-controller limit (confirmed
against its own \`control/input_handler.cpp\` source) — the practical
ceiling is whatever your Moonlight *client* supports (commonly 4 on
PC/iOS).
The catch: with only 3 concrete forced types (above), controller 4 can't
get its own guaranteed-unique GUID — it has to reuse \`XBOX\`, \`PS\`, or
\`NINTENDO\` and land back in the same ambiguous-GUID situation with
whichever one it matches.
**Workaround for the 4th controller: bind it in Cemu one at a time.**
Disconnect (or just don't touch) every controller except the one you're
currently binding, then use Cemu's own "press a button to detect" step
for that slot. With only one Wolf virtual pad actually emitting input at
that moment, there's nothing for Cemu's picker to confuse it with,
regardless of which type it's sharing a GUID with. This is a reasoned
workaround, not one confirmed working live — if it doesn't pan out,
that's useful to know.
## EA games (Battlefront II 2017, etc.)
EA titles require GE-Proton and the EA App to be installed inside the Wine
prefix. The EA App handles authentication — without it, the game launches
@@ -4110,6 +4316,9 @@ time Cemu launches from ES-DE too.
repo, not just reconnecting) reliably cleared it in testing, giving a
fresh PulseAudio session to work with. Reconnect to a fresh Desktop
session afterward and try again.
6. **Adding a second controller and the first one ends up driving both
characters?** That's not a Cemu-specific bug — see
"Multiple controllers" above (\`./manage.sh controllers\`).
ROMs are mounted at \`/ROMs\` inside Desktop too, so Cemu's own File → Load
can browse straight to \`/ROMs/wiiu/\` without needing ES-DE at all.
@@ -4159,6 +4368,15 @@ against that same doc): Alt+1 DEL, Alt+2 INS, Alt+3 ERASE, Alt+4 CLEAR,
Alt+5 BEGIN, Alt+6 PROC'D, Alt+7 AID, Alt+8 REDO, Alt+9 BACK, Alt+= QUIT.
## AntiMicroX: extra gamepad buttons for TI-99/4A and Wii U
**Status: confirmed NOT working for TI-99/4A in live testing** (remapped
buttons didn't do anything); Wii U untested. Root cause not yet found —
possible suspects are the \`--eventgen uinput\` backend not actually
injecting into ES-DE's Sway session from inside that same container, or
AntiMicroX's own \`--hidden\` mode needing a display it doesn't have. Not
under active investigation right now — see "Multiple controllers" above
for the actively-maintained fix for the "second controller doesn't work"
problem instead, which doesn't depend on AntiMicroX at all.
If you said yes to the AntiMicroX prompt during install, both TI-99/4A and
Wii U (Cemu) get a second, separately-labeled launch command in ES-DE —
pick it via ES-DE's own **Alternative emulators** option (per-game or