#============================================================================== # UDEV, SDL, systemd and Multiple Controllers and Linux # VERSION: 0x05 # AUTHOR: sairuk # CONTACT: mameau.com #============================================================================== #C | Changelog #============================================================================== 0x05 - Add globbing matches to systemd path unit, tweak service unit 0x04 - Added steam controllers and madcatz mc2 xbox wheel - moved the nubytext arcade controller to external to maintain naming standards - looking longterm i'll probably stop using udev completely for this use case and just base it on systemd path monitoring 0x03 - Add some dependancy changes for systemd since boot was screaming 0x02 - Change the system timer to a path file 0x01 - Initial release, prob typos #X | Contents: #============================================================================== #0|Introduction #1|SDL input override #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 the 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. 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 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. ############################################################################### ### 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 # ############################################################################### ### XBOX Dragon USB Adapter # Nubytech 3in1 Arcade Controller # 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" ACTION=="remove", SUBSYSTEM=="input", ATTRS{Serial}=="0c12_0005", RUN+="/usr/local/sbin/linkdev_rm arcade" # Mat Catz MC2 Wheel # REQUIRES: None # SERIAL: 0738_4520 # LSUSB: Bus 002 Device 035: ID 0738:4520 Mad Catz, Inc. XBox Device ACTION=="add", SUBSYSTEM=="input", ATTRS{idVendor}=="0738", GROUP="input", MODE="0660" ACTION=="remove", SUBSYSTEM=="input", ATTRS{Serial}=="0738_4520", RUN+="/usr/local/sbin/linkdev_rm xmc2wheel" ### STEAM Controller ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="28de", GROUP="input", MODE="0660" KERNEL=="hidraw*", ATTRS{idVendor}=="28de", GROUP="input", MODE="0660" # STEAM Joystick Emulation KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" # STEAM Controller x360 emulation ACTION=="remove", SUBSYSTEM=="input", ATTRS{Serial}=="noserial", RUN+="/usr/local/sbin/linkdev_rm steam360" ### 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 Wants=controllers.path [Service] Type=oneshot ExecStart=/usr/local/sbin/linkdev_js Restart=no }}} /etc/systemd/system/controllers.path {{{ [Unit] Description=Monitor input changes Wants=systemd-udev-trigger.service systemd-udevd.service graphical.target [Path] PathExistsGlob=/dev/input/js[0-9]* Unit=controllers.service [Install] WantedBy=graphical.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