diff --git a/services/wolf.sh b/services/wolf.sh index 0d15391..6d0d9f4 100644 --- a/services/wolf.sh +++ b/services/wolf.sh @@ -325,6 +325,7 @@ EOF 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" + echo " - Force ES-DE's 'Run in background' off durably (mounts + writes esde-settings/es_settings.xml)" return 0 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_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 entry, + # same idempotent pattern as everywhere else in this file. Written as a + # plain top-level element with no wrapper, matching what + # ES-DE's own Settings::saveFile() actually produces today (confirmed + # against its source — the "wrap everything in " 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]*[ \t]*\n?', '', content) +line = ' \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 ───────────────────────────────────────────────────────── echo "" echo "═══════════════════════════════════════════════════════" @@ -1941,7 +1985,23 @@ CATALOG = { f'{games}/retroarch:/home/retro/.config/retroarch:rw', f'{games}/emulators:/mnt/games/emulators: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', ulimits=[], privileged=False, devices=['/dev/uinput:/dev/uinput'], @@ -3415,14 +3475,17 @@ print(next(iter(ids)) if len(ids) == 1 else '') if [ -n "$ACTIVE_CLIENT_ID" ]; then CLIENT_ID="$ACTIVE_CLIENT_ID" 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 CLIENT_ID="${CLIENT_IDS[0]}" echo "" - echo "Only one paired client — using it." + echo "Only one paired Moonlight device — using it." else 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 echo "Invalid selection." exit 1 @@ -3430,8 +3493,64 @@ print(next(iter(ids)) if len(ids) == 1 else '') CLIENT_ID="${CLIENT_IDS[$((PICK-1))]}" fi + # Wolf's own logs record "Creating joypad for controller " + # 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 "" - 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}" if ! [[ "$NSLOTS" =~ ^[0-9]+$ ]] || [ "$NSLOTS" -lt 1 ]; then echo "Invalid number." @@ -3440,8 +3559,16 @@ print(next(iter(ids)) if len(ids) == 1 else '') OVERRIDE_TYPES=() for i in $(seq 1 "$NSLOTS"); do - read -r -p " Slot $i type (AUTO/XBOX/PS/NINTENDO) [AUTO]: " T - T="${T:-AUTO}" + cnum=$((i - 1)) + 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:]')" case "$T" in AUTO|XBOX|PS|NINTENDO) ;; @@ -3603,7 +3730,23 @@ CATALOG = { f'{games}/retroarch:/home/retro/.config/retroarch:rw', f'{games}/emulators:/mnt/games/emulators: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', ulimits=[], privileged=False, 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 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 joypad for controller \` 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 each one to. There are only **3 concrete types** (confirmed against Wolf's own 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, 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 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 @@ -4391,6 +4594,44 @@ time Cemu launches from ES-DE too. characters?** That's not a Cemu-specific bug — see "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 \`\` blocks (one per SDL device that's +ever been assigned to that slot, identified by its own \`\`/ +\`\`) with a \`\` list of \`\`/\`