Controller Discovery - approxeng.input.controllers


New in 2.3, the discovery layer tidies up and abstracts the process of finding a controller or controllers matching a particular set of requirements. In previous versions you had to handle evdev device nodes directly which wasn’t particularly friendly, especially as some controllers used multiple OS devices.


If you are using the resource binding via ControllerResource the same classes are used within that binding to specify controllers.

Simple discovery

Before you can use a controller, you need to find it:

from approxeng.input.controllers import find_matching_controllers

discoveries = find_matching_controllers()

This is all you need to perform the most basic discovery process, specifically one which will attempt to find a single connected controller.

Specifying properties

You may not want to just pick the first controller available (although the library does attempt to order controllers such that the first one will be the most capable). You can use the ControllerRequirement to specify exactly what you want. At the moment this class supports two filters:

  1. You can specify an exact controller class with require_class, such as specifying that you only want a DualShock4

  2. You can specify a set of controls on the controller, identified by sname, with ‘require_snames’ that any controller must have to be included in the results. This is great when you know that you need a pair of analogue triggers but don’t care what exact controller type you get as long as it has them.

from approxeng.input.controllers import find_matching_controllers, ControllerRequirement

discoveries = find_matching_controllers(ControllerRequirement(require_snames=['lx','ly']))

Specifying multiple controllers

Both the above examples return a single controller. This is because they both have a single requirement - when you don’t provide any requirements as in the first example the library treats it as if you’d given it a single requirement with no filters applied.

The result of the find_matching_controllers() call is a list of ControllerDiscovery objects, each of which contains both the actual Controller sub-class and the matching evdev device nodes. In both these cases this list contains a single item.

To discover multiple controllers, specify multiple requirements when calling:

from approxeng.input.controllers import find_matching_controllers, ControllerRequirement

discoveries = find_matching_controllers(ControllerRequirement(require_snames=['lx','ly']),

The above will attempt to find two controllers. The first one must have the specified axes, the second can be any connected device. The result will be a two item list, with the controller discoveries in the same order as the supplied controller requirements.

Discovery failures

If the system does not have the controllers you’ve requested, the discovery process will raise a ControllerNotFoundError, in general you should catch this and wait before trying to bind again.

Discovery API

class approxeng.input.controllers.ControllerDiscovery(controller_class, controller_constructor_args, devices, name)[source]

Represents a single discovered controller attached to the host. Ordered, with controllers with more axes and buttons being given a higher ordering, and controllers with force feedback higher than those without, then falling back to the assigned name.

__init__(controller_class, controller_constructor_args, devices, name)[source]
property has_ff

True if there’s a force feedback compatible device in this discovery’s device list, False otherwise

exception approxeng.input.controllers.ControllerNotFoundError[source]

Raised during controller discovery if the specified set of controller requirements cannot be satisfied

class approxeng.input.controllers.ControllerRequirement(require_class=None, require_snames=None, require_ff=False)[source]

Represents a requirement for a single controller, allowing restriction on type. We might add more filtering options later, such as requiring a minimum number of axes, or the presence of a particular control. If you want that now, you can subclass this and pass it into the find_matching_controllers and similar functions.

__init__(require_class=None, require_snames=None, require_ff=False)[source]

Create a new requirement

  • require_class – If specified, this should be a subclass of approxeng.input.Controller, only controllers which match this class will be accepted. Defaults to None, accepting any available controller class.

  • require_snames – If specified, this should be a list of strings containing snames of controls (buttons or axes) that must be present in the controller. Use this when you know what controls you need but don’t mind which controller actually implements them.

  • require_ff – If true, requires controllers with at least one force-feedback compatible device node

accept(discovery: ControllerDiscovery)[source]

Returns True if the supplied ControllerDiscovery matches this requirement, False otherwise

approxeng.input.controllers.device_verbose_info(device: InputDevice)[source]

Gather and format as much info as possible about the supplied InputDevice. Used mostly for debugging at this point.


device – An InputDevice to examine


A dict containing as much information as possible about the input device.

approxeng.input.controllers.find_all_controllers(**kwargs) List[ControllerDiscovery][source]

A list of ControllerDiscovery instances corresponding to controllers attached to this host, ordered by the ordering on ControllerDiscovery. Any controllers found will be constructed with kwargs passed to their constructor function, particularly useful for dead and hot zone parameters.

approxeng.input.controllers.find_matching_controllers(*requirements, **kwargs) List[ControllerDiscovery][source]

Find a sequence of controllers which match the supplied requirements, or raise an error if no such controllers exist.


requirements – Zero or more ControllerRequirement instances defining the requirements for controllers. If no item is passed it will be treated as a single requirement with no filters applied and will therefore match the first controller found.


A sequence of the same length as the supplied requirements array containing ControllerDiscovery instances which match the requirements supplied.


ControllerNotFoundError if no appropriately matching controllers can be located

approxeng.input.controllers.get_controller_classes(scan_home=True, additional_locations=None)[source]

Get a map of ‘vendor-product’ string to controller class. This loads known ‘complex’ controller classes first, then loads simple YAML based ones from within the library, and finally loads from ~/.approxeng_input treating this as a directory, iterating over files within it and loading definitions from each. This means that users can override controller definitions by putting files in this directory.

  • scan_home – if true, uses ~/.approxeng.input/ as a source for YAML templates, default to True

  • additional_locations – if provided, is interpreted as a list of directory paths which will be scanned in order for additional YAML definitions. If this is a single string it will be wrapped automatically in a list


Pretty-print all controllers found


Simple test function which prints out all devices found by evdev

approxeng.input.controllers.unique_name(device: InputDevice) str[source]

Construct a unique name for the device based on, in order if available, the uniq ID, the phys ID and finally a concatenation of vendor, product, version and filename.


device – An InputDevice instance to query


A string containing as unique as possible a name for the physical entity represented by the device