# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. # See LICENSE.md for license information. # SPDX-License-Identifier: MIT # # Shared by the joystick gates of the ports that read the host's gamepads # (SDL3, Win32 under Wine): two virtual Xbox 360 pads # (scripts/lib/virtualPad.py) as sticks A and B. The pads are the whole # machine's while they exist, so a real pad plugged in here would be seen # too: PAD_ONLY_ENV is the environment that hides every other pad from SDL # (and from Wine's SDL-backed bus driver). Source after joyGate.sh. # # padStart WORK make the pads (exit 2 when /dev/uinput cannot be # written) # padOrder the host numbers two identical pads in its own # order (Wine does B first): move pad A once with JOY # up, and if JOY shows it as stick B, swap the pads' # roles from then on # padStop remove them # joyPress / joyRelease joyGate.sh's: hold or let go of the controls PAD_PID="" PAD_FD="" PAD_OUT="" PAD_SENT=0 PAD_SWAP=0 PAD_READY_SECONDS=10 PAD_REPLY_SECONDS=5 # The virtual pads' USB vendor/product (virtualPad.py's). PAD_ONLY_ENV=(SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT=0x045e/0x028e) # padWait COUNT SECONDS -- wait until the pad helper has printed COUNT # lines of "ok" (or, COUNT 0, "ready"). padWait() { local deadline=$((SECONDS + $2)) while [ "$SECONDS" -lt "$deadline" ]; do if [ "$1" = 0 ]; then grep -qx ready "$PAD_OUT" 2> /dev/null && return 0 elif [ "$(grep -cx ok "$PAD_OUT" 2> /dev/null)" -ge "$1" ]; then return 0 fi joeyAlive "$PAD_PID" || return 1 sleep 0.1 done return 1 } padOrder() { local got padSend press A-left sleep "$JOY_PRESS_SECONDS" joyShot "$JOY_WORK/order.png" got=$(python3 "$JOEY_REPO/scripts/lib/joyScreen.py" "$JOY_WORK/order.png" "${JOY_SCREEN_ARGS[@]}") padSend release A-left sleep "$JOY_PRESS_SECONDS" case "$got" in *"A=1-000"*) PAD_SWAP=0 ;; *"B=1-000"*) PAD_SWAP=1 ;; *) joyFail "pad A moved neither stick: $got" ;; esac } # padSend WORD CONTROL... -- with the pads' roles swapped (PAD_SWAP), stick # A's controls go to pad B and B's to pad A. padSend() { local words=("$1") local control shift for control in "$@"; do if [ "$PAD_SWAP" = 1 ]; then case "$control" in A-*) control="B-${control#A-}" ;; B-*) control="A-${control#B-}" ;; esac fi words+=("$control") done printf '%s\n' "${words[*]}" >&"$PAD_FD" PAD_SENT=$((PAD_SENT + 1)) padWait "$PAD_SENT" "$PAD_REPLY_SECONDS" || joyFail "the virtual pads did not take: $*" } padStart() { local fifo="$1/pad.in" if [ ! -w /dev/uinput ]; then echo "$JOY_LABEL: cannot write /dev/uinput, which the virtual gamepads need (a desktop session's user usually can)" >&2 exit 2 fi PAD_OUT="$1/pad.out" mkfifo "$fifo" python3 "$JOEY_REPO/scripts/lib/virtualPad.py" "JoeyLib gate" < "$fifo" > "$PAD_OUT" 2>&1 & PAD_PID=$! exec {PAD_FD}> "$fifo" if ! padWait 0 "$PAD_READY_SECONDS"; then echo "$JOY_LABEL: FAIL - the virtual gamepads did not come up: $(cat "$PAD_OUT")" >&2 exit 1 fi } padStop() { [ -n "$PAD_PID" ] || return 0 printf 'quit\n' >&"$PAD_FD" 2> /dev/null exec {PAD_FD}>&- wait "$PAD_PID" 2> /dev/null PAD_PID="" } joyPress() { padSend press "$@" } joyRelease() { padSend release "$@" }