Running OpenSD

OpenSD is intended to be run as a user-level service, but it can be run from a terminal like most other Linux programs. This can be useful for troubleshooting or debugging purposes since every message is logged to stdout.

OpenSD requires access to the uinput and hidraw subsystems. To enable this access, OpenSD provides a udev rule which will be installed when installing OpenSD. In this rule, access is allowed to both subsystem for members of the opensd group, which is also created on install. Any user that intends to run OpenSD must be a member of this group, or otherwise have R/W access to these device nodes. After installing for the first time it will be necessary to log out in order for the user’s group changes to take effect.

For more information see the Troubleshooting section.

Warning
Do NOT run the OpenSD daemon as root, either directly or as a system service.

From a terminal

$ opensdd

Will start an instance of the daemon and log all output to the terminal. The default log level is WARN.

Or to see debug messages run:

$ opensdd -l debug

A full list of command line options can be shown using:

$ opensdd --help

As a User-Level Service

For systemd users, a service file is provided which can be run as a user-level service that will automatically start on log-in.

To run the OpenSD daemon as a service on user log-in:

$ systemctl --user enable opensd
$ systemctl --user start opensd

Configuration Files

OpenSD uses two types of configuration files. The first type being the Daemon Configuration, and the second type are gamepad Profiles which are loaded by the daemon.

All files use an ini-style format and are easily created or modified with any text editor.

Since OpenSD is a userspace daemon, configuration files are stored in $XDG_CONFIG_HOME/opensd/ which, on most systems, will be ~/.config/opensd/. Gamepad profiles are stored in the profile subdirectory, (example: ~/.config/opensd/profiles/). If the OpenSD user config directory does not exist, a new one will be created and populated with the default configuration files when the daemon is run.

Note
OpenSD follows the XDG specification, so environments which set / change $XDG_HOME or $XDG_CONFIG_HOME should behave correctly. For the purposes of this documentation we’ll refer to the default XDG paths for user configuration files (example: ~/.config/opensd/).
Basic format rules:
  • Parsing is line-based and space delimited.

  • The files are broken into braced enclosed sections and keys.

  • Names of sections and keys are not case-sensitive, but values can be, such as file names.

  • Sections are enclosed in square brackets (example: [SectionName])

  • Keys must be contained within a section.

  • Key assignment must be separated by whitespace. (example: key = value1 value2 value3)

  • Comments are preceeded by a # or ; at the beginning of the line.

  • Placing a value in quotes " treats the value as a literal string which allows whitespace. (example: key = "A string with spaces")


Daemon Configuration File (config.ini)

This single file contains a small group of options that pertain to the daemon itself. The default configuration file for the daemon can be found at ~/.config/opensd/config.ini. An unmodified copy of this file will be installed to /usr/local/share/opensd/config/config.ini or /usr/share/opensd/config/config.ini.

An example config.ini can be found in this documentation if you want to see what a complete file might look like.

[Daemon] Section

Note
This section is required.

Profile

This tells the daemon which profile to load when it starts up. Profiles are only loaded from the ~./config/opensd/profiles/ directory, so only specify the filename here. OpenSD also has other methods for loading profiles once the daemon is running, this setting is only for the default profile you wish to use.

Format:

Profile = <filename>

Example:

[Daemon]
Profile = default.profile

AllowClients

Note
This feature is not yet fully implemented.

This setting enables or disables the use of the CLI and GUI utilities which connect to the daemon. If set to false, the daemon will not listen for clients. This can be useful if you want to "lock down" a configuration so it cannot be changed while it is running. The default is true.

Format:

AllowClients = <true | false>

Example:

[Daemon]
AllowClients = true

Profile Configuration Files (*.profile)

These files are used to configure the gamepad driver features and bindings. A default profile is configured in the config.ini file to be loaded at startup, but you can also switch between them at any time while the daemon is running using any of several possible methods.

Gamepad profiles can be found in ~/.config/opensd/profiles/. The file extension is *.profile.

An example profile can be found in this documentation if you want to see what a complete file might look like.

A default profile (cleverly named default.profile) is provided which includes documentation in the comments on how to configure it. It is not recommended to modify this file, instead you should make a copy of it, renaming it to whatever_you_want.profile and edit that file instead. If the default profile is deleted, it will be recreated when the daemon is run, so if you need a clean or updated copy of the default.profile, simply delete it and restart the OpenSD daemon.

An unmodified copy of this file will be installed to /usr/local/share/opensd/profiles/default.profile or /usr/share/opensd/profiles/default.profile.

[Profile] Section

Name

The profile name as it will appear in the GUI and through the CLI query. Should be unique for each profile to avoid confusion.

Format:

Name = <profile_name>
  • profile_name: Any unique name. Should be enclosed in quotes "" to preserve spaces.

Example:

[Profile]
Name = "My favourite gamepad profile"
Note
Prior to version 0.47, quoted literal strings were not supported. It’s recommended, but not necessary to update old profiles to put this value in quotes.

Description

A breif description of the profile for use in the GUI and CLI query. Does not affect anything else, just intended as a hint for users.

Format:

Description = <description>
  • description: A brief, helpful description of what your profile does. Should be enclosed in quotes "" to preserve spaces.

Example:

[Profile]
Description = "Just a profile I use for most applications."
Note
Prior to version 0.47, quoted literal strings were not supported. It’s recommended, but not necessary to update old profiles to put this value in quotes.

[Features] Section

ForceFeedback

Enable or disables haptic / force-feedback events for the gamepad device. It’s worth mentioning that only the Gamepad device can receive force-feedback events; the Motion or Mouse devices will not receive these events.

Format:

ForceFeedback = <true | false>

Example:

[Features]
ForceFeedback = true

If unspecified, this value defaults to false.

Note
This feature is not yet fully implemented

MotionDevice

If this is set to true, an additional input device will be created which will report motion control data. Motion axes need to have thier ranges and bindings defined. If this is disabled, any Motion device bindings will be ignored.

Format:

MotionDevice = <true | false>

Example:

[Features]
MotionDevice = true

If unspecified, this value defaults to false.

Note
While it’s possible to combine gamepad and motion input into a single input device, Linux kernel and uinput specifications state that motion control devices should be separate from other gamepad / joystick input. Not separating these can also make it difficult to configure controls in most applications.

