Users Guide to FGInput - Joystick And Keyboard Bindings For FlightGear

Or The document formerly know as

The Users Guide to Joystick Usage Under FlightGear Flight Simulator"

version 0.7.7.3 07/02/2001 Author John Check
  1. Some History
  2. First Things First
  3. About XML
  4. Determining Your Joystick Output
  5. Default Joystick Properties
  6. Okay, Now What?
  7. Modifiers For Raw Joystick Values
  8. The Command Manager
  9. Bindings
  10. Joystick Axes
  11. Button Properties
  12. Digital Coolie Hats
  13. Keyboard Bindings

This document is written with versions of FlightGear 0.7.7 and greater in mind. It assumes a working joystick present on your system. It is written from the perspective a Linux user, but the information presented is valid on other platforms. The most current version is available online.

Thanks to David Megginson, who aside from actually implementing FGFS XML features, lets me rip off his descriptions of how stuff works so I can look smart.


Some History:


Earlier versions of FGFS had assignments of joystick axis/buttons and key bindings hard coded. If you had a joystick that did not use the default channel assignments, or wanted different key bindings, you had to edit the source code and recompile.

Fortunately, around about v0.7.5 a "property manager" was implemented, which facilitated being able to set the parameters for the joystick at runtime. Version 0.7.7 saw an expanded role for the property manager and the addition of a "command manager" that allows for binding of events to commands. The code that does this is known as FGInput and is used to configure keyboard command bindings as well as joysticks.

Version 0.7.9 added a compiled decision tree allowing for conditionals to be used.


First Things First:


I will cover joysticks first and save the keyboard stuff for later. FGInput treats things in a generic enough way that the line between joystick buttons and keyboard events starts to blur.

Storing alternate keyboard or joystick bindings can be done in a variety of ways. The order of precedence for options is thus:

Source        Location  Format                Scope
------        --------  ------                -----
command line  STDIN     see examples          session
.fgfsrc       ~/        command line options  single user
system.fgfsrc $FG_ROOT  command line options  system wide
joystick.xml  $FG_ROOT  XML property list     system wide
keyboard.xml  $FG_ROOT  XML property list     system wide


About XML:


In case you were wondering, XML stands for eXtensible Markup Language. It looks a lot like HTML, except you get to define your own tags. Well, in the case of FGFS we defined the tags you need to configure things. It is well suited for describing hierarchically organized structures, such as the property tree FGFS uses to expose the it's innards to external applications. Your XML configuration files for FGFS must start and end with the following pair of tags.

<PropertyList>
 <!-- this is a comment, See I told you it was like HTML -->
</PropertyList>


Determining your joystick output:


FlightGear ships with a utility called js_demo. It will report the number of joysticks attached to a system and their capabilites. By observing the output of js_demo while working your joystick you can determine what controls are where. It should be noted that, at least on UNIX, numbering generally starts with zero. In the following example the system has 1 joystick (js0) connected. The output shown is from an analog Gravis BlackHawk with four buttons and a throttle.

Typical output of js_demo:

Joystick test program.
~~~~~~~~~~~~~~~~~~~~~~
Joystick 1 not detected    <!-- remember we start at 0 -->
Joystick 2 not detected
+--------------JS.0--------------+
| Btns    Ax:0   Ax:1    Ax:2    |                    
+--------------------------------+
| 0000    +0.0   +0.0    -1.0    |

The buttons are handled internally as a binary number in which bit 0 (the least significant bit) represents button 0, bit 1 represents button 1, etc., but this number is displayed on the screen in hexadecimal notation, so:

  0001 => button 0 pressed
  0002 => button 1 pressed
  0004 => button 2 pressed
  0008 => button 3 pressed
  0010 => button 4 pressed
  0020 => button 5 pressed
  0040 => button 6 pressed
  ... etc. up to ...
  8000 => button 15 pressed
  ... and ...
  0014 => buttons 2 and 4 pressed simultaneously
  ... etc.


Default Joystick Properties:


 Axis 0    =  Aileron
 Axis 1    =  Elevator
 Axis 2    =  Rudder
 Axis 3    =  Throttle 
 Button 0  =  All brakes
 Button 1  =  Elevator trim (up)
 Button 2  =  Elevator trim (down)


Okay, Now what?


Now that you know what the output of the devices connected to the joystick port (or USB port joystick devices) is, you probably want to dive straight in and to start connecting to FGInput. If you are familiar with configuring the joystick on versions of FGFS prior to 0.7.7 you can skip down to the section "The Command Manager".

