$ cat article_0x01.txt
#==============================================================================
# UDEV, SDL, Multiple Controllers and Linux
# VERSION: 0x1
# AUTHOR: sairuk
# CONTACT: mameau.com
#==============================================================================
#C | Changelog
#==============================================================================
0x1 - Initial release, prob typos
#X | Contents:
#==============================================================================
#0|Intro
#1|SDL input overrice
#2|UDEV rules.d example
#3|systemd management
#4|bash scripts
#0 | Introduction
#==============================================================================
This article looks at UDEV, SDL and general controller maintenance under linux.
Needed a better way to manage multiple controller types across varying inputs
under linux so began looking at udev as an option. Found a number of
shortcomings (based on my limited understanding of udev perhaps) so found other
solutions.
The shortcomings worked around (in no particular order)
- linux kernel indexes js devices dynamically based on order discovered,
influenced by connection order and/or usb bus order, can change on boot
- udev kernel device detection and firing is too fast for general shell scripts
to action against
- udev external command wont take wildcards(?)
- Multiplayer usb adapters do not provide properties to match each player port
accurately against to create a static name
The goal was to take a device node e.g. /dev/input/js0 and symlink it to a
static name based on the device type and player port e.g. /dev/input/snesp1 so
that programs could be configured to use the static name instead of device that
may change index.
The problem exists that if we were to plug another joystick device into a usb
port with a higher index on the usb bus and reboot, our existing device
/dev/input/js0 would become /dev/input/js1 with the device on the higher port
now being /dev/input/js0 and when starting our program would then be using the
incorrect joystick device.
If we instead configure our application (either through the application or a
SDL input override see section 1) to use the static device name instead of the
dynamic name it will not matter if the js device index changes so long as we
mange the static name link to the device.
The final solution was to use a number of external means to create and manage
the static names, there is a 5s delay between plugging in a controller and the
static names being available.
Noting, this is still very much a work in progress
#1 | SDL input override
#==============================================================================
Set the env var SDL_JOYSTICK_DEVICE before launching the SDL application to add
additional devices instead of the start search paths. Handy if you have udev
static naming for your devices
A good example of this is Supermodel where you cannot easily set the joy devices
Running supermodel -print-inputs will output devices in standard SDL search
paths for inputs /dev/js* /dev/input/js* /dev/input/event*
{{{
Input System Settings
---------------------
Input System: SDL
Keyboards:
System Keyboard
Mice:
System Mouse
Joysticks:
1: HID 0c12:0005
2: Twin USB Joystick
3: Twin USB Joystick
In this example:
1 is js0, my Nubytech Arcade controller
2 is js1,
3 is js2
}}}
But for say Scud race it would be better to configure the one of the Twin USB
Joysticks with analogs over the arcade controller.
Supermodel assigns joystick devices automatically based on their device index
We can override that and tell it to also add additional devices, devices passed
with the env var will become SDL index 1 so if we launch supermodel as per below
we will see that the Twin USB Joystick gets listed twice
SDL_JOYSTICK_DEVICE=/dev/input/psxp1:/dev/input/psxp2
{{{
$ SDL_JOYSTICK_DEVICE=/dev/input/psxp1:/dev/input/psxp2 ./bin/supermodel -print-inputs
Input System Settings
---------------------
Input System: SDL
Keyboards:
System Keyboard
Mice:
System Mouse
Joysticks:
1: Twin USB Joystick
2: Twin USB Joystick
3: HID 0c12:0005
4: Twin USB Joystick
5: Twin USB Joystick
}}}
So now our static devices are index 1 and 2 and subsequently player
1 and 2
#2 | UDEV rules.d example
#==============================================================================
/etc/udev/rules.d/99-my-controllers.rules
{{{
###############################################################################
# My controller(s) setup
# - sairuk
#
# Save to `/etc/udev/rules.d` for use.
#
# Goal(s):
# -----------------------------------------------------------------------------
# 1. Where possible use udev
#
# 2. Provide static names for USB controllers regardless of port or order the
# devices are plugged into the system
#
# 3. Devices should be available when a controller is plugged in and any
# properties removed when the device is no longer connected
#
# Problem(s):
# -----------------------------------------------------------------------------
# Linux indexes the js devices by the order in which they are connected OR the
# order in which the device appears on the usb bus. This does not allow for
# static identifcation of a device because there is the possibility it will
# change on boot or reconnection.
#
# Multiplayer USB adapters do not provide properties where each player can
# be confidently identified by udev.
#
# Therefore using SYMLINK+="input/p%n" with KERNEL=="js[0-9*" or similar
# will not permit you create consistent device naming. The resulting name will
# be either:
# - determined by the device index
# - only create the initial symlink
# - overwrite the existing the symlink
#
# Using the udev RUN= or RUN+= to execute and external script will fire too
# quickly for the shell to process actions and you end up with a race condition
# when determining if a symlink exists for each player
#
# RUN does not appear to accept wildcards
#
# Solution
# -----------------------------------------------------------------------------
# When a device is added it is indexed from the first player through to the
# last therefore all incrementing indexes for a device should start at player1
# as lowest device index. We should be able to loop over the js devices and
# match on properties to determine the type of adapter then force our naming
# convention for symlinks
#
# Where it is not possible to accurately identify a each indiviual player on
# an adapter use an external script to create symlinks processesed outside of
# udev to meet goal 2
#
# let udev clean up the device to meet goals 1 and 3
#
###############################################################################
###############################################################################
# UDEV
#
# udev is used where a device has a property to allow for it to be identified
# accuractely and actioned appropriately. Perhaps the adapter is a single
# player adapter or has a property we can use to identify it.
#
# e.g. XBOX Dragon USB Adapter or a DS3 controller where we can match on the
# uniq device property.
###############################################################################
### XBOX Dragon USB Adapter
# REQUIRES: None
# SERIAL: 0c12_0005
# LSUSB: Bus 002 Device 072: ID 0c12:8801 Zeroplus Xbox Controller
ACTION=="add", SUBSYSTEM=="input", ATTRS{idVendor}=="0c12", GROUP="input", MODE="0660"
KERNEL=="js[0-9]*", SUBSYSTEM=="input", ATTRS{idVendor}=="0c12", ATTRS{idProduct}=="0005", SYMLINK+="input/arcade"
### DUALSHOCK 3
#KERNEL=="event*", SUBSYSTEM=="input", ATTRS{uniq}=="00:1b:fb:Bbd:0b:bd", SYMLINK+="input/dualshock3"
###############################################################################
# EXTERNAL
#
# These USB adapters do not have a property available to determine which player
# the device is for so we handle that externally once the devices are connected
#
# The script /usr/local/bin/linkdev_js is run as a systemd service file by a
# systemd timer the timer executes the service every 5s
#
# systemd unit files
# /etc/systemd/system/controllers.service
# /etc/systemd/system/controllers.timer
#
# linkdev_js loops over the js devices checking if a symlink exists with a
# predetermined naming convention based on the SERIAL property of the device
#
# udev handles the symlink cleanup when the device is removed this is completed
# by the external script /usr/local/bin/linkdev_rm
#
###############################################################################
### TWINUSB
# REQUIRES: external script for symlinks
# SERIAL: 0810_Twin_USB_Joystick
# LSUSB: Bus 002 Device 005: ID 0810:0001 Personal Communication Systems, Inc. Dual PSX Adaptor
ACTION=="add", SUBSYSTEM=="input", ATTRS{idVendor}=="0810", GROUP="input", MODE="0660"
ACTION=="remove", SUBSYSTEM=="input", ATTRS{idVendor}=="0810", RUN+="/usr/local/sbin/linkdev_rm psx"
### SNES 2p Adapter
# REQUIRES: external script for symlinks
# SERIAL: HuiJia_USB_GamePad
# LSUSB: Bus 002 Device 015: ID 0e8f:3013 GreenAsia Inc.
ACTION=="add", SUBSYSTEM=="input", ATTRS{idVendor}=="0e8f", GROUP="input", MODE="0660"
ACTION=="remove", SUBSYSTEM=="input", ATTRS{idVendor}=="0e8f", RUN+="/usr/local/sbin/linkdev_rm snes"
### Gamecube 4p Adapter
# REQUIRES: external script for symlinks
# SERIAL: ShanWan_Hyperkin_4_Port_Adapter
# LSUSB: Bus 002 Device 014: ID 0079:1843 DragonRise Inc.
ACTION=="add", SUBSYSTEM=="input", ATTRS{idVendor}=="0079", GROUP="input", MODE="0660"
ACTION=="remove", SUBSYSTEM=="input", ATTRS{idVendor}=="0079", RUN+="/usr/local/sbin/linkdev_rm gc"
}}}
#3 | systemd management
#==============================================================================
/etc/systemd/system/controllers.service
{{{
[Unit]
Description=Controllers
[Service]
Type=forking
ExecStart=/usr/local/sbin/linkdev_js
[Install]
WantedBy=multi-user.target
}}}
/etc/systemd/system/controllers.timer
{{{
[Unit]
Description=Controllers
[Timer]
OnCalendar=*-*-* *:*:00/5
Persistent=true
[Install]
WantedBy=timers.target
}}}
#4 | bash scripts
#==============================================================================a
/usr/local/sbin/linkdev_rm
Very basic script to remove the links from /dev/input used as a RUN action by
udev remove action
{{{
#!/bin/bash
/bin/rm /dev/input/$1p*
}}}
/usr/local/sbin/linkdev_js
Loop through the js devices and match based on the serial property, from there
see if the symlink exists for up to n players and if not create it.
This provides a static name based on the lowest index (p1) to the hightes (pn)
{{{
#!/bin/bash
#
#
# TODO
# - check that symlink is correct
function _log() {
echo "$(date +'%Y-%m-%d %H:%M:%S') $*" >> /tmp/linkdev_js.log
}
###
#
# Create New nodes
#
function create() {
local SOURCE=$2
for p in $(seq 1 $3)
do
local TARGET=/dev/input/$1p$p
_log "[TARGET]: $TARGET"
_log "[SOURCE]: $SOURCE"
if [ ! -L $TARGET ]
then
_log "[CREATE]: $SOURCE -> $TARGET"
ln -s $SOURCE $TARGET
return
else
_log "[EXISTS]: $TARGET"
fi
done
}
for JS in $(ls /dev/input/js*)
do
DEV=$(udevadm info $JS | awk -F'=' /ID_SERIAL/{'print $2'})
case $DEV in
ShanWan_Hyperkin_4_Port_Adapter)
create gc $JS 4
;;
0810_Twin_USB_Joystick)
create psx $JS 2
;;
HuiJia_USB_GamePad)
create snes $JS 2
;;
*)
echo "USAGE"
;;
esac
done
}}}
- sairuk
- mameau.com