MouseDevice

If this is set to true, an additional input device will be created which will be used to send mouse / trackpad events. Mouse events still need to have thier bindings defined. If this is disabled, any Mouse device bindings will be ignored.

Format:

MouseDevice = <true | false>

Example:

[Features]
MouseDevice = true

If unspecified, this value defaults to true.


LizardMode

The Steam Controller and the Steam Deck both have a kind of fallback "BIOS" mode which emulates some keyboard and mouse input using the gamepad. Valve refers to this as "Lizard Mode". This mode cannot be redefined. It sends events IN ADDITION to the gamepad events created by the OpenSD driver, so it should always be disabled. If you would like mouse or keyboard emulation, they should be configured with bindings. When OpenSD exits, Lizard Mode is re-enabled.

If this is set to false "Lizard Mode" will be disabled (recommended).

Format:

LizardMode = <true | false>

Example:

[Features]
LizardMode = false

If unspecified, this value defaults to false.


StickFiltering

The thumbsticks on the Steam Deck have a circular range but return square-ish data, which makes it feel odd and complicated to apply radial deadzones to. Because of this, OpenSD vectorizes the stick position and returns "cleaner", round stick ranges, as well as being able to create clean deadzone rescaling. If you disable this setting, axis ranges are still internally normalized and rescaled to the the uinput device, but no vectorization will be applied and any deadzones will be ignored.

If set to true thumbsticks will be filtered (recommended).

Format:

StickFiltering  = <true | false>

Example:

[Features]
StickFiltering  = true

If unspecified this value defaults to true.

Note
This must be enabled for thumbstick deadzones to work.

TrackpadFiltering

Similar to StickFiltering, but matches the square shape of the trackpad. Filtering is only applied to absolute values. This setting must be enabled to apply deadzones to the trackpad absolute axes. Relative values (*PadRelX and *PadRelY) are unaffected, therefore deadzones do not affect mouse movement with the pads.

If set to true trackpads will be filtered (recommended).

Format:

TrackpadFiltering  = <true | false>

Example:

[Features]
TrackpadFiltering  = true

If unspecified, this value defaults to true.

Note
This must be enabled for trackpad deadzones to work.

[DeviceInfo] Section

This section is used to optionally configure the USB identification of each device OpenSD creates. While OpenSD has its own defaults, they can be overridden to make these devices appear as a different hardware. This can be useful when some very poorly written games only look for / support specific gamepads, rather than a universal API. Shame. Often, these games filter out all input devices which don’t match the device name string and/or the USB VID:PID. These settings enable the gamepad to mimic the appearance of a device which the software does recognise.

Format:

<device>    = <vid> <pid> <ver> <name>
  • device: Any OpenSD input device: Gamepad, Motion, or Mouse.

  • vid: The USB device VendorID to use. Format must be in hexidecimal.

  • pid: The USB device ProductID to use. Format must be in hexidecimal.

  • ver: The USB device version to use. Format must be in hexidecimal.

  • name: The USB device name string to use. Should be enclosed in quotes "" if it contains any whitespace characters.

Example:

[DeviceInfo]
Gamepad     = 0xDEAD 0xBEEF 0x0001 "OpenSD Gamepad Device"
Motion      = 0xDEAD 0xBEEF 0x0001 "OpenSD Motion Control Device"
Mouse       = 0xC475 0xF00D 0x0101 "OpenSD Mouse / Trackpad Device"
Note
Some software may also perform gamepad detection based on button and axis configuration. Axis settings can also be configured in the [GamepadAxes] Section and [MotionAxes] Section for this very purpose.

[Deadzones] Section

These values are double precision floating point and represent the percentage of the total axis range to ignore. A value of 0.05 would be a 5% deadzone. Deadzones are capped at 0.9 (90%). A value of 0 is considered disabled. If StickFiltering is disabled, LStick and RStick deadzones will be ignored. If TrackpadFiltering is disabled, LPad and RPad deadzones will be ignored.

Format:

<axis>      = <value>
  • axis: Any of the supported gamepad axes, which are: * LStick, RStick, LPad, RPad, LTrigg and RTrigg.

  • value: A double-precision floating point value between 0 and 0.9.

Example:

[Deadzones]
LStick      = 0.1
RStick      = 0.1
LPad        = 0
RPad        = 0
LTrigg      = 0
RTrigg      = 0

Any undefined axis deadzone will default to 0 (disabled).

Note
Because the Steam Deck thumbsticks tend not to return to center completely (at least on current revisions), a small deadzone of around 0.10 (10%) is generally recommended.

[GamepadAxes] Section

Gamepad absolute axes must have a defined range or they will not be created. Any Gamepad ABS_* events which are configured in the Gamepad Bindings section must be defined here first, or they will be ignored.

Internally, the axis values are normalized and rescaled between the actual hardware and the value seen by applications, so no clipping or "dead extremes" will occur. There is no "right" or "wrong" value here that you need to know, but it may be useful to precisely emulate other hardware so it can be detected as such by certain applications which try to guess what kind of device you have.

The Steam Deck hardware uses signed 16-bit integers (-32767 to 32767) for its thumbstick, trackpad, trigger and motion axes, so there’s no reason to use a larger or smaller range for those inputs, unless you are trying to emulate a specific device.

Hat-type axes (ABS_HAT*) should typically use a range of -1 to 1 because of thier historical purpose, but this is not strictly enforced.

Triggers should typically have minumum value of 0 so that they rest at zero when released.

Format:

<abs_event>     = <min> <max> [fuzz] [res]
  • abs_event: Any absolute axis event code you wish to bind. Absolute event codes begin with ABS_. A full list of input event codes can be found at linux/input-event-codes.h from the Linux kernel.

  • min: A value representing the minimum range of the axis. This is a signed 32-bit integer.

  • max: A value representing the maximum range of the axis. This is a signed 32-bit integer.

  • fuzz: Optional value representing the fuzziness of the axis values. This is a signed 32-bit integer.

  • res: Optional value representing the resolution of the axis in units/mm (units/radian for rotational axes). This is a signed 32-bit integer.

Example:

