Introduction
A few years ago I wrote on using XNA to access the Xbox 360 controller from a Windows Form application. I had received
a question about accessing the controller in an environment in which the XNA assemblies wouldn't be appropriate. So I
wrote this code as an example of accessing the controller without the XNA assemblies. This is still a managed program.
But it makes use of P/Invoke to use the functionality provided in the DirectX library.
Prerequisites
I wrote this code using Visual Studio 2012. It makes use of the XInput
dll which is already part of Windows. So no additional
installations are needed to use the code.
What is XInput
DirectX is composed of many libraries. There's Direct3D for for rendering 3D graphics, DirectSound for playing sound, and so on. DirectInput
contains functionality for accessing various input devices (joysticks, keyboards, and more). But the Xbox 360 controller is not accessed
through that library. It is accessed through a different library named XInput
. This library is dedicated to the the Xbox 360 controller.
Let's take a look at what is in the C language header file to see what functionality it provides. On my computer I can find the header
in C:\Program Files (x86)\Windows Kits\8.0\Include\um\Xinput.h
.
The header starts with defining the name of the DLL in which we will find the functionality.
#if(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
#define XINPUT_DLL_A "xinput1_4.dll"
#define XINPUT_DLL_W L"xinput1_4.dll"
#else
#define XINPUT_DLL_A "xinput9_1_0.dll"
#define XINPUT_DLL_W L"xinput9_1_0.dll"
#endif
When a program is being compiled for an OS version before Windows 8 the functionality in the xinput1_4.dll
. For programs
compiled for Widnows 8 the functionality from xinput9_1_0.dll
will be used. If you wanted to target both platforms you could
make use of the older DLL (xinput9_1_0.dll
). It will work on Windows 8 too. But the newer DLL (xinput1_4.dll
)
contains functionality for accessing the battery .
typedef struct _XINPUT_GAMEPAD
{
WORD wButtons;
BYTE bLeftTrigger;
BYTE bRightTrigger;
SHORT sThumbLX;
SHORT sThumbLY;
SHORT sThumbRX;
SHORT sThumbRY;
} XINPUT_GAMEPAD, *PXINPUT_GAMEPAD;
typedef struct _XINPUT_STATE
{
DWORD dwPacketNumber;
XINPUT_GAMEPAD Gamepad;
} XINPUT_STATE, *PXINPUT_STATE;
typedef struct _XINPUT_VIBRATION
{
WORD wLeftMotorSpeed;
WORD wRightMotorSpeed;
} XINPUT_VIBRATION, *PXINPUT_VIBRATION;
typedef struct _XINPUT_CAPABILITIES
{
BYTE Type;
BYTE SubType;
WORD Flags;
XINPUT_GAMEPAD Gamepad;
XINPUT_VIBRATION Vibration;
} XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES;
There's also a section that contains the newer structures for functionality that became available with Windows 8.
#if(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
typedef struct _XINPUT_BATTERY_INFORMATION
{
BYTE BatteryType;
BYTE BatteryLevel;
} XINPUT_BATTERY_INFORMATION, *PXINPUT_BATTERY_INFORMATION;
typedef struct _XINPUT_KEYSTROKE
{
WORD VirtualKey;
WCHAR Unicode;
WORD Flags;
BYTE UserIndex;
BYTE HidCode;
} XINPUT_KEYSTROKE, *PXINPUT_KEYSTROKE;
#endif
Let's take a look at the structures redefined for .Net and C#.
struct XInputGamePad
The gamepad has two types of data to expose; information from the analog inputs and that from the digital inputs. The digital inputs include the
A,B,X,Y, LB, and RB buttons. It also includes the Start, Back, the four directions on the D-pad, and the buttonsunder the thumb sticks. The
analog inputs include the two triggers and the two thumb sticks. For the analog inputs there is a field for each input. The left and right
triggers both an integer value between 0 and 255 in a byte. The thumb sticks return their X and Y values as 16-bit integers between the range of
-32,768 and 32,767. The digital inputs are all returned in a single dield named wButtons
.
[StructLayout(LayoutKind.Explicit)]
public struct XInputGamepad
{
[MarshalAs(UnmanagedType.I2)]
[FieldOffset(0)]
public short wButtons;
[MarshalAs(UnmanagedType.I1)]
[FieldOffset(2)]
public byte bLeftTrigger;
[MarshalAs(UnmanagedType.I1)]
[FieldOffset(3)]
public byte bRightTrigger;
[MarshalAs(UnmanagedType.I2)]
[FieldOffset(4)]
public short sThumbLX;
[MarshalAs(UnmanagedType.I2)]
[FieldOffset(6)]
public short sThumbLY;
[MarshalAs(UnmanagedType.I2)]
[FieldOffset(8)]
public short sThumbRX;
[MarshalAs(UnmanagedType.I2)]
[FieldOffset(10)]
public short sThumbRY;
public bool IsButtonPressed(int buttonFlags)
{
return (wButtons & buttonFlags) == buttonFlags;
}
public bool IsButtonPresent(int buttonFlags)
{
return (wButtons & buttonFlags) == buttonFlags;
}
public void Copy(XInputGamepad source)
{
sThumbLX = source.sThumbLX;
sThumbLY = source.sThumbLY;
sThumbRX = source.sThumbRX;
sThumbRY = source.sThumbRY;
bLeftTrigger = source.bLeftTrigger;
bRightTrigger = source.bRightTrigger;
wButtons = source.wButtons;
}
public override bool Equals(object obj)
{
if (!(obj is XInputGamepad))
return false;
XInputGamepad source = (XInputGamepad)obj;
return ((sThumbLX == source.sThumbLX)
&& (sThumbLY == source.sThumbLY)
&& (sThumbRX == source.sThumbRX)
&& (sThumbRY == source.sThumbRY)
&& (bLeftTrigger == source.bLeftTrigger)
&& (bRightTrigger == source.bRightTrigger)
&& (wButtons == source.wButtons));
}
}
The method IsButtonPressed
accepts a constant that identifies the button of interest and will look in the wButtons
member to see if the button is pressed and return true
or false
accordingly. I've added a few methods to make the the structure easier to use. The methods IsButtonPressed
and IsButtonPresent
have
the exact same implementation.
The same structure is used for querying the state of the controller and the capabilities of the controller. I've added the two methods as a notational difference.
XInputVibration
The XInputVibration
structure contains two unsigned integers between 0 and 65,535. The higher the value placed in the structure the faster the associated motor will be
when it is turned on.
[StructLayout(LayoutKind.Sequential)]
public struct XInputVibration
{
[MarshalAs(UnmanagedType.I2)]
public ushort LeftMotorSpeed;
[MarshalAs(UnmanagedType.I2)]
public ushort RightMotorSpeed;
}
XInputState
The XInputState
structure is an extension of the XInputGamepad
structure with an additional field, PacketNumber
. When the state of the
controller is queried several times this value will continue to be the same if the the state of the controller is the same from the last time it was queried. If the state of
the controller has changed then this value will be different.
[StructLayout(LayoutKind.Explicit)]
public struct XInputState
{
[FieldOffset(0)]
public int PacketNumber;
[FieldOffset(4)]
public XInputGamepad Gamepad;
public void Copy(XInputState source)
{
PacketNumber = source.PacketNumber;
Gamepad.Copy(source.Gamepad);
}
public override bool Equals(object obj)
{
if ((obj == null) || (!(obj is XInputState)))
return false;
XInputState source = (XInputState)obj;
return ((PacketNumber == source.PacketNumber)
&& (Gamepad.Equals(source.Gamepad)));
}
}
XInputCapabilities
The XInputCapabilities
structure returns the capabilities of the controller. Not all Xbox controlls have all of the available buttons on them, such as the dance pads which generally only contains. Ironically the
the Type
field will always be populated with the same value (this may change in the future) and can be ignored for now. The field of interest is the
SubType
field. You can tell whether the controller is a gamepad, arcade stick, steeringwheel, or other type of controller. You can see a list of the controller
types in the subtypes documentation page. I've added the enumeration
ControllerSubtypes
to the XInputConstants.cs
file which contains the possible values for this field. Other capabilities of the controller are
indicated in the Flags
member. This includes whether or not the controller is wireless, supports voice, has navigation buttons (start, back, dpad), supports
force feedback, and whether it has a plugin module like the chat pad. The possible values can be viewed in the enumeration CapabilityFlags
in
XInputConstants.cs
.
The The next field is a
XinputGamepad
. For each one of the possible inputs there will be a non-zero value if the
controller supports that input. After the
XInputGamepad
structure is a
XInputVibration
structure. Like the
XInputGamepad
if the left and
right motors are supported there will be a non-zero value in the fields of this structure.
[StructLayout(LayoutKind.Explicit)]
public struct XInputCapabilities
{
[MarshalAs(UnmanagedType.I1)]
[FieldOffset(0)]
byte Type;
[MarshalAs(UnmanagedType.I1)]
[FieldOffset(1)]
public byte SubType;
[MarshalAs(UnmanagedType.I2)]
[FieldOffset(2)]
public short Flags;
[FieldOffset(4)]
public XInputGamepad Gamepad;
[FieldOffset(16)]
public XInputVibration Vibration;
}
XInputBatteryInformation
For controllers that use batteries this structure will indicate what type of battery that the control is using and indicated the battery level. The supported
battery types are NiMH, alkaline, unknown, disconnected, and wired (for devices that don't actually use batteries). For the battery level only 4 values are
supported to indicated empty, low, medium, and full. The values for both of these fields are also defined in the enumerations BatteryType
and
BatteryLevel
in the XInputConstants.cs
file.
[StructLayout(LayoutKind.Explicit)]
public struct XInputBatteryInformation
{
[MarshalAs(UnmanagedType.I1)]
[FieldOffset(0)]
public byte BatteryType;
[MarshalAs(UnmanagedType.I1)]
[FieldOffset(1)]
public byte BatteryLevel;
}
XInput Functions
There are four functions that I use from the XInput library for interacting with the controller, XInputGetState
, XInputSetState
,
XInputGetCapabilities
and XInputGetBatteryInformation
. All of these functions take as their parameters the index of the controller
that is being manipulated and a reference to the structure into which information will be stored or retrieved. The two functions XInputGetBatteryInformation and
XInputGetCapabilities take an additional parameter.
For XInputGetBatteryInformation
takes a parameter to the device type to be probed.
If a user is using a wireless headset it could have it's own battery level separate from the level of the controller. The enumeration BatteryDeviceType
contains values for BATTERY_DEVTYPE_GAMEPAD
and BATTERY_DEVTYPE_HEADSET
for this purpose. XInputGetCapabilities
only
accepts a single value of INPUT_FLAG_GAMEPAD
for its additional value. This parameter could accept different values in the future as more capabilities
are exposed.
[DllImport("xinput1_4.dll")]
public static extern int XInputGetState
(
int dwUserIndex, ref XInputState pState );
[DllImport("xinput1_4.dll")]
public static extern int XInputSetState
(
int dwUserIndex, ref XInputVibration pVibration );
[DllImport("xinput1_4.dll")]
public static extern int XInputGetCapabilities
(
int dwUserIndex, int dwFlags, ref XInputCapabilities pCapabilities );
[DllImport("xinput1_4.dll")]
public static extern int XInputGetBatteryInformation
(
int dwUserIndex, byte devType, ref XInputBatteryInformation pBatteryInformation );
XboxController Class
Now that I've talked about all of the structures and functions available in XInput the one other class to present is one that I've made to manage
the use of these other classes.
I've wrapped the calls to the functionality in a class named XboxController class. The constructor for this class is private. There can only
be up to 4 Xbox controllers on a system. So I've restricted the access to the constructor to prevent more than 4 instances from being created. To
get an instance of a controller the static method XbocController.RetrieveController
is available.
It's necessary to poll the controller to continually get updates
about it's state. In an XNA or DirectX program this would be part of your game loop and so you don't really need to think about it. If you use the class in an environment without such a loop there are two options for getting updates to the controller. You could either call
UpdateState
manually or you could call the static method XboxController.StartPolling
. The static method will create a new
thread that will update the controller's state on some frequency. By default I've set this to 25 times per second. If you want to change it to some other value
assign the number of updates per second that you want to received to the static member UpdateFrequency
. When you nolonger wish to receive updates remember
to call the XboxController.StopPolling
method to end the thread. In the example code that I've attached to this article I call the StopPolling
method when the program is closed.
protected override void OnClosing(CancelEventArgs e)
{
XboxController.StopPolling();
base.OnClosing(e);
}
There's a single event exposed by the class named StateChanged
that
is called if something about the controller's state has changed from the last thethat
it was called.The e
vent arguments for this event contain the current and previous state of the controller.
For making the controller vibrate call the Vibrate
method. You could call this method by passing two double values (between 0.0d and 1.0d) to indicate
how fast the motor should be. Values higher than 1.0 are clamped down to 1.0. Optionally you can specify the amount of time that the motor should be on. If no
timespan is specific the motor will remain on until there's another call to Vibrate
specifying a speed of zero. The polling loop for the Xbox controller
class also checks to see if it is time to turn off any motors (if a timespan had been specified.
#region Motor Functions
public void Vibrate(double leftMotor, double rightMotor)
{
Vibrate(leftMotor, rightMotor, TimeSpan.MinValue);
}
public void Vibrate(double leftMotor, double rightMotor, TimeSpan length)
{
leftMotor = Math.Max(0d, Math.Min(1d, leftMotor));
rightMotor = Math.Max(0d, Math.Min(1d, rightMotor));
XInputVibration vibration = new XInputVibration() { LeftMotorSpeed = (ushort)(65535d * leftMotor), RightMotorSpeed = (ushort)(65535d * rightMotor) };
Vibrate(vibration, length);
}
public void Vibrate(XInputVibration strength)
{
_stopMotorTimerActive = false;
XInput.XInputSetState(_playerIndex, ref strength);
}
public void Vibrate(XInputVibration strength, TimeSpan length)
{
XInput.XInputSetState(_playerIndex, ref strength);
if (length != TimeSpan.MinValue)
{
_stopMotorTime = DateTime.Now.Add(length);
_stopMotorTimerActive = true;
}
}
#endregion
Most importantly the XBoxController
has a property input so that you can read its state. For the digital inputs reading one of the methods
indirectly results in a call to IsButtonPressed
.
#region Digital Button States
public bool IsDPadUpPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_UP); }
}
public bool IsDPadDownPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_DOWN); }
}
public bool IsDPadLeftPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_LEFT); }
}
public bool IsDPadRightPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_RIGHT); }
}
public bool IsAPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_A); }
}
public bool IsBPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_B); }
}
public bool IsXPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_X); }
}
public bool IsYPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_Y); }
}
public bool IsBackPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_BACK); }
}
public bool IsStartPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_START); }
}
public bool IsLeftShoulderPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_LEFT_SHOULDER); }
}
public bool IsRightShoulderPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_RIGHT_SHOULDER); }
}
public bool IsLeftStickPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_LEFT_THUMB); }
}
public bool IsRightStickPressed
{
get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_RIGHT_THUMB); }
}
#endregion
For the analog inputs a numeric value is returned for the left or right triggers, or a pair of numeric values (for X and Y directions) are returned for the thumbsticks.
#region Analogue Input States
public int LeftTrigger
{
get { return (int)gamepadStateCurrent.Gamepad.bLeftTrigger; }
}
public int RightTrigger
{
get { return (int)gamepadStateCurrent.Gamepad.bRightTrigger; }
}
public Point LeftThumbStick
{
get
{
Point p = new Point()
{
X = gamepadStateCurrent.Gamepad.sThumbLX,
Y = gamepadStateCurrent.Gamepad.sThumbLY
};
return p;
}
}
public Point RightThumbStick
{
get
{
Point p = new Point()
{
X = gamepadStateCurrent.Gamepad.sThumbRX,
Y = gamepadStateCurrent.Gamepad.sThumbRY
};
return p;
}
}
#endregion
History
- 11 November 2011 - Initial Publication