If you are a new FGFS user, you should at least skim the next bit since it explains some concepts you may or may not know. It also covers some legacy joystick options which have not been implemented yet in the context of the command manager.


Modifiers For Raw Joystick Values


These concepts are expressed by supplying arguments to the joystick bindings. The raw values coming from the joystick axes may not be suitable to use directly. For that matter not all joysticks are created equal so understanding the basic concepts should save you some time when experimenting to get the best performance.

The full order of precedence for axis properties is

1. The raw axis value ...
2. is filtered by dead-band, ... (deadband is implemented outside the command manager.)
3. then adjusted to offset, ...
4. then multiplied by factor, ...
5. the resulting value is assigned to the FlightGear control property.

Put another way....

cooked_value = (( raw_value > dead-band ) + offset) * factor

Axis properties:

    dead-band

-1      0      1
 ...............
-1     | |     1
        ^
    dead-band

This is an area where signals are ignored. It is used to compensate for noise, or potentiometers of dubious quality by creating a threshold below which any signal is suppressed. dead-band is relative to zero.

The default of 0.1 for elevators and ailerons is very forgiving. A lower number results in a tighter feel. In some cases such as throttle you may wish to not set a deadband. Use a value of 0.0 in this case.

     offset
-1 0         1
 .............
-1 ^         1
 offset

Used to maximize a controls use of it's axis, as in the case of a throttle where zero would be a minimum and not a center point like in the case of a rudder. Typical value -1.0.


tolerance

Used to compensate for jittery potentiometer output, tolerance is in essence a gate. Amounts of movement below the threshold are ignored. Unlike dead-band, tolerance is relative to where ever the axis is at rest.


factor

Controls proportional movement of an axis. If the factor is too low it results in control not reaching its maximum possible limit. Negating the number will result in the control moving counter to the default. The default value is 1.0, think unity gain.

In my case, throttle behavior was inverted from what I preferred. I set this value to -1.0 and everything was groovy.


The Command Manager:


Previous versions of FGFS allowed joystick output to be bound directly to the property manager. This has changed for FGFS v0.7.7 and now events are bound to commands. Commands *must* be specified for a binding to have an effect. The current list of commands is broken down here into two categories, mainly for my convenience.

Visual And File Related:
command           options      used for
-------           -------      --------
null              none         clearing a previous binding
exit              none         Exiting FGFS
load              file name*   Load a saved flight
save              file name*   Save current flight
load-panel        path **      Change/reload panel
load-preferences  path **      Load preferences ***
screen-capture    none         Save a screenshot to ./fgfs-screen.ppm
view-cycle        none         Change the direction of the pilots view


*Saved/loaded relative to current working directory.
**The path includes the filename you wish to load and is relative to $FG_ROOT, which is the location of the installed base package. The default for load-panel is the value of /sim/panel/path (from preferences.xml) or if that is unset Panel/Default/default.xml. The default for load preferences is preferences.xml. ***This might make a good first binding to experiment with, since it's not currently bound to anything. Reloading preferences will allow you to test settings without having to sit through a restart of FGFS for every edit. You can always (re)move it later. Flight Control: command options effect ------- ------- ------ property-toggle property toggle the property full on property-assign "" "", value targets a property for action property-adjust "" "", step Increment size for changes property-swap "" ""[0], "" ""[1] Set values in a switch property-scale "" "", offset, factor Processes the raw joystick value


Bindings:


A command may have more than one binding. By default, the examples below use just /binding or <binding>, but /binding[0] or <binding n="0"> is implied. When bindings are specified in XML the indices are created automagically. If you wish to avoid XML you must supply the index number for multiple bindings in your command line formatted options.


Joystick Axes:


Here's a sample Joystick axis declaration in XML:

<axis n="0">                   <!-- target an axis -->
 <desc>Aileron</desc>          <!-- descriptive name (optional) -->
 <binding>                     <!-- open a container for the binding -->
  <command>property-scale</command>    <!-- pick a command -->
  <property>/controls/flight/aileron</property>   <!-- target a property -->
 </binding>                    <!-- closing tag for binding -->
</axis>                        <!-- closing tag for axis -->