[GamepadAxes]
ABS_HAT0X       = -1        1
ABS_HAT0Y       = -1        1
ABS_X           = -32767    32767
ABS_Y           = -32767    32767
ABS_RX          = -32767    32767
ABS_RY          = -32767    32767
ABS_Z           = 0         32767
ABS_RZ          = 0         32767

[MotionAxes] Section

Motion control absolute axes, as with the gamepad device, must have a defined range or they will not be created. Any Motion ABS_* events which are configured in the Motion Bindings must be defined here first, or they will be ignored.

Internally, the axis values are normalized and rescaled between the actual hardware and the value seen by applications, so no clipping or "dead extremes" will occur. There is no "right" or "wrong" value here that you need to know, but it may be useful to precisely emulate other hardware so it can be detected as such by certain applications which try to guess what kind of device you have.

The Steam Deck hardware uses signed 16-bit integers (-32767 to 32767) for its thumbstick, trackpad, trigger and motion axes, so there’s no reason to use a larger or smaller range for those inputs, unless you are trying to emulate a specific device.

Format:

<abs_event>     = <min>     <max>
  • abs_event: Any absolute axis event code you wish to bind. Absolute event codes begin with ABS_. A full list of input event codes can be found at linux/input-event-codes.h from the Linux kernel.

  • min: An integer representing the minimum range of the axis. This is a signed 32-bit integer.

  • max: An integer representing the maximum range of the axis. This is a signed 32-bit integer.

Example:

[MotionAxes]
ABS_X           = -32767    32767
ABS_Y           = -32767    32767
ABS_Z           = -32767    32767
ABS_RX          = -32767    32767
ABS_RY          = -32767    32767
ABS_RZ          = -32767    32767
Note
Motion controls are not yet fully implemented.

[Bindings] Section

This should be a list of all the physical gamepad buttons/sticks/pads/motion inputs you want to bind to a virtual input event or command. Anything not specified here will be considered "unbound" and not emit any event.

There are currently four basic binding types: device bindings, Command bindings, Profile bindings and None.

Device bindings

Represent input events which are generated by pressing buttons, keys, moving the mouse, thumbsticks, motion control, etc. Event bindings are tied to specific input devices, which include Gamepad, Motion and Mouse. Applications read events from these different device types in different ways so they should generally be separated.

Command bindings

Executes a given command inside a shell environment.

Profile bindings

Used to switch to a different profile when triggered.

None

This is used to indicate that a particular input has no binding. (default)

Input binding names which this document will refer to as input or <input>, are represent physical buttons, triggers, axes, etc. on the physical gamepad portion of the Steam Deck. They can be broken down into a several categories for simplicity:

Directional Pad

DPad{Up|Down|Left|Right}

Buttons

A B X Y L1 L2 L3 L4 L5 R1 R2 R3 R4 R5 Menu Options Steam QuickAccess

Triggers

{L|R}Trigg

Thumbsticks

{L|R}Stick{Up|Down|Left|Right|Touch|Force}

Trackpads

{L|R}Pad{Up|Down|Left|Right|RelX|RelY|Touch|Press|Force}

Accelerometers

Accel{X|Y}{Plus|Minus}

Attitude / gyros

{Roll|Pitch|Yaw}{Plus|Minus}

Input names prefixed with L or R indicate left and right controls (example: LStickLeft vs RStickLeft)

Additionally, trackpads are mapped out into several button layouts simultaneously. This means that when pressed, specific areas of the trackpad behave like individual buttons. There are several "maps" which can be used non-exclusively. These are

Quadrant button maps

{L|R}PadPressQuad{Up|Down|Left|Right}

Orthogonal button maps

{L|R}PadPressOrth{Up|Down|Left|Right}

2x2 grid maps

{L|R}PadPressGrid2x2_{1|2|3|4}

3x3 grid maps

{L|R}PadPressGrid3x3_{1|2|3|4|5|6|7|8|9}

A full list of available input codes can be seen in the example profile section, as well as in default.profile file from your installation.

A detailed explanation of each of these inputs can be found in the Input Types section.


Gamepad Bindings

The Gamepad device binding is used to generate input events for a joystick / gamepad-type device. This generally means buttons (BTN_*) and absolute axis (ABS_*) events. KEY_* events are allowed, but many programs will not read KEY_* events from a joystick device, instead try binding key events to the Mouse device.

The syntax for bindings differs slightly depending on the event type. Absolute axis (EV_ABS) events are prefixed with ABS_ and key / button events (EV_KEY) are prefixed with KEY_ and BTN_ respectively. OpenSD supports most event codes. For a full list of event codes, see linux/input-event-codes.h from the Linux kernel.

When bound to a button-type input (example: the A button), the bind is triggered when the button is pressed. When bound to an axis-type input (example: LStickUp), the event is emitted when the axis is in a non-zero position and leaves the deadzone (if any).

For KEY / BTN events:

Format:

input = Gamepad <event_code>
  • input: Any one of the input binding names.

  • event_code: Any EV_KEY type event. These events are prefixed with BTN_ or KEY_. (example: BTN_START or KEY_ESCAPE)

Example:

[Bindings]
Menu = Gamepad BTN_START

 

For ABS events:

Format:

input = Gamepad <event_code> <direction>
  • input: Any one of the input binding names.

  • event_code: Any EV_ABS type event. These events are prefixed with ABS_. (example: ABS_X)

  • direction: Indicates the direction that the axis is moved in. Values may be + or -. For centered axes, like thubsticks, - represents moving the axis up or left, and + represents moving the axis down or right. For ramped axes, like triggers and pressure sensors, + represents applying pressure.

When binding a button-type input like a DPad direction or, say, the B button to an ABS event, the button will push the axis to its maximum extent in the given direction. When binding an analog axis, like a thumbstick, to an ABS value, the range of motion is mapped to the axis value in the given direction.

Examples:

[Bindings]
# Button mapped to an axis
DPadUp          = Gamepad   ABS_HAT0Y   -

# Analogue stick mapped to an axis
RStickUp        = Gamepad   ABS_Y       -
RStickDown      = Gamepad   ABS_Y       +

# Analogue trigger mapped to an axis
LTrigg          = Gamepad   ABS_Z       +

A full list of gamepad input names can be seen in the example profile.

A detailed explanation of inputs can be found in the Input Types section of this documentation.

