wolf: add fullscreen wrapper for Steam-launched emulators, document controller/crash troubleshooting

Addresses reported symptoms after adding Cemu/Azahar/PCSX2/Dolphin/ES-DE
as non-Steam games in Wolf's Steam app: a launched emulator opens at
roughly half the screen, and the controller doesn't respond.

Half-screen: a non-Steam game launched from Steam is a second top-level
window inside Wolf's single-app Steam Sway session (RUN_SWAY=true) —
Sway's own kiosk config only auto-fullscreens the ONE window it expects
(Steam's own), so anything launched from inside Steam opens at whatever
default size it requests instead. steam-add-nonsteam-game and
steam-setup-frontends now route the launch through a small generated
wrapper (emulators/steam-fullscreen-wrap) that launches the real binary
and repeatedly asks Sway to fullscreen whatever currently has focus for
a few seconds after launch. NOT yet confirmed live against a real Wolf
Steam session — if swaymsg isn't reachable from inside that container,
the wrapper's loop just fails silently (2>/dev/null) and the launch is
unaffected, so this is safe to try either way.

Since every wrapped shortcut now shares the same Exe (the wrapper) with
the real per-emulator target carried in LaunchOptions instead, the
shortcuts.vdf dedupe/appid logic had to move from keying on Exe alone
to the (Exe, LaunchOptions) pair — otherwise adding a second emulator
would have silently overwritten the first one's entry. Also cleans up
a legacy pre-wrapper entry (Exe pointing directly at the same real
binary, no LaunchOptions) when re-adding something added before this
existed, so re-running doesn't leave a stale duplicate tile behind.
Verified with a synthetic shortcuts.vdf: two different emulators keep
distinct entries, re-adding one updates in place, and a legacy
direct-launch entry gets replaced rather than duplicated.

Controller not responding turned out to most likely be Steam Input
applying its own (unconfigured) controller mapping to the new shortcut
rather than passing raw input through — documented the actual fix
(Big Picture -> shortcut -> Controller Options -> Gamepad template or
disable Steam Input) since this is a Steam-side per-shortcut setting
this installer has no way to preconfigure via shortcuts.vdf.

Also documented the third reported symptom (Cemu/Azahar/PCSX2 tiles
appear but crash on launch, while Dolphin/ES-DE work) as a separate,
not-yet-diagnosed issue, with the exact docker exec command to capture
the real error directly (no wrapper, no Steam) since guessing at a fix
without that output isn't reliable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BWYKEERLA1a7gv86Z4W23
This commit is contained in:
Claude
2026-09-10 03:40:42 +00:00
parent d41da6356d
commit 05942fb44a
+120 -12
View File
@@ -4071,9 +4071,50 @@ except Exception:
ls "$_SANG_EMU_DIR" 2>/dev/null ls "$_SANG_EMU_DIR" 2>/dev/null
exit 1 exit 1
fi fi
_SANG_EXE="/home/retro/Applications/$(basename "$_SANG_HOST_FILE")" _SANG_REAL_EXE="/home/retro/Applications/$(basename "$_SANG_HOST_FILE")"
[ -z "$_SANG_NAME" ] && _SANG_NAME=$(basename "$_SANG_HOST_FILE" | sed -E 's/\.(AppImage|sh|x86_64)$//I') [ -z "$_SANG_NAME" ] && _SANG_NAME=$(basename "$_SANG_HOST_FILE" | sed -E 's/\.(AppImage|sh|x86_64)$//I')
# Route the actual launch through a small fullscreen-forcing wrapper
# instead of pointing Steam's Exe directly at the AppImage. Why: a
# non-Steam game launched from Steam is a SECOND top-level window in
# Wolf's single-app Steam Sway session (RUN_SWAY=true on the 'steam'
# CATALOG entry) — Sway's own kiosk config only auto-fullscreens the
# ONE window it expects (Steam's own), so anything launched from
# inside Steam opens at whatever default size it requests instead,
# which reads as roughly half the screen against a full
# Moonlight-resolution display. Reported live against ES-DE/Dolphin
# added this way. NOT yet confirmed live that swaymsg actually
# reaches Sway from inside this exact container/session — if it
# doesn't, the wrapper's fullscreen loop just fails silently
# (2>/dev/null below) and the app launches exactly as it did before
# this existed, so this is safe to try without risking the launch
# itself. Regenerated on every call (cheap, stateless — nothing to
# lose by overwriting it) so a fix to the wrapper reaches every
# existing shortcut the next time it's (re-)added, not just new ones.
_SANG_WRAP="$_SANG_EMU_DIR/steam-fullscreen-wrap"
cat > "$_SANG_WRAP" << 'WRAPEOF'
#!/bin/bash
# steam-fullscreen-wrap <real-binary> [args...]
# Launches the real target, then repeatedly asks Sway to fullscreen whatever
# currently has input focus for a few seconds after launch — the newly
# launched window is expected to grab focus once it maps, same as any
# ordinary X11/Wayland client. 'fullscreen enable' (not 'toggle') is
# idempotent, so repeating it while the window is already fullscreen is a
# harmless no-op rather than flipping it back off.
REAL_BIN="$1"; shift
"$REAL_BIN" "$@" &
PID=$!
(
for _i in $(seq 1 20); do
sleep 0.5
swaymsg fullscreen enable 2>/dev/null
done
) &
wait "$PID"
WRAPEOF
chmod +x "$_SANG_WRAP"
_SANG_EXE="/home/retro/Applications/steam-fullscreen-wrap"
STEAM_HOME=$(_steam_home) STEAM_HOME=$(_steam_home)
if [ -z "$STEAM_HOME" ]; then if [ -z "$STEAM_HOME" ]; then
echo "No Steam home found yet under ${WOLF_STATE_DIR:-/etc/wolf}." echo "No Steam home found yet under ${WOLF_STATE_DIR:-/etc/wolf}."
@@ -4100,10 +4141,10 @@ except Exception:
sleep 3 sleep 3
fi fi
sudo python3 - "$_SANG_VDF" "$_SANG_EXE" "$_SANG_NAME" "/home/retro/Applications" << 'VDFPY' sudo python3 - "$_SANG_VDF" "$_SANG_EXE" "$_SANG_NAME" "/home/retro/Applications" "$_SANG_REAL_EXE" << 'VDFPY'
import sys, struct, os, zlib import sys, struct, os, zlib
path, exe_path, app_name, start_dir = sys.argv[1:5] path, exe_path, app_name, start_dir, real_exe = sys.argv[1:6]
TYPE_MAP, TYPE_STR, TYPE_INT, TYPE_END = 0x00, 0x01, 0x02, 0x08 TYPE_MAP, TYPE_STR, TYPE_INT, TYPE_END = 0x00, 0x01, 0x02, 0x08
@@ -4167,13 +4208,30 @@ if shortcuts_entry is None:
entries_list = shortcuts_entry[2] entries_list = shortcuts_entry[2]
# exe_path now points at the shared steam-fullscreen-wrap script (same Exe
# for every emulator added this way), with the real per-emulator target
# carried in LaunchOptions instead — so matching (for both idempotent
# re-adds and appid uniqueness) has to key on the (exe, LaunchOptions) PAIR,
# not exe alone, or adding a second emulator would silently overwrite the
# first one's shortcut entry. Also cleans up a legacy entry from before this
# wrapper existed, where Exe pointed directly at this same real binary with
# no LaunchOptions at all — re-adding an emulator added under the old
# scheme replaces that stale direct-launch entry instead of leaving a
# duplicate tile behind.
quoted_exe = f'"{exe_path}"' quoted_exe = f'"{exe_path}"'
entries_list[:] = [e for e in entries_list if get_field(e[2], 'exe') != quoted_exe] quoted_launch = f'"{real_exe}"'
entries_list[:] = [
e for e in entries_list
if not (
(get_field(e[2], 'exe') == quoted_exe and get_field(e[2], 'LaunchOptions') == quoted_launch)
or get_field(e[2], 'exe') == quoted_launch
)
]
for idx, e in enumerate(entries_list): for idx, e in enumerate(entries_list):
e[0] = str(idx) e[0] = str(idx)
new_index = str(len(entries_list)) new_index = str(len(entries_list))
crc_input = (exe_path + app_name).encode('utf-8') crc_input = (exe_path + real_exe + app_name).encode('utf-8')
appid = (zlib.crc32(crc_input) | 0x80000000) & 0xFFFFFFFF appid = (zlib.crc32(crc_input) | 0x80000000) & 0xFFFFFFFF
appid_signed = appid - 0x100000000 if appid >= 0x80000000 else appid appid_signed = appid - 0x100000000 if appid >= 0x80000000 else appid
@@ -4184,7 +4242,7 @@ new_entry_fields = [
['StartDir', TYPE_STR, f'"{start_dir}"'], ['StartDir', TYPE_STR, f'"{start_dir}"'],
['icon', TYPE_STR, ''], ['icon', TYPE_STR, ''],
['ShortcutPath', TYPE_STR, ''], ['ShortcutPath', TYPE_STR, ''],
['LaunchOptions', TYPE_STR, ''], ['LaunchOptions', TYPE_STR, quoted_launch],
['IsHidden', TYPE_INT, 0], ['IsHidden', TYPE_INT, 0],
['AllowDesktopConfig', TYPE_INT, 1], ['AllowDesktopConfig', TYPE_INT, 1],
['AllowOverlay', TYPE_INT, 1], ['AllowOverlay', TYPE_INT, 1],
@@ -4200,12 +4258,12 @@ entries_list.append([new_index, TYPE_MAP, new_entry_fields])
with open(path, 'wb') as f: with open(path, 'wb') as f:
f.write(serialize_map(root)) f.write(serialize_map(root))
print(f"Wrote shortcuts.vdf: {app_name} -> {exe_path} (appid {appid_signed})") print(f"Wrote shortcuts.vdf: {app_name} -> {real_exe} (via {exe_path}, appid {appid_signed})")
VDFPY VDFPY
sudo chown 1000:1000 "$_SANG_VDF" sudo chown 1000:1000 "$_SANG_VDF"
echo "" echo ""
echo "Added '$_SANG_NAME' -> $_SANG_EXE to Steam's shortcuts.vdf." echo "Added '$_SANG_NAME' -> $_SANG_REAL_EXE (via the fullscreen wrapper) to Steam's shortcuts.vdf."
echo "Open Steam in Moonlight (it will restart since it was stopped above) —" echo "Open Steam in Moonlight (it will restart since it was stopped above) —"
echo "the new tile appears in your Library (may need the Library view, not just Home)." echo "the new tile appears in your Library (may need the Library view, not just Home)."
echo "First launch: right-click it -> Properties -> Compatibility, and confirm" echo "First launch: right-click it -> Properties -> Compatibility, and confirm"
@@ -5314,10 +5372,60 @@ controllers per player slot even when the physical controllers are
identical models (the same case \`./manage.sh controllers\` exists to work identical models (the same case \`./manage.sh controllers\` exists to work
around for ES-DE). Add Cemu here, then in Steam's own Big Picture around for ES-DE). Add Cemu here, then in Steam's own Big Picture
Controller Settings you can see and assign each detected controller Controller Settings you can see and assign each detected controller
individually. This hasn't been confirmed live yet against Wolf's individually.
container-created virtual controllers specifically (real hardware behaves
this way; whether Steam Input sees Wolf's virtual joypads the same way is **Controller doesn't respond at all once added to Steam?** Confirmed live:
still to be tested) — worth trying before assuming it works. this is Steam Input, not the emulator — by default Steam applies its own
controller configuration to *every* shortcut, Steam or non-Steam alike,
and a freshly-added non-Steam game with no configuration picked yet can
end up with no usable mapping at all rather than passing raw input
through. Fix from Steam's own Big Picture UI: select the shortcut → the
controller icon / **Manage Game** → **Controller Options**, and either
pick a **Gamepad** template (closest to "pass it through as a normal
joystick", needed for ES-DE/RetroArch's own native SDL input handling to
see it at all) or turn **Steam Input** off for that one shortcut entirely
if you don't need the per-device assignment this section is about in the
first place. This is a one-time, per-shortcut setting Steam remembers —
not something this installer can preconfigure from the command line, since
it's stored in Steam's own (separate, Steam Cloud-synced) controller
config rather than \`shortcuts.vdf\`.
**Opens at roughly half the screen instead of fullscreen?** Also confirmed
live, and now worked around automatically as of the version of this repo
that added the point below — \`steam-add-nonsteam-game\`/
\`steam-setup-frontends\` route the launch through a small wrapper
(\`emulators/steam-fullscreen-wrap\`) instead of pointing Steam's \`Exe\`
directly at the AppImage. The underlying cause: a non-Steam game launched
from Steam is a *second* top-level window inside Wolf's single-app Steam
Sway session, and Sway's own kiosk config only auto-fullscreens the ONE
window it expects (Steam's own) — anything launched from inside Steam
opens at whatever default size it requests instead. The wrapper launches
the real binary, then repeatedly asks Sway to fullscreen whatever currently
has focus for a few seconds after launch (idempotent — harmless if it's
already fullscreen). **This has not been confirmed live against a real
Wolf Steam session yet** — if \`swaymsg\` isn't reachable from inside that
exact container, the wrapper's fullscreen loop just fails silently and the
window opens at its old default size, same as before this existed; it's
safe to try either way since the underlying launch itself is unaffected.
Re-run \`steam-add-nonsteam-game\`/\`steam-setup-frontends\` for anything
already added before this existed — it replaces the old direct-launch
shortcut with the wrapped one automatically, no duplicate tile left behind.
**An added emulator's tile appears in Steam but crashes on launch instead
of opening at all** (as opposed to the half-screen case above, where it
does open): that's a different problem — something about the AppImage
itself failing to run inside the Steam container specifically, since the
same binary launching fine through the \`esde\`/\`desktop\` Wolf apps rules out
the AppImage being broken outright. Worth checking before assuming it's
unfixable:
\`\`\`bash
CONTAINER=\$(docker ps --format '{{.Names}}' | grep -i WolfSteam | head -1)
docker exec "\$CONTAINER" /home/retro/Applications/<the-appimage-file> ; echo "exit: \$?"
\`\`\`
Run directly like this (no wrapper, no Steam involved) the real error
usually prints straight to the terminal — a missing shared library, a
FUSE/AppImage mount failure (\`dlopen(): error loading libfuse.so.2\`),
or similar. That output is what actually narrows down the fix.
## Running ES-DE and/or RetroArch through Steam instead of their own Wolf apps ## Running ES-DE and/or RetroArch through Steam instead of their own Wolf apps
The same Steam Input reasoning above applies beyond just Cemu: ES-DE and The same Steam Input reasoning above applies beyond just Cemu: ES-DE and