Remember how I said the property tree was a hierarchy? Thoughtful readers will notice how the nested tags keep things organized. This binding appears in the context /input/joysticks/js/, so the command-line option equivalent of this declaration (leaving out the 'desc', which isn't strictly necessary), is

  --prop:/input/joysticks/js[0]/axis[0]/binding/command=property-scale
  --prop:/input/joysticks/js[0]/axis[0]/binding/property=/controls/flight/aileron

Do you see how the command line versions uses a path to represent the hierarchy? Cool! You should read README.xmlpanel next, working with FGFS XML configuration system is easy and it's fun for the whole family! ( not sold in stores. excludes tax and title. void where prohibited by law.)

Ok, back to business. The 'property-scale' command also recognizes 'offset' and 'factor' arguments. The "type" arguments shown in the following example are specifying a double precision floating point number. A double has more discrete steps and gives a smoother action than a plain float.


<axis n="2">
 <desc>Throttle</desc>
 <!-- See important note about dead-band below -->
 <binding>
  <command>property-scale</command>
  <property>/controls/engines/engine[0]/throttle</property>
  <offset type="double">-1.0</offset>
  <factor type="double">-0.5</factor>
 </binding>
</axis>

or

 --prop:/input/joysticks/js[0]/axis[2]/binding/command=property-scale
 --prop:/input/joysticks/js[0]/axis[2]/binding/property=/controls/engiens/engine[0]/throttle
 --prop:/input/joysticks/js[0]/axis[2]/binding/offset=-1.0
 --prop:/input/joysticks/js[0]/axis[2]/binding/factor=-0.5

*Important Note About dead-band*
--------------------------------

You may recall from the section about raw axis value modifiers that dead-band is implemented outside the command manager. This means that if you want to apply a dead-band, the tag *must* precede the binding tag. If you are using the command line format you must omit the 'binding' part like so:

  --prop:/input/joysticks/js[0]/axis[2]/dead-band=0.005


Joystick Button Properties:


Buttons, being boolean by nature, can use a little help. By this I mean that there are times when you need momentary action, times where you need a repeating action and sometimes you just want a plain old toggle. In order to facilitate this need we have some tags that modify the actions of buttons.


<repeatable> 
<!-- Will be either true or false. If it is true, holding down a button (or key) will cause 
repeated events, say, for moving the trim; if false (the default), pressing a key will cause only a single event. -->

<step>
<!-- The property-adjust command takes a 'step' argument specifying the amount of the adjustment 
for each event. In the following example, the elevator trim moves 0.1% for each event (without automatic 
repetition, you'd have a pretty sore finger). -->

<value>
<!-- Use value on non-repeatables to supply the value for each consecutive press-->
<mod-up>
<!-- This stands for "modifier up", my favorite I think. This is used to set up a binding for 
when you *release* a key. As you'll see, it comes in handy -->

Here's a sample joystick button declaration in XML:

<button n="1">                        <!-- target a button -->
 <desc>Elevator trim up</desc>  <!-- optional description -->
 <repeatable>true</repeatable>  <!-- Ok, repeatable is outside the command manager too -->
  <binding>                           <!-- Open the "binding" node of the tree-->
   <command>property-adjust</command>           <!-- pick a command type to bind -->
   <property>/controls/flight/elevator-trim</property> <!-- target a property -->
   <step type="double">0.001</step>
  </binding>
</button>

In command-line option syntax, this would appear as

  --prop:/input/joysticks/js[0]/button[1]/repeatable=true     <-- See? no 'binding' -->
  --prop:/input/joysticks/js[0]/button[1]/binding/command=property-adjust
  --prop:/input/joysticks/js[0]/button[1]/binding/property=/controls/flight/elevator-trim
  --prop:/input/joysticks/js[0]/button[1]/binding//step=0.001

Here's a slightly fancier declaration, that applies the left (differential) brakes when button 4 is pressed, and releases them automatically when the user releases the button:

<button n="4">
 <desc>Left brake</desc>
 <binding>
  <command>property-assign</command>
  <property>/controls/gear/wheel[0]/brake</property>
  <value type="double">1.0</value>    <!-- brakes are a toggle so 1.0 represents on -->
  </binding>
   <mod-up>                    <!-- it's not a parking brake so we need to release it -->
    <binding>
    <command>property-assign</command>
    <property>/controls/gear/wheel[0]/brake</property>
    <value type="double">0.0</value>       <!-- 1.0 is on so 0.0 is off, right? -->
   </binding>
  </mod-up>
</button>

The first binding is straight-forward: when the button is pressed, the 'property-assign' command assigns the value 1.0 (i.e. full) to the left brake property. The second binding, however, is nested inside a 'mod-up' element, it will be fired when the user *releases* the button, and will use the 'property-assign' command to assign the value 0.0 (i.e. none) to the left brake property. Repetition is left at the default value of false, so that the same value will not be assigned over and over again.

Here's the command-line equivalent:

  --prop:/input/joysticks/js[0]/button[4]/binding/command=property-assign
  --prop:/input/joysticks/js[0]/button[4]/binding/property=/controls/gear/wheel[0]/brake
  --prop:/input/joysticks/js[0]/button[4]/binding/value=1.0
  --prop:/input/joysticks/js[0]/button[4]/mod-up/binding/command=property-assign
  --prop:/input/joysticks/js[0]/button[4]/mod-up/binding/property=/controls/gear/wheel[0]/brake
  --prop:/input/joysticks/js[0]/button[4]/mod-up/binding/value=0.0

Remember that more than one binding can be included in each context. Here's a very hairy example from the default bindings that fires all three brakes when button 0 is pressed, and releases all three when button 0 is released:

 <button n="0">
  <desc>Brakes</desc>
  <binding>
   <command>property-assign</command>
   <property>/controls/gear/wheel[0]/brake</property>
   <value type="double">1.0</value>
  </binding>
  <binding>
   <command>property-assign</command>
   <property>/controls/gear/wheel[1]/brake</property>
   <value type="double">1.0</value>
  </binding>
  <binding>
   <command>property-assign</command>
   <property>/controls/gear/wheel[2]/brake</property>
   <value type="double">1.0</value>
  </binding>
  <mod-up>
   <binding>
    <command>property-assign</command>
    <property>/controls/gear/wheel[0]/brake</property>
    <value type="double">0.0</value>
   </binding>
   <binding>
    <command>property-assign</command>
    <property>/controls/gear/wheel[1]/brake</property>
    <value type="double">0.0</value>
   </binding>
   <binding>
    <command>property-assign</command>
    <property>/controls/gear/wheel[2]/brake</property>
    <value type="double">0.0</value>
   </binding>
  </mod-up>
</button>

For people who take pleasure in avoiding XML, here's the command-line equivalent (note the subscripts to distinguish multiple bindings; the XML will handle this automatically):

  --prop:/input/joysticks/button[0]/binding[0]/command=property-assign
  --prop:/input/joysticks/button[0]/binding[0]/property=/controls/gear/wheel[0]/brake
  --prop:/input/joysticks/button[0]/binding[0]/value=1.0
  --prop:/input/joysticks/button[0]/binding[1]/command=property-assign
  --prop:/input/joysticks/button[0]/binding[1]/property=/controls/gear/wheel[1]/brake
  --prop:/input/joysticks/button[0]/binding[1]/value=1.0
  --prop:/input/joysticks/button[0]/binding[2]/command=property-assign
  --prop:/input/joysticks/button[0]/binding[2]/property=/controls/gear/wheel[2]/brake
  --prop:/input/joysticks/button[0]/binding[2]/value=1.0
  --prop:/input/joysticks/button[0]/mod-up/binding[0]/command=property-assign
  --prop:/input/joysticks/button[0]/mod-up/binding[0]/property=brakes[0]
  --prop:/input/joysticks/button[0]/mod-up/binding[0]/value=0.0
  --prop:/input/joysticks/button[0]/mod-up/binding[1]/command=property-assign
  --prop:/input/joysticks/button[0]/mod-up/binding[1]/property=brakes[1]
  --prop:/input/joysticks/button[0]/mod-up/binding[1]/value=0.0
  --prop:/input/joysticks/button[0]/mod-up/binding[2]/command=property-assign
  --prop:/input/joysticks/button[0]/mod-up/binding[2]/property=brakes[2]
  --prop:/input/joysticks/button[0]/mod-up/binding[2]/value=0.0

This is a good time to remind you that command line formatted options are used in your .fgfsrc file. You can put them in $FG_ROOT/system.fgfsrc to make them global.


Digital Coolie Hats:


Many common joysticks come with digital coolie hats. These are detected as axes rather than as buttons, although they are in fact just four (or eight) simple switches. FGFS provides 2 virtual buttons to every axis which are triggered whenever the axis reaches one of the end positions. These virtual buttons can be addressed via two sub-properties »low« and »high« and accept any of the common button properties.

For example, if you just want to control view direction, you can map two of the axes to /sim/view/axes/long and /sim/view/axes/lat:

  --prop:/input/joysticks/js/axis[5]/binding/command=property-scale
  --prop:/input/joysticks/js/axis[5]/binding/property=/sim/view/axes/lat

  --prop:/input/joysticks/js/axis[6]/binding/command=property-scale
  --prop:/input/joysticks/js/axis[6]/binding/property=/sim/view/axes/long

If you want to use them as buttons (say, to scroll the panel vertically or horizontally), you can use the high/low bindings. Here's an example of how to use an axis to adjust the elevator trim:

  --prop:/input/joysticks/js/axis[1]/low/repeatable=true
  --prop:/input/joysticks/js/axis[1]/low/binding/command=property-adjust
  --prop:/input/joysticks/js/axis[1]/low/binding/property=/controls/flight/elevator-trim
  --prop:/input/joysticks/js/axis[1]/low/binding/step=0.001

  --prop:/input/joysticks/js/axis[1]/high/repeatable=true
  --prop:/input/joysticks/js/axis[1]/high/binding/command=property-adjust
  --prop:/input/joysticks/js/axis[1]/high/binding/property=/controls/flight/elevator-trim
  --prop:/input/joysticks/js/axis[1]/high/binding/step=-0.001

If you want you can specify a time interval for processing digital hat repeats. For example if you want an interval of 0.05 seconds, which will effect up to 20 repeats per second, then add the following to the property:

  --prop:/input/joysticks/js/axis[1]/interval-sec=0.05

You could also bind some axes to brakes, so that you can use positions between between 0.0 (no brakes) and 1.0 (full brakes).


Keyboard Bindings:


Keyboard bindings are almost exactly identical to joystick-button bindings, as in the following example (the context is /input/keyboard):

 <key n="1">
  <name>Ctrl-A</name>
  <desc>Toggle autopilot altitude lock.</desc>
  <binding>
   <command>property-toggle</command>
   <property>/autopilot/locks/altitude</property>
  </binding>
 </key>

There are a few gotcha's though. First, the index of the key specifies the key code that you're binding; in the above example, '1' corresponds to Ctrl-A (as mentioned in the friendly comment). Regular key codes go up to 255; codes from 256 up represent special keys like function and arrow keys (you can get the value from include/GL/glut.h, but adding 256 to the special key code). Here is the command-line equivalent of the above, leaving out the documentation properties:

  --prop:/input/keyboard/key[1]/binding/command=property-toggle
  --prop:/input/keyboard/key[1]/binding/property=/autopilot/locks/altitude

(The 'property-toggle' command switches a boolean value between true and false, so it needs no arguments except for the property name.) DON'T LEAVE OUT THE INDEX!!!

The second gotcha is that keys can take more modifiers than joystick buttons. In addition to mod-up (representing the key release), a key may use any combination of nested 'mod-alt', 'mod-ctrl', and 'mod-shift' modifiers, as in the following example:

 <key n="49">
  <name>1</name>
   <mod-shift>
   <desc>Look back left</desc>
   <binding>
    <command>property-assign</command>
    <property>/sim/view/goal-offset</property>
    <value type="double">135</value>
   </binding>
  </mod-shift>
 </key>

In this example, the '1' key combined with shift causes the view to switch to back left. Note that this will work only with the keypad 1, since pressing shift+1 on the regular keyboard will give a '!' character instead.

The input module tries to be smart about supplying control and shift modifiers automatically where they make sense -- note that it wasn't necessary to use a nested 'mod-ctrl' element for the ctrl-A binding earlier, since a keycode less that 32 implies a control character anyway.

With the new input module, the key-up events can also be detected for the keyboard, so it's possible to have touch-sensitive brakes (etc.) just as with the joystick:

 <key n="44">
  <name>,</name>
  <desc>Left brake</desc>
   <binding>
   <command>property-assign</command>
   <property>/controls/gear/wheel[0]/brake</property>
   <value type="double">1.0</value>
  </binding>
  <mod-up>
   <binding>
   <command>property-assign</command>
   <property>/controls/gear/wheel[0]/brake</property>
   <value type="double">0.0</value>
   </binding>
  </mod-up>
 </key>

Now here is a different way to bind a brake. In this example, there is no <mod-up> tag, so it *does* work like a parking brake.

 <key n="66"> 
  <name>B</name>
  <desc>Toggle parking brake on or off</desc>
  <binding>
   <command>property-toggle</command>
   <property>/controls/gear/wheel[0]/brake</property>
  </binding>
  <binding>
   <command>property-toggle</command>
   <property>/controls/gear/wheel[1]/brake</property>
  </binding>
  <binding>
   <command>property-toggle</command>
   <property>/controls/gear/wheel[2]/brake</property>
  </binding>
 </key>