Note
ABS events must have a defined range in the [GamepadAxes] Section

Motion Bindings

The Motion device binding is used to generate input events for a motion control-type device. While OpenSD does not strictly enforce this, the Linux kernel and uinput specify that motion control events should be emitted by separate devices. Not doing so can create a lot of "noise", especially when configuring controls within another application. As per this spec, the Motion device only supports EV_ABS type events. These events are prefixed with ABS_ (example: ABS_Z). For a full list of event codes, see linux/input-event-codes.h from the Linux kernel.

The syntax and behaviour for binding Motion device events is the same as binding ABS events with the Gamepad device in the previous section.

Also, the Motion device is a completely separate context and namespace from the Gamepad and Mouse devices, much in the same way that two players with identical controllers will have the same buttons, but very different meanings to the game. For example, pressing A on controller #1 does not affect player #2. Its up the the end-user’s software to decide the the context and meaning of the individual events.

Format:

input = Motion <event_code> <direction>
  • input: Any one of the input binding names.

  • event_code: Any EV_ABS type event. These events are prefixed with ABS_. (example: ABS_X)

  • direction: Indicates the direction that the axis is moved in. Values may be + or -.

Examples:

[Bindings]
# Bind roll attitude to Motion device
RollPlus        = Motion    ABS_X       +
RollMinus       = Motion    ABS_X       -

A full list of motion contol input names can be seen in the example profile.

A detailed explanation of motion input be found in the Input Types Section of this document.

Note
ABS events must have a defined range in the [MotionAxes] Section
Note
This feature is not yet fully implemented.

Mouse Bindings

The Mouse device binding is used to generate input events which will be interpreted as events coming from a pointer-type device such as a physical mouse. This binding type supports button / key and relative axis events. The Mouse device can also function a bit like a keyboard, so it’s advised to bind any key events to this device.

The syntax for bindings differs slightly depending on the event type. Relative axis (EV_REL) events are prefixed with REL_ and key / button events (EV_KEY) are prefixed with KEY_ and BTN_ respectively. OpenSD supports most event codes. For a full list of event codes, see linux/input-event-codes.h from the Linux kernel.

For KEY / BTN events:

input = Mouse <event_code>
  • input: Any one of the input binding names.

  • event_code: Any EV_KEY type event. These events are prefixed with BTN_ or KEY_. (example: BTN_LEFT or KEY_ESCAPE)

Example:

[Bindings]
RPadPress       = Mouse BTN_LEFT
QuickAccess     = Mouse BTN_RIGHT

For REL events:

[Bindings]
RPadRelX        = Mouse REL_X
RPadRelY        = Mouse REL_Y
  • input: Any one of the input binding names.

  • event_code: Any EV_REL type event. These events are prefixed with REL_. (example: REL_X)

Please see the Trackpad input type section of this documentation for a better explanation of how {L|R}PadRel{X|Y} relative inputs work.


Command Bindings

The Command binding allows you to execute external programs or scripts by forking them off as a child process. These processes run concurrently, do not return any usable exit code, and will not interrupt the driver.

Format:

input = Command <wait_for_exit> <repeat_delay_ms> <command_to_execute>
  • input: Any one of the input binding names. Best suited to button-types.

  • wait_for_exit: a true or false value which specifies if the command should complete before the binding can be triggered again.

  • repeat_delay_ms: The amount of time in milliseconds that must elapse before the binding can be triggered again. The timer starts when the binding is successfully triggered.

  • command_to_execute: The name of the command / script you want to run, same as you would from a terminal. The command executes normally inside a shell, so variable expansion should work. Arguments may be specfied by placing them after the command as usual.

Example:

[Bindings]
QuickAccess     = Command   true    0   rofi -show run

Profile Bindings

The Profile binding type allows you to switch to a different profile using just the gamepad input. Profiles are loaded from the user profile directory. There is a 2 second delay after profile switching before the profile can be changed again, to prevent undesirable rapid cycling. If a profile fails to load, the process will be aborted and the profile currently in use will remain so.

Format:

input = Profile <profile_name>
  • input: Any one of the input binding names. Best suited to button-types.

  • profile_name: Filename of the profile ini you want to load. Path is fixed to the user profile directory, so only specify the filename itself.

Example:

[Bindings]
L5      = Profile   left_hand_mouse.profile

Input Types

As briefly described in the Bindings section, the gamepad has multiple input components which can be categorized by their interface, but also by a primitive type. For example, the thumbsticks on the Steam Deck have a pair of X / Y axes (example: LStickUp, LStickLeft), which, as a primitive type are absolute, but the thumbsticks also have a touch sensor at the top which can be read as a binary button primitive (LStickTouch) as well as a pressure level (LStickForce) which is read as a single absolute axis like a trigger would be.

The intent is for each input name to be as simple and intuitive as possible, but that’s always going to be pretty subjective. This section intends to provide a painfully detailed explanation for every individual input that can have a binding ; )

Directional Pad

The directional pad is just a set of four buttons which are diametrically exclusive — you can press up and left simultaneously but you cannot press left and right simultaneously.

Naming Convention
  • DPad{Up|Down|Left|Right}

Descriptions
  • DPadUp: The top button on the directional pad.

  • DPadDown: The botton button on the directional pad.

  • DPadLeft: The left button on the directional pad.

  • DPadRight: The right button on the directional pad.

Use

While you can bind them to nearly anything, these buttons are usually bound to pair of Hat axes, which are typically axes with a range of -1 to 1 and 0 when resting / released.

  • Button bindings to KEY_ and BTN_ events work directly as you might expect.

  • Buttons bound to ABS_ events emit the axis limit in the given direction.

A common configuration, as seen in the example profile, might look like this:

[Bindings]
DpadUp              = Gamepad   ABS_HAT0Y   -
DpadDown            = Gamepad   ABS_HAT0Y   +
DpadLeft            = Gamepad   ABS_HAT0X   -
DpadRight           = Gamepad   ABS_HAT0X   +

Buttons

These are pretty straightforward. As you probably expect, buttons have two states. They’re true when pressed and false when released. The Steam Deck borrows common names for most buttons, but also adds a few of it’s own. It’s debatable how to organize or classify these, so I’ll just sorta go by the legacy naming standards.

