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

Claude/wolf pair port conflict 7nz8qg
This commit is contained in:
Outis
2026-09-03 13:57:42 -04:00
committed by GitHub
+249 -8
View File
@@ -325,6 +325,7 @@ EOF
echo " and Wii U (Cemu) in ES-DE via a second, opt-in 'Alternative emulators' command" 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 " - 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" echo " so './manage.sh controllers' can force distinct pad types per controller slot"
echo " - Force ES-DE's 'Run in background' off durably (mounts + writes esde-settings/es_settings.xml)"
return 0 return 0
fi fi
@@ -1391,6 +1392,49 @@ PS2XMLPY
log_success "PS2: added PCEE2 as the default RetroArch core (custom_systems/es_systems.xml) — ES-DE's" log_success "PS2: added PCEE2 as the default RetroArch core (custom_systems/es_systems.xml) — ES-DE's"
log_info "own build doesn't ship it yet (added upstream in the not-yet-released 3.5.0)" log_info "own build doesn't ship it yet (added upstream in the not-yet-released 3.5.0)"
# ── ES-DE: force "Run in background" off ────────────────────────────────
# ES-DE's own compiled default for this IS already off (confirmed against
# its real Settings.cpp: mBoolMap["RunInBackground"] = {false, false}) —
# this write exists purely so that default stays durable and explicit
# rather than relying on it never having been toggled. Real, confirmed
# cause of a genuine bug otherwise: with it on, ES-DE keeps running and
# listening to every controller even after a game/emulator has launched
# and taken visual focus — a second controller's input can still reach
# ES-DE's own menu in the background and launch (and start playing audio
# for) a completely different game while the first one is still up
# front. ES-DE's own USERGUIDE.md explicitly names this failure mode
# ("make sure that the setting Run in background... is disabled").
# Confirmed live: this affects any system, not just Cemu.
#
# es_settings.xml is the user's OWN full settings state (theme, scraper
# prefs, everything) — unlike es_systems.xml this is never a full
# rewrite, only a surgical strip-and-reappend of this one <bool> entry,
# same idempotent pattern as everywhere else in this file. Written as a
# plain top-level element with no <settings> wrapper, matching what
# ES-DE's own Settings::saveFile() actually produces today (confirmed
# against its source — the "wrap everything in <settings>" format is
# loader-side forward-compatibility for a future ES-DE release, not
# what gets written now); either format loads fine either way.
mkdir -p "$GAME_STORAGE_DIR/esde-settings"
_ESDE_SETTINGS="$GAME_STORAGE_DIR/esde-settings/es_settings.xml"
backup_if_exists "$_ESDE_SETTINGS"
python3 - "$_ESDE_SETTINGS" << 'ESDESETTINGSPY'
import re, sys
path = sys.argv[1]
try:
with open(path) as f:
content = f.read()
except FileNotFoundError:
content = ''
content = re.sub(r'[ \t]*<bool name="RunInBackground" value="[^"]*"[ \t]*/>[ \t]*\n?', '', content)
line = ' <bool name="RunInBackground" value="false" />\n'
content = (content.rstrip('\n') + '\n' + line) if content.strip() else line
with open(path, 'w') as f:
f.write(content)
ESDESETTINGSPY
chown "$ACTUAL_USER:$ACTUAL_USER" "$_ESDE_SETTINGS"
log_success "ES-DE: 'Run in background (while game is launched)' forced off (esde-settings/es_settings.xml)"
# ── App selection ───────────────────────────────────────────────────────── # ── App selection ─────────────────────────────────────────────────────────
echo "" echo ""
echo "═══════════════════════════════════════════════════════" echo "═══════════════════════════════════════════════════════"
@@ -1941,7 +1985,23 @@ CATALOG = {
f'{games}/retroarch:/home/retro/.config/retroarch:rw', f'{games}/retroarch:/home/retro/.config/retroarch:rw',
f'{games}/emulators:/mnt/games/emulators:rw', f'{games}/emulators:/mnt/games/emulators:rw',
f'{games}/emulators:/home/retro/Applications:rw', f'{games}/emulators:/home/retro/Applications:rw',
f'{games}/esde-custom-systems:/home/retro/ES-DE/custom_systems:rw'], f'{games}/esde-custom-systems:/home/retro/ES-DE/custom_systems:rw',
# ~/ES-DE (settings, gamelists, downloaded_media, logs) has
# NO mount at all otherwise (confirmed against ES-DE's own
# source and GOW's es-de startup.sh: getAppDataDirectory()
# is a plain $HOME/ES-DE, no XDG redirect) — Wolf normally
# reuses the same app container across sessions rather than
# recreating it each time (confirmed against Wolf's own
# docker.cpp: it only removes the container on session end
# if WOLF_STOP_CONTAINER_ON_EXIT=TRUE, which this repo never
# sets), so this doesn't get wiped every reconnect — but it
# IS lost on any real reinstall/container recreate, unlike
# everything else here which lives on the game drive. Only
# mounting settings/ specifically (not the whole ~/ES-DE
# tree) keeps this additive and non-breaking alongside the
# existing custom_systems mount above — gamelists/scraped
# media durability is a separate, not-yet-done improvement.
f'{games}/esde-settings:/home/retro/ES-DE/settings:rw'],
env=ESDE_ENV, cap_add=STD_CAP, security_opt=[], ipc_mode='host', env=ESDE_ENV, cap_add=STD_CAP, security_opt=[], ipc_mode='host',
ulimits=[], privileged=False, ulimits=[], privileged=False,
devices=['/dev/uinput:/dev/uinput'], devices=['/dev/uinput:/dev/uinput'],
@@ -3415,14 +3475,17 @@ print(next(iter(ids)) if len(ids) == 1 else '')
if [ -n "$ACTIVE_CLIENT_ID" ]; then if [ -n "$ACTIVE_CLIENT_ID" ]; then
CLIENT_ID="$ACTIVE_CLIENT_ID" CLIENT_ID="$ACTIVE_CLIENT_ID"
echo "" echo ""
echo "Exactly one client is actively streaming right now — using that one." echo "Exactly one Moonlight session (device/TV) is actively streaming right"
echo "now — using that one. (This is about which SCREEN/DEVICE you're"
echo "configuring, not how many controllers are plugged into it — that's"
echo "the next question.)"
elif [ "${#CLIENT_IDS[@]}" -eq 1 ]; then elif [ "${#CLIENT_IDS[@]}" -eq 1 ]; then
CLIENT_ID="${CLIENT_IDS[0]}" CLIENT_ID="${CLIENT_IDS[0]}"
echo "" echo ""
echo "Only one paired client — using it." echo "Only one paired Moonlight device — using it."
else else
echo "" echo ""
read -r -p "Which client [1-${#CLIENT_IDS[@]}]: " PICK read -r -p "Which Moonlight session/device [1-${#CLIENT_IDS[@]}]: " PICK
if ! [[ "$PICK" =~ ^[0-9]+$ ]] || [ "$PICK" -lt 1 ] || [ "$PICK" -gt "${#CLIENT_IDS[@]}" ]; then if ! [[ "$PICK" =~ ^[0-9]+$ ]] || [ "$PICK" -lt 1 ] || [ "$PICK" -gt "${#CLIENT_IDS[@]}" ]; then
echo "Invalid selection." echo "Invalid selection."
exit 1 exit 1
@@ -3430,8 +3493,64 @@ print(next(iter(ids)) if len(ids) == 1 else '')
CLIENT_ID="${CLIENT_IDS[$((PICK-1))]}" CLIENT_ID="${CLIENT_IDS[$((PICK-1))]}"
fi fi
# Wolf's own logs record "Creating <TYPE> joypad for controller <N>"
# every time it creates a virtual pad (confirmed live against real
# log output) — polling this gives real, human-readable context per
# slot instead of asking the user to guess or watch logs by hand
# themselves. Wolf's own wording is 0-indexed ("controller 0",
# "controller 1"); every prompt in this tool is deliberately
# 1-indexed instead ("1st controller", "2nd controller"...) —
# confirmed live that mixing the two ("exactly one CLIENT is
# active" read as "exactly one CONTROLLER" right above a
# 0-indexed-sounding prompt) is a real, easy misread. "1st
# controller" here always means Wolf's "controller 0", "2nd" means
# "controller 1", and so on — stated explicitly at every prompt so
# the two numbering schemes never have to be reconciled in your head.
#
# NOTE: this only tells you what TYPE Wolf most recently created
# for a given slot NUMBER — it can't tell you which PHYSICAL
# controller that was, and it can't let you reassign a physical
# controller to a different slot number. Which controller becomes
# slot 0 vs. slot 1 is decided entirely by Moonlight (the client),
# upstream of Wolf, based on connection order — not something this
# override (or Wolf's API at all) can control.
_ordsuffix() {
case "$1" in
1) echo "st" ;;
2) echo "nd" ;;
3) echo "rd" ;;
*) echo "th" ;;
esac
}
echo "" echo ""
read -r -p "How many controller slots to force a type for [2]: " NSLOTS echo "Checking Wolf's logs for controllers it has already created a pad for..."
declare -A LAST_SEEN_TYPE
while IFS=$'\t' read -r cnum ctype; do
[ -n "$cnum" ] && LAST_SEEN_TYPE["$cnum"]="$ctype"
done < <(docker compose logs wolf 2>/dev/null | python3 -c "
import re, sys
for line in sys.stdin:
m = re.search(r'Creating (\w+) joypad for controller (\d+)', line)
if m:
print(f'{m.group(2)}\t{m.group(1).upper()}')
")
if [ "${#LAST_SEEN_TYPE[@]}" -gt 0 ]; then
echo "Last type Wolf created, per controller (most recent reconnect wins):"
for cnum in $(printf '%s\n' "${!LAST_SEEN_TYPE[@]}" | sort -n); do
_n=$((cnum + 1))
echo " Your ${_n}$(_ordsuffix "$_n") controller (Wolf calls it \"controller $cnum\"): last seen as ${LAST_SEEN_TYPE[$cnum]}"
done
else
echo "No controllers found in Wolf's logs yet — connect them and start a"
echo "stream first if you want this list populated (not required, you can"
echo "still answer blind below)."
fi
echo ""
echo "Now, separately: how many physical GAMEPADS/CONTROLLERS do you have"
echo "connected to that device right now (this tool can't detect that —"
echo "count your own controllers)?"
read -r -p "Number of controllers [2]: " NSLOTS
NSLOTS="${NSLOTS:-2}" NSLOTS="${NSLOTS:-2}"
if ! [[ "$NSLOTS" =~ ^[0-9]+$ ]] || [ "$NSLOTS" -lt 1 ]; then if ! [[ "$NSLOTS" =~ ^[0-9]+$ ]] || [ "$NSLOTS" -lt 1 ]; then
echo "Invalid number." echo "Invalid number."
@@ -3440,8 +3559,16 @@ print(next(iter(ids)) if len(ids) == 1 else '')
OVERRIDE_TYPES=() OVERRIDE_TYPES=()
for i in $(seq 1 "$NSLOTS"); do for i in $(seq 1 "$NSLOTS"); do
read -r -p " Slot $i type (AUTO/XBOX/PS/NINTENDO) [AUTO]: " T cnum=$((i - 1))
T="${T:-AUTO}" SEEN="${LAST_SEEN_TYPE[$cnum]:-}"
DEFAULT="${SEEN:-AUTO}"
SUF="$(_ordsuffix "$i")"
if [ -n "$SEEN" ]; then
read -r -p " Your ${i}${SUF} controller (controller $cnum, last seen as $SEEN) — force to which type? (AUTO/XBOX/PS/NINTENDO) [$DEFAULT]: " T
else
read -r -p " Your ${i}${SUF} controller (controller $cnum, not seen yet) — force to which type? (AUTO/XBOX/PS/NINTENDO) [AUTO]: " T
fi
T="${T:-$DEFAULT}"
T="$(echo "$T" | tr '[:lower:]' '[:upper:]')" T="$(echo "$T" | tr '[:lower:]' '[:upper:]')"
case "$T" in case "$T" in
AUTO|XBOX|PS|NINTENDO) ;; AUTO|XBOX|PS|NINTENDO) ;;
@@ -3603,7 +3730,23 @@ CATALOG = {
f'{games}/retroarch:/home/retro/.config/retroarch:rw', f'{games}/retroarch:/home/retro/.config/retroarch:rw',
f'{games}/emulators:/mnt/games/emulators:rw', f'{games}/emulators:/mnt/games/emulators:rw',
f'{games}/emulators:/home/retro/Applications:rw', f'{games}/emulators:/home/retro/Applications:rw',
f'{games}/esde-custom-systems:/home/retro/ES-DE/custom_systems:rw'], f'{games}/esde-custom-systems:/home/retro/ES-DE/custom_systems:rw',
# ~/ES-DE (settings, gamelists, downloaded_media, logs) has
# NO mount at all otherwise (confirmed against ES-DE's own
# source and GOW's es-de startup.sh: getAppDataDirectory()
# is a plain $HOME/ES-DE, no XDG redirect) — Wolf normally
# reuses the same app container across sessions rather than
# recreating it each time (confirmed against Wolf's own
# docker.cpp: it only removes the container on session end
# if WOLF_STOP_CONTAINER_ON_EXIT=TRUE, which this repo never
# sets), so this doesn't get wiped every reconnect — but it
# IS lost on any real reinstall/container recreate, unlike
# everything else here which lives on the game drive. Only
# mounting settings/ specifically (not the whole ~/ES-DE
# tree) keeps this additive and non-breaking alongside the
# existing custom_systems mount above — gamelists/scraped
# media durability is a separate, not-yet-done improvement.
f'{games}/esde-settings:/home/retro/ES-DE/settings:rw'],
env=ESDE_ENV, cap_add=STD_CAP, security_opt=[], ipc_mode='host', env=ESDE_ENV, cap_add=STD_CAP, security_opt=[], ipc_mode='host',
ulimits=[], privileged=False, ulimits=[], privileged=False,
devices=['/dev/uinput:/dev/uinput'], devices=['/dev/uinput:/dev/uinput'],
@@ -4108,6 +4251,31 @@ once (Wolf doesn't dedupe repeated pairings of the same device) — every
duplicate of an active ID is tagged ACTIVE together, so it doesn't matter duplicate of an active ID is tagged ACTIVE together, so it doesn't matter
which one you'd have picked by hand. which one you'd have picked by hand.
**It labels controllers "1st"/"2nd"/"3rd"/"4th" — always meaning Wolf's own
"controller 0"/"controller 1"/"controller 2"/"controller 3".** Wolf's own
log wording is 0-indexed; this tool is deliberately 1-indexed throughout
so the two numbering schemes never have to be reconciled in your head
(confirmed live that mixing them — reading "exactly one client is
active" as "exactly one controller" right above a 0-indexed-sounding
prompt — is a real, easy misread).
Before asking anything, it polls \`docker compose logs wolf\` for every
\`Creating <TYPE> joypad for controller <N>\` line Wolf has ever logged
(most recent per slot wins) and shows you what it last saw, e.g.:
\`\`\`
Your 1st controller (Wolf calls it "controller 0"): last seen as NINTENDO
Your 2nd controller (Wolf calls it "controller 1"): last seen as XBOX
\`\`\`
— then uses that as the suggested default at each prompt, so if nothing's
changed you can just hit Enter through them. **This can't tell you which
physical controller became which slot, and it can't let you reassign
one** — which controller becomes slot 0 vs. slot 1 is decided entirely by
Moonlight (the client), based on connection order, upstream of Wolf and
this tool alike. If you need to know that for certain, connect your
controllers one at a time while following the log live
(\`docker compose logs -f wolf | grep -i controller\`) — you'll see
exactly which slot number each one becomes as it connects.
Then it asks how many controllers to configure and what type to force Then it 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 each one to. There are only **3 concrete types** (confirmed against Wolf's own
virtual-pad source, \`inputtino\`) — not one per real controller brand: virtual-pad source, \`inputtino\`) — not one per real controller brand:
@@ -4163,6 +4331,41 @@ 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, workaround, not one confirmed working live — if it doesn't pan out,
that's useful to know. that's useful to know.
## ES-DE launches a second game while one is already running
**Symptom** (confirmed live, not Cemu-specific — happens on any system):
a second controller's input reaches ES-DE's own menu in the background
and launches an entirely different game — you'll hear/see it start
playing underneath whatever already has focus.
**Cause:** ES-DE's own **"Run in background (while game is launched)"**
setting. When it's on, ES-DE keeps running and listening to every
controller even after something else has launched and taken visual
focus — ES-DE's own USERGUIDE.md names this exact failure mode directly.
ES-DE's compiled default for this is already off, but nothing durably
enforced that in this setup before now, so it's easy for it to have
gotten toggled on at some point and stick that way.
**This installer now forces it off automatically** (\`esde-settings/es_settings.xml\`,
written fresh on every install/reinstall) — no menu digging required. If
you're on an install from before this existed, re-run \`sudo ./setup.sh wolf\`
to pick it up, or set it by hand: **Main Menu → Other Settings → Run in
background (while game is launched) → off**.
**Why this needed its own mount, not just a one-time write:** \`~/ES-DE\`
(settings, gamelists, scraped artwork, logs) had no bind mount onto the
game drive at all before this — confirmed against ES-DE's own source
(\`getAppDataDirectory()\` is a plain \`$HOME/ES-DE\`, no XDG redirect) and
GOW's own \`es-de\` startup script. In normal day-to-day use this mostly
didn't matter, since Wolf reuses the same app container across sessions
rather than recreating it each time (confirmed against Wolf's own
\`docker.cpp\`: the container is only removed on session end if
\`WOLF_STOP_CONTAINER_ON_EXIT=TRUE\`, which this repo never sets) — but it
meant this setting, and everything else under \`~/ES-DE\`, wasn't safe
across an actual reinstall or container recreate the way roms/saves/BIOS
already are. Only \`settings/\` is mounted now (\`esde-settings/\`, additive,
doesn't touch the existing \`custom_systems\` mount) — gamelists and
scraped media durability is a related gap, not yet fixed.
## EA games (Battlefront II 2017, etc.) ## EA games (Battlefront II 2017, etc.)
EA titles require GE-Proton and the EA App to be installed inside the Wine 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 prefix. The EA App handles authentication — without it, the game launches
@@ -4391,6 +4594,44 @@ time Cemu launches from ES-DE too.
characters?** That's not a Cemu-specific bug — see characters?** That's not a Cemu-specific bug — see
"Multiple controllers" above (\`./manage.sh controllers\`). "Multiple controllers" above (\`./manage.sh controllers\`).
**If Input Settings' Save button is cut off the bottom of the screen**
(confirmed live: happens even maximized, at higher resolutions, and
closing the window doesn't save — Cemu's Input Settings dialog is a
fixed size, not resizable, and can genuinely be taller than the session's
viewport) — try moving the dialog first: hold **Alt** and **left-click-drag
anywhere inside it** (not just the titlebar) to reposition it upward, or
use xfwm4's own **Alt+F7** (grab-to-move, drop with click or Enter) if
that doesn't work. Either is a plain X11/XFCE window-manager trick, not
Cemu-specific, and usually the fastest fix.
**If that still doesn't work, or you'd rather skip the GUI entirely: edit
the controller profile file directly.** Cemu's per-slot controller config
isn't inside the Docker container at all — it's plain XML on the game
drive (Desktop and ES-DE both mount \`~/.config\` from the same place):
\`\`\`bash
GAME_DIR=\$(grep '^GAME_STORAGE_DIR=' $WOLF_DIR/.env | cut -d= -f2-)
ls "\$GAME_DIR/retro-home/Cemu/controllerProfiles/"
\`\`\`
File naming is 0-indexed the same way Wolf's own controller numbering is
(yet another place this comes up) — \`controller0.xml\` is Cemu's
**Controller 1**, \`controller1.xml\` is **Controller 2**, and so on.
Each holds one or more \`<controller>\` blocks (one per SDL device that's
ever been assigned to that slot, identified by its own \`<uuid>\`/
\`<display_name>\`) with a \`<mappings>\` list of \`<mapping>\`/\`<button>\`
pairs. **Confirmed against real captured profiles from different
controller brands: these values are universal SDL_CONTROLLER_BUTTON_*
semantics, not raw per-device button indices** — so a complete, working
\`<mappings>\` block can be copied verbatim from one \`<controller>\` entry
into a different device's empty one in the same file (e.g. a
newly-selected device whose Save got cut off, leaving it with
\`<mappings />\` and nothing bound) without touching Cemu's GUI at all. If
a slot ends up with more than one \`<controller>\` block and only one
device is actually in use, deleting the stale block(s) removes any
ambiguity about which one Cemu picks. Back up the file first
(\`cp controllerN.xml controllerN.xml.bak\`) before hand-editing; no
\`sudo\` needed, these files are written world-writable by Cemu itself.
Close Cemu completely and relaunch for a hand-edited profile to take effect.
ROMs are mounted at \`/ROMs\` inside Desktop too, so Cemu's own File → Load 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. can browse straight to \`/ROMs/wiiu/\` without needing ES-DE at all.