manage.sh controllers: poll Wolf's logs, label slots ordinally; document Cemu profile-editing recovery

Two follow-ups from live testing that just confirmed the whole
controllers_override chain works end to end:

1. manage.sh controllers now polls `docker compose logs wolf` for
   "Creating <TYPE> joypad for controller <N>" lines (most recent per
   slot wins) and shows what it last saw before asking anything, using
   it as the suggested default at each prompt. Every prompt is now
   labeled "1st controller"/"2nd controller"/etc - always meaning
   Wolf's own 0-indexed "controller 0"/"controller 1" - instead of
   asking for a raw slot number, closing the exact client-vs-controller
   0-vs-1 confusion surfaced live earlier. Documents plainly that this
   can show what TYPE a slot last used but can't show or control WHICH
   physical controller becomes which slot - that's decided by Moonlight
   client-side, upstream of Wolf's API entirely.

2. Documents a full recovery procedure in the Wii U section for when
   Cemu's Input Settings Save button is cut off-screen (confirmed live:
   happens even maximized, at higher resolutions, and closing the
   window doesn't save) - an Alt-drag/Alt+F7 window-manager fix first,
   then editing Cemu's own controllerProfiles/controllerN.xml directly
   on the host (plain XML, not inside the container) as a full GUI
   bypass. Documents the key insight that made this actually work live:
   <mapping>/<button> pairs are universal SDL_CONTROLLER_BUTTON_*
   semantics (confirmed earlier against real captured profiles from
   different controller brands), so a working mapping can be copied
   verbatim into a different device's empty <mappings/> block without
   redoing the live button-capture in Cemu's GUI at all.

Verified the redesigned manage.sh controllers flow end to end against a
mocked docker compose logs matching the user's real log output (both
the log-seen and no-logs-seen paths), plus the ordinal-suffix logic in
isolation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
This commit is contained in:
Claude
2026-09-03 17:44:53 +00:00
parent 4b5ca9f6ea
commit 9ba1d7e9db
+126 -2
View File
@@ -3433,6 +3433,59 @@ print(next(iter(ids)) if len(ids) == 1 else '')
CLIENT_ID="${CLIENT_IDS[$((PICK-1))]}"
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 "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 —"
@@ -3446,8 +3499,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) ;;
@@ -4114,6 +4175,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 <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
each one to. There are only **3 concrete types** (confirmed against Wolf's own
virtual-pad source, \`inputtino\`) — not one per real controller brand:
@@ -4397,6 +4483,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 \`<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
can browse straight to \`/ROMs/wiiu/\` without needing ES-DE at all.