Naming conventions
  • A B X Y {L|R}{1|2|3|4|5} Menu Options Steam QuickAccess

Descriptions
  • A: Same as it appears on the front of the Steam Deck.

  • B: Same as it appears on the front of the Steam Deck.

  • X: Same as it appears on the front of the Steam Deck.

  • Y: Same as it appears on the front of the Steam Deck.

  • L1: The top left bumper / shoulder button.

  • R1: The top right bumper / shoulder button.

  • L2: Button nested inside the pressure sensor of the left trigger.

  • R2: Button nested inside the pressure sensor of the right trigger.

  • L3: Button nested at the bottom of the left stick. Activated by pressing down until it clicks.

  • R3: Button nested at the bottom of the right stick. Activated by pressing down until it clicks.

  • L4: Upper paddle button located on the back-left side of the Steam Deck.

  • R4: Upper paddle button located on the back-right side of the Steam Deck.

  • L5: Lower paddle button located on the back-left side of the Steam Deck.

  • R5: Lower paddle button located on the back-right side of the Steam Deck.

  • Menu: Raised hamburger button (☰) located above the *right *thumbstick.

  • Options: Raised overlapped rectangle button (⮻) located above the left thumbstick.

  • Steam: Flat button of the same name (STEAM), located below the left trackpad.

  • QuickAccess: Flat button with three interpuncts (···), located below the right trackpad.

Use

Binding buttons is simple.

  • Button bindings to KEY_ and BTN_ events work directly.

  • Buttons bound to ABS_ events emit the axis limit in the given direction. For example, if you create a binding like A = ABS_X +, then when you press the A button, it will emit an ABS_X event for whatever the maximum axis limit for ABS_X is, whereas A = ABS_X - will emit the minimum axis limit.

  • A/B/X/Y have respective input codes for Gamepad devices: BTN_SOUTH, BTN_EAST, BTN_WEST and BTN_NORTH.

  • {L|R}{1|2} have respective input codes for Gamepad devices: BTN_TL BTN_TL2 BTN_TR and BTN_TR2.

  • {L|R}3 have respective input codes for Gamepad devices: BTN_THUMBL and BTN_THUMBR

  • Menu has a respective (based on location and common use) input code for Gamepad devices: BTN_START.

  • Options has a respective (based on location and common use) input code for Gamepad devices: BTN_SELECT.

  • Steam is probably closest to a PS or HOME button on a Gamepad device; possibly use BTN_MODE.


Thumbsticks

The thumbsticks on the Steam Deck are associated with six different inputs in OpenSD. As you would expect, there is an X/Y axis pair for each stick, but there are also touch and pressure (well, sorta) sensors located on the top of each one. The directional axes are broken into halves such that each direction can emit different events if desired.

Axis values from thumbsticks are normalized internally so they can be rescaled to the defined ABS event ranges. The hardware returns signed 16-bit integer values for axis values in the HID reports. The "pressure sensor" component has a very short numerical range, but is quite sensitive.

If Stick Filtering is enabled the full axis is internally normalized as a unit vector and a radial deadzone may be applied.

Naming convention
  • {L|R}Stick{Up|Down|Left|Right|Touch|Force}

Descriptions
  • LStickUp: Represents the top half of the Y-axis of the left thumbstick.

  • LStickDown: Represents the bottom half of the Y-axis of the left thumbstick.

  • LStickLeft: Represents the left half of the X-axis of the left thumbstick.

  • LStickRight: Represents the right half of the X-axis of the left thumbstick.

  • LStickTouch: This is a binary button that return true when the top of the left thumbstick is touched.

  • LStickForce: Capacitive touch sensor at the top of the left thumbstick. It is very sensitive and can register if your hand is near, without actually touching it. At a hardware level, the sensitivity ranges from 0 to ~112, which is an odd number. This value is returned as an normalized axis (0 to 1.0), just like a trigger.

  • RStickUp: Represents the top half of the Y axis of the right thumbstick.

  • RStickDown: Represents the bottom half of the Y axis of the right thumbstick.

  • RStickLeft: Represents the left half of the X axis of the right thumbstick.

  • RStickRight: Represents the right half of the X axis of the right thumbstick.

  • RStickTouch: This is a binary button that return true when the top of the right thumbstick is touched.

  • RStickForce: Capacitive touch sensor at the top of the right thumbstick. It is very sensitive and can register if your hand is near, without actually touching it. At a hardware level, the sensitivity ranges from 0 to ~112, which is an odd number. This value is returned as an normalized axis (0 to 1.0), just like a trigger.

Use

Directional inputs are treated like axis halves. You typically want to map Up and Down onto the same ABS event code, but in opposite directions. Doing so will perfectly map the full range of motion to an event code. An example of this, as demonstrated in the [Bindings] section of the example profile:

# Left Stick
LStickUp            = Gamepad   ABS_Y       -
LStickDown          = Gamepad   ABS_Y       +
LStickLeft          = Gamepad   ABS_X       -
LStickRight         = Gamepad   ABS_X       +
# Right Stick
RStickUp            = Gamepad   ABS_RY      -
RStickDown          = Gamepad   ABS_RY      +
RStickLeft          = Gamepad   ABS_RX      -
RStickRight         = Gamepad   ABS_RX      +

You’re also able to treat each axis half like a button if you bind it to a key or button event code, in which case it will trigger the binding when the stick leaves the center / deadzone. You can use the deadzone in this case to determine how far the stick must be pushed from center before the binding is triggered.

  • LStickLeft & LStickRight are typically bound to the ABS_X event code on Gamepad devices.

  • LStickUp & LStickDown are typically bound to the ABS_Y event code on Gamepad devices.

  • RStickLeft & RStickRight are typically bound to the ABS_RX event code on Gamepad devices.

  • RStickUp & RStickDown are typically bound to the ABS_RY event code on Gamepad devices.

  • Use LStickTouch / RStickTouch to detect if a players hands are on the controls.

  • Use LStickForce / RStickForce if you want to write a tiny electric theremin simulator? If this sensor is bound to an absolute axis, a range of 0 to 100 is recommened.


Triggers

Triggers are pressure sensors that are also treated a bit like a thumbstick’s half-axis, with the difference being there’s no complement half. Triggers have a resting position of the defined axis minimum limit and move toward the maximum limit when actuated. Typically the minimum limit is zero, so the axis does not return non-zero values when released / resting, but you can do any weird thing you want.

Axis values from triggers are normalized internally so they can be rescaled to the defined ABS event ranges. Internally, the HID reports return trigger values as unsigned 16-bit integers.

Triggers also have a binary button component: L2 and R2. Information about these buttons can be found in the Buttons section.

Linear deadzones can be applied to triggers, if desired in the Deadzones Section of a profile.

Naming convention
  • {L|R}Trigg

Descriptions
  • LTrigg: Represents a pressure sensor value for the left trigger on the top-rear of the device.

  • RTrigg: Represents a pressure sensor value for the right trigger on the top-rear of the device.

Use

These inputs are absolute axes and can be mapped to ABS event codes as well as KEY / BTN event codes. One possible reason to use the axis itself as a button-type binding would be to use L2 / R2 buttons on partial actuation, and use a deadzone to emit another event code on full actuation.

  • LTrigg is typically bound to ABS_Z.

  • RTrigg is typically bound to ABS_RZ.


Trackpads

At the core, trackpads are absolute axis devices have with an X / Y pair, as well as a pressure sensor Z-axis and a slightly tactile button. A number of inputs can be extrapolated from the data those basic types provide. That includes touch sensors, press / button sensors, pressure / force sensors, absolute coordinates, relative movement tracking as well as the ability to map out regions of the pad and treat them as individual buttons. OpenSD trackpads have the most input bindings out of all the components.

As with the Thumbsticks, directional axes are broken into halves such that each direction can emit different events if desired. These halves can be mapped on to a whole ABS event code, or use separately.

Trackpads support radial deadzones for absolute axis inputs, and can be configured in the Deadzones Section of a profile.

Relative trackpad input, such as LPadRelX or LPadRelY, are not affected by deadzones and return data suitable for pointing devices like mice. These return the difference in positional movement calculated between HID frames. Some filtering is always applied to this process to reduce jitter and a small amount of intertia is also applied. The typical value range returned by these inputs is usually between -5 and +5. Binding RelX and *RelY inputs to anything other than REL_ event codes on the Mouse device is probably not useful.

Touchpads are non-multitouch devices so they only relay a single X / Y coordinate pair. I’ve read that, at a hardware level, they are supposedly multi-touch capable, but I don’t have any information on reading that data yet. If true, it will probably be supported in a future version.

OpenSD provides multiple "button maps", any of which can be used non-exclusively with each other. These "button maps" break the full area of the trackpad into logical sections which, when pressed (as opposed to being merely touched) act as individual buttons. If so desired, trackpads can be used to create "button clusters", which can be used to emulate a Directional Pad, arrow keys, run scripts, launch applications, etc. For the sake of readability, button maps are described separately below the main input descriptions.

Naming convention
  • {L|R}Pad{Up|Down|Left|Right|Touch|Press|Force|RelX|RelY}

  • {L|R}PadPressQuad{Up|Down|Left|Right}

  • {L|R}PadPressOrth{Up|Down|Left|Right}

  • {L|R}PadPressGrid2x2_{1|2|3|4}

  • {L|R}PadPressGrid3x3_{1|2|3|4|5|6|7|8|9}

Descriptions
  • LPadUp: Represents the top half of the Y-axis of the left trackpad.

  • LPadDown: Represents the bottom half of the Y-axis of the left trackpad.

  • LPadLeft: Represents the left half of the X-axis of the left trackpad.

  • LPadRight: Represents the right half of the X-axis of the left trackpad.

  • LPadTouch: This is a button sensor which detects if the left pad is being touched. Quite sensitive.

  • LPadPress: This is also a button which detects if the left pad is being pressed down like a button. Actuation has a slight tactile bump.

  • LPadForce: This is a pressure sensor which returns a normalized value of how much force is being used to press down on the left trackpad. This is an absolute axis value and behaves the same as Triggers.

  • LPadRelX: This is a derived relative axis value that measures the amount of relative X-axis movement between update frames of the left trackpad. This represents the same kind of input data that mice use. This input is unaffected by deadzones. Typical values returned fall inside the range of -5 to +5.

  • LPadRelY: This is a derived relative axis value that measures the amount of relative Y-axis movement between update frames of the left trackpad. This represents the same kind of input data that mice use. This input is unaffected by deadzones. Typical values returned fall inside the range of -5 to +5.

  • RPadUp: Represents the top half of the Y-axis of the right trackpad.

  • RPadDown: Represents the bottom half of the Y-axis of the right trackpad.

  • RPadLeft: Represents the left half of the X-axis of the right trackpad.

  • RPadRight: Represents the right half of the X-axis of the right trackpad.

  • RPadTouch: This is a button sensor which detects if the right pad is being touched. Quite sensitive.

  • RPadPress: This is also a button which detects if the right pad is being pressed down like a button. Actuation has a slight tactile bump.

  • RPadForce: This is a pressure sensor which returns a normalized value of how much force is being used to press down on the right trackpad. This is an absolute axis value and behaves the same as Triggers.

  • RPadRelX: This is a derived relative axis value that measures the amount of relative X-axis movement between update frames of the right trackpad. This represents the same kind of input data that mice use. This input is unaffected by deadzones. Typical values returned fall inside the range of -5 to +5.

  • RPadRelY: This is a derived relative axis value that measures the amount of relative Y-axis movement between update frames of the right trackpad. This represents the same kind of input data that mice use. This input is unaffected by deadzones. Typical values returned fall inside the range of -5 to +5.

Quadrant Button Map

The quadrant map (PadPressQuad*) provides a way to treat each touchpad as being composed of four non-overlapping triagular quadrants, as depicted in the figure 1 below. Each button is inherently exclusive to the others in this map since the X / Y coordinate can only fall inside of one of these regions at a time. This means that this map will not detect any overlapping presses, like a and b when you press in the upper-left region.

Fig. 1

