joeylib2/scripts/lib/padGate.sh

138 lines
4.2 KiB
Bash

# 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 "$@"
}