# shellcheck shell=bash # 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 # (tests/gate/virtualPad.c) as sticks A and B, or two generic joysticks # no gamepad mapping knows. 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, as a gamepad and as # a raw joystick both (and from Wine's SDL-backed bus driver). Source after # joyGate.sh. # # padStart WORK [generic] make the pads, Xbox 360 pads or generic # joysticks, and set PAD_ONLY_ENV to their ids (exit # 2 when /dev/uinput cannot be written); again after # padStop for another set # 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's), by kind. PAD_IDS_XBOX360=0x045e/0x028e PAD_IDS_GENERIC=0x1209/0x0001 PAD_ONLY_ENV=(SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT=$PAD_IDS_XBOX360) # 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=$("$(joeyTool joyScreen)" "$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" local kind=() local pad 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_ONLY_ENV=(SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT=$PAD_IDS_XBOX360) if [ "${2:-}" = generic ]; then kind=(--generic) PAD_ONLY_ENV=(SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT=$PAD_IDS_GENERIC) fi PAD_OUT="$1/pad.out" PAD_SENT=0 PAD_SWAP=0 rm -f "$fifo" "$PAD_OUT" mkfifo "$fifo" pad=$(joeyTool virtualPad) || exit 1 "$pad" "${kind[@]}" "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 "$@" }