┌─────────────┐
│ \         / │
│  \   a   /  │
│   \     /   │
│    \   /    │
│     \ /     │
│ b    X    c │
│     / \     │
│    /   \    │
│   /     \   │
│  /   d   \  │
│ /         \ │
└─────────────┘
  • LPadPressQuadUp: The logical button on the left trackpad corresponding to fig. 1, region "a".

  • LPadPressQuadDown: The logical button on the left trackpad corresponding to fig. 1, region "d".

  • LPadPressQuadLeft: The logical button on the left trackpad corresponding to fig. 1, region "b".

  • LPadPressQuadRight: The logical button on the left trackpad corresponding to fig. 1, region "c".

  • RPadPressQuadUp: The logical button on the right trackpad corresponding to fig. 1, region "a".

  • RPadPressQuadDown: The logical button on the right trackpad corresponding to fig. 1, region "d".

  • RPadPressQuadLeft: The logical button on the right trackpad corresponding to fig. 1, region "b".

  • RPadPressQuadRight: The logical button on the right trackpad corresponding to fig. 1, region "c".


Orthogonal Button Map

The orthogonal map (PadPressOrth*) works similarly to a Directional Pad. As you can see below in figure 2, it demonstrates how the "a", "b", "c" and "d" regions represent orthogonal directions which are not strictly exclusive as they are with the Quadrant Button Map. If a diagonal corner is pressed, it triggers both orthogonally adjacent buttons. For example, pressing the upper-middle of the pad only triggers "a", but pressing the upper-left of the pad will trigger both "a" and "b".

Fig. 2

┌─────┬─────┬─────┐
│     │     │     │
│  ab │  a  │  ac │
│     │     │     │
├─────┼─────┼─────┤
│     │     │     │
│  b  │     │  c  │
│     │     │     │
├─────┼─────┼─────┤
│     │     │     │
│  db │  d  │  dc │
│     │     │     │
└─────┴─────┴─────┘
  • LPadPressOrthUp: The logical button on the left trackpad corresponding to fig. 2 regions containing "a".

  • LPadPressOrthDown: The logical button on the left trackpad corresponding to fig. 2 regions containing d".

  • LPadPressOrthLeft: The logical button on the left trackpad corresponding to fig. 2 regions containing "b".

  • LPadPressOrthRight: The logical button on the left trackpad corresponding to fig. 2 regions containing "c".

  • RPadPressOrthUp: The logical button on the right trackpad corresponding to fig. 2 regions containing "a".

  • RPadPressOrthDown: The logical button on the right trackpad corresponding to fig. 2 regions containing d".

  • RPadPressOrthLeft: The logical button on the right trackpad corresponding to fig. 2 regions containing "b".

  • RPadPressOrthRight: The logical button on the right trackpad corresponding to fig. 2 regions containing "c".


2x2 Grid Button Map

This button map divides the pad into regions along the center axes, resulting in four square buttons in each corner, as depicted in figure 3. Buttons are naturally exclusive, so only one can be pressed at a time. Buttons are enumerated left-to-right, top-to-bottom.

Fig. 3

┌────────┬────────┐
│        │        │
│        │        │
│   a    │    c   │
│        │        │
│        │        │
├────────┼────────┤
│        │        │
│        │        │
│   c    │    d   │
│        │        │
│        │        │
└────────┴────────┘
  • LPadPressGrid2x2_1: The logical button on the left trackpad corresponding to fig. 3, region "a".

  • LPadPressGrid2x2_2: The logical button on the left trackpad corresponding to fig. 3, region "b".

  • LPadPressGrid2x2_3: The logical button on the left trackpad corresponding to fig. 3, region "c".

  • LPadPressGrid2x2_4: The logical button on the left trackpad corresponding to fig. 3, region "d".

  • RPadPressGrid2x2_1: The logical button on the right trackpad corresponding to fig. 3, region "a".

  • RPadPressGrid2x2_2: The logical button on the right trackpad corresponding to fig. 3, region "b".

  • RPadPressGrid2x2_3: The logical button on the right trackpad corresponding to fig. 3, region "c".

  • RPadPressGrid2x2_4: The logical button on the right trackpad corresponding to fig. 3, region "d".


3x3 Grid Button Map

This button map divides the pad into thirds along both axes, resulting in a 3x3 grid of nine square buttons, as depicted in figure 4. These buttons are naturally exclusive to one another, so only one can be pressed at a time. Buttons are enumerated left-to-right, top-to-bottom.

Note
The resulting size of each button will be fairly small, so it may require a little practice and small thumbs to manipulate them precisely.

Fig. 4

┌─────┬─────┬─────┐
│     │     │     │
│  a  │  b  │  c  │
│     │     │     │
├─────┼─────┼─────┤
│     │     │     │
│  d  │  e  │  f  │
│     │     │     │
├─────┼─────┼─────┤
│     │     │     │
│  g  │  h  │  i  │
│     │     │     │
└─────┴─────┴─────┘
  • LPadPressGrid3x3_1: The logical button on the left trackpad corresponding to fig. 4, region "a".

  • LPadPressGrid3x3_2: The logical button on the left trackpad corresponding to fig. 4, region "b".

  • LPadPressGrid3x3_3: The logical button on the left trackpad corresponding to fig. 4, region "c".

  • LPadPressGrid3x3_4: The logical button on the left trackpad corresponding to fig. 4, region "d".

  • LPadPressGrid3x3_5: The logical button on the left trackpad corresponding to fig. 4, region "e".

  • LPadPressGrid3x3_6: The logical button on the left trackpad corresponding to fig. 4, region "f".

  • LPadPressGrid3x3_7: The logical button on the left trackpad corresponding to fig. 4, region "g".

  • LPadPressGrid3x3_8: The logical button on the left trackpad corresponding to fig. 4, region "h".

  • LPadPressGrid3x3_9: The logical button on the left trackpad corresponding to fig. 4, region "i".

  • RPadPressGrid3x3_1: The logical button on the right trackpad corresponding to fig. 4, region "a".

  • RPadPressGrid3x3_2: The logical button on the right trackpad corresponding to fig. 4, region "b".

  • RPadPressGrid3x3_3: The logical button on the right trackpad corresponding to fig. 4, region "c".

  • RPadPressGrid3x3_4: The logical button on the right trackpad corresponding to fig. 4, region "d".

  • RPadPressGrid3x3_5: The logical button on the right trackpad corresponding to fig. 4, region "e".

  • RPadPressGrid3x3_6: The logical button on the right trackpad corresponding to fig. 4, region "f".

  • RPadPressGrid3x3_7: The logical button on the right trackpad corresponding to fig. 4, region "g".

  • RPadPressGrid3x3_8: The logical button on the right trackpad corresponding to fig. 4, region "h".

  • RPadPressGrid3x3_9: The logical button on the right trackpad corresponding to fig. 4, region "i".


Troubleshooting

TODO: This section <<<

Example config.ini

[Daemon]
# The gamepad profile to be loaded on startup
Profile = default.profile

# Allow client connections from CLI and GUI configuration tools
AllowClients = true

Example Profile

[Profile]
Name            = Example Profile
Description     = Just an example profile to show basic use

[Features]
ForceFeedback      = true
MotionDevice       = true
MouseDevice        = true
LizardMode         = false
StickFiltering     = true
TrackpadFiltering  = true

[Deadzones]
LStick    = 0.1
RStick    = 0.1
LPad      = 0
RPad      = 0
LTrigg    = 0
RTrigg    = 0

[GamepadAxes]
ABS_HAT0X    = -1        1
ABS_HAT0Y    = -1        1
ABS_X        = -32767    32767
ABS_Y        = -32767    32767
ABS_RX       = -32767    32767
ABS_RY       = -32767    32767
ABS_Z        = 0         32767
ABS_RZ       = 0         32767

[MotionAxes]
ABS_X        = -32767    32767
ABS_Y        = -32767    32767
ABS_Z        = -32767    32767
ABS_RX       = -32767    32767
ABS_RY       = -32767    32767
ABS_RZ       = -32767    32767

[Bindings]
DpadUp              = Gamepad   ABS_HAT0Y   -
DpadDown            = Gamepad   ABS_HAT0Y   +
DpadLeft            = Gamepad   ABS_HAT0X   -
DpadRight           = Gamepad   ABS_HAT0X   +
# Buttons
A                   = Gamepad   BTN_SOUTH
B                   = Gamepad   BTN_EAST
X                   = Gamepad   BTN_WEST
Y                   = Gamepad   BTN_NORTH
L1                  = Gamepad   BTN_TL
R1                  = Gamepad   BTN_TR
L2                  = Gamepad   BTN_TL2
R2                  = Gamepad   BTN_TR2
L3                  = Gamepad   BTN_THUMBL
R3                  = Gamepad   BTN_THUMBR
L4                  = None
R4                  = None
L5                  = None
R5                  = None
Menu                = Gamepad   BTN_START
Options             = Gamepad   BTN_SELECT
Steam               = Gamepad   BTN_MODE
QuickAccess         = Command   true        0   rofi -show drun
# Triggers
LTrigg              = Gamepad   ABS_Z       +
RTrigg              = Gamepad   ABS_RZ      +
# Left Stick
LStickUp            = Gamepad   ABS_Y       -
LStickDown          = Gamepad   ABS_Y       +
LStickLeft          = Gamepad   ABS_X       -
LStickRight         = Gamepad   ABS_X       +
LStickTouch         = None
LStickForce         = None
# Right Stick
RStickUp            = Gamepad   ABS_RY      -
RStickDown          = Gamepad   ABS_RY      +
RStickLeft          = Gamepad   ABS_RX      -
RStickRight         = Gamepad   ABS_RX      +
RStickTouch         = None
RStickForce         = None
# Left Trackpad
LPadUp              = None
LPadDown            = None
LPadLeft            = None
LPadRight           = None
LPadRelX            = None
LPadRelY            = None
LPadTouch           = None
LPadPress           = Mouse     BTN_LEFT
LPadForce           = None
LPadPressQuadUp     = None
LPadPressQuadDown   = None
LPadPressQuadLeft   = None
LPadPressQuadRight  = None
LPadPressOrthUp     = None
LPadPressOrthDown   = None
LPadPressOrthLeft   = None
LPadPressOrthRight  = None
LPadPressGrid2x2_1  = None
LPadPressGrid2x2_2  = None
LPadPressGrid2x2_3  = None
LPadPressGrid2x2_4  = None
LPadPressGrid3x3_1  = None
LPadPressGrid3x3_2  = None
LPadPressGrid3x3_3  = None
LPadPressGrid3x3_4  = None
LPadPressGrid3x3_5  = None
LPadPressGrid3x3_6  = None
LPadPressGrid3x3_7  = None
LPadPressGrid3x3_8  = None
LPadPressGrid3x3_9  = None
# Right Trackpad
RPadUp              = None
RPadDown            = None
RPadLeft            = None
RPadRight           = None
RPadRelX            = Mouse     REL_X
RPadRelY            = Mouse     REL_Y
RPadTouch           = None
RPadPress           = Mouse     BTN_RIGHT
RPadForce           = None
RPadPressQuadUp     = None
RPadPressQuadDown   = None
RPadPressQuadLeft   = None
RPadPressQuadRight  = None
RPadPressOrthUp     = None
RPadPressOrthDown   = None
RPadPressOrthLeft   = None
RPadPressOrthRight  = None
RPadPressGrid2x2_1  = None
RPadPressGrid2x2_2  = None
RPadPressGrid2x2_3  = None
RPadPressGrid2x2_4  = None
RPadPressGrid3x3_1  = None
RPadPressGrid3x3_2  = None
RPadPressGrid3x3_3  = None
RPadPressGrid3x3_4  = None
RPadPressGrid3x3_5  = None
RPadPressGrid3x3_6  = None
RPadPressGrid3x3_7  = None
RPadPressGrid3x3_8  = None
RPadPressGrid3x3_9  = None
# Accelerometers
AccelXPlus          = Motion    ABS_RX      +
AccelXMinus         = Motion    ABS_RX      -
AccelYPlus          = Motion    ABS_RY      +
AccelYMinus         = Motion    ABS_RY      -
AccelZPlus          = Motion    ABS_RZ      +
AccelZMinus         = Motion    ABS_RZ      -
# Gyro / Attitude
RollPlus            = Motion    ABS_X       +
RollMinus           = Motion    ABS_X       -
PitchPlus           = Motion    ABS_Y       +
PitchMinus          = Motion    ABS_Y       -
YawPlus             = Motion    ABS_Z       +
YawMinus            = Motion    ABS_Z       -