Tree branch
Another tree branch
OpenGL Tutorials
Spider web
Facebook Twitter Google Plus
Capturing DirectX DirectInput Keyboard and Mouse Input

Initialization of DirectX-based components is usually a pretty technical task. That means there won't be much theory in this tutorial. I will show you how to initialize and use a Direct Input interface in this tutorial. Both keyboard and mouse input are covered. The source code combines files: dierr.h, dierr.cpp, directinput.h and directinput.cpp:

dierr.h ; DirectInput error codes header file dierr.cpp ; DirectInput error codes implementation directinput.h ; DirectInput definitions directinput.cpp ; DirectInput implementation

I always think it's more convenient to look at the header file of a particular code block first and only then read the contents of the actual code (the c, cpp files). So here's directinput.h, it contains all of the main Direct Input function definitions and some structures:

#define MOUSEL 0 #define MOUSER 1 #define MOUSEM 2 // mouse_t; defines the mouse data struct typedef struct tagMouse { int x, y, pinstate, pin; DIMOUSESTATE state; } mouse_t; // keyboard_t; defines the keyboard data struct typedef struct tagKeyboard { byte state[256]; } keyboard_t; extern mouse_t mse; extern keyboard_t key; extern LPDIRECTINPUT lpdi; // DI interface extern LPDIRECTINPUTDEVICE lpKeyboard; // keyboard device extern LPDIRECTINPUTDEVICE lpMouse; // mouse device void I_Init (HWND hwnd, HINSTANCE instance); // initializes DI interface void I_Shutdown (void); // shuts down DI interface void I_DeviceInit (void); // resets keyboard and mouse structs to 0 void I_Get (void); // gets mouse and keyboard input void I_GetMouse (void); // gets mouse input only void I_GetKeyboard (void); // gets keyboard input only void I_ReaquireKeyboard (void); // reaquire the keyboard device void I_ReaquireMouse (void); // reaquire the mouse device

The #defines MOUSEL, MOUSER and MOUSEM define the constant values for the left, right and middle mouse button controls. The mouse_t structure defines all variables needed to utilize the mouse controls. The keyboard_t struct defines the 256-key array that will be used to identify a key stroke.

The functions defined by I_name are commented and should be self-explanatory. We will look at the contents of each in a while. For now, lets look at lpdi, lpKeyboard and lpMouse. lpdi is the main Direct Input interface, it should be first initialized (which is done within I_Init) and destroyed at the end of your program (this is accomplished with a call to I_Shutdown). lpKeyboard is a Direct Input device.

A DirectInput (DI) device is simply an interface for defining an input system. There could be keyboard, mouse and joystick DI input devices. In this tutorial we're only concerned with keyboard and mouse input. The DI input device that describes the mouse controls is lpMouse. Each input device must be created. Also, you have to set cooperative level for each input device, set its data format, and finally acquire it.

These four steps for each of the two input devices (mouse, keyboard) are accomplished within I_Init. If, during the execution of your code, an input device is interrupted (this could happen when switching between windowed and full-screen modes or when your application conflicts with another one which also uses Direct Input), it needs to be reaquired. And that's exactly what I_ReaquireKeyboard(void) and I_ReaquireMouse(void) functions are for.

After an input device is created and acquired, you can start getting input from it. For the described in this tutorial mouse and keyboard input interfaces, this is accomplished by calling I_GetMouse(void) and I_GetKeyboard(void).

To get both simultaneously, call the I_Get(void) command. I_DeviceInit(void) is called to reset all parameters of the used mouse and keyboard structs (mouse_t mse; and keyboard_t key;) to 0, to make sure that all mouse buttons and all keys on the keyboard are set to unpressed state.

Before we step right into the Direct Input code, I want to mention how Direct Input is handled in case of an error during initialization or during a call to a Direct Input command. The files dierr.h and dierr.cpp take care of this functionality. You see, when a call to a Direct Input command is made, in case of an error a specific function writes the error code out into the system log file describing the error encountered as that command was executing.

The reason for this is that the code would start to look extremely unreadable if we included all error-code variations within the I_Init(void) function itself. dierr.h and dierr.cpp makes it all look a lot cleaner. dierr.cpp is rather lengthy, as it contains functions to output all possible Direct Input error codes for almost every Direct Input function we will be using. However, we'll take a look at dierr.h:

extern char dierr_msg[128]; extern dword err; // direct input errors // Use with these functions: extern char *dierrCreate (dword err); // DirectInputCreate extern char *dierrCreateDevice (dword err); // CreateDevice extern char *dierrCooperate (dword err); // SetCooperativeLevel extern char *dierrSetDataFormat (dword err); // SetDataFormat extern char *dierrAcquire (dword err); // Acquire extern char *dierrGetDeviceState (dword err); // GetDeviceState

The Direct Input interface provides the programmer with some generic functions. These functions manage the DI interface. For example the DirectInputCreate function creates the DI interface.

The function CreateDevice is called to create a DI device, and so on. Each of these functions usually returns DI_OK. But when they don't, that means there is an error. In that case, dierr.h and dierr.cpp come to help. When a DI function fails, execution falls back to one of the functions specified in these modules.

These functions define the "error-code" versions of each generic DI command. So for example, if the function DirectInputCreate fails with a return value that doesn't match DI_OK, we will use the dierrCreate function to find out what the error code actually was. The return value of dierrCreate is a C, NULL-terminated string. This string is then passed to the LogErr(...); function which logs it to the system log file(the LogErr(...) function is explained in tutorial c & misc. - 1).

The program then exits and you are able to look up what the problem was in the system log file and then try to fix the situation. But that's all in case of an error, which shouldn't be the case(perhaps, unless DirectX isn't installed on the machine you're running this code on).

Every separate DirectX component must be first initialized. Here, we're dealing with Direct Input, so let's see what we need to do in order to initialize Direct Input. Direct Input initialization consists of creating the DI interface by calling the generic function DirectInputCreate.

The parameters of this function are the windows instance handle (hinstance), the version of DI you are initializing (DIRECTINPUT_VERSION; could be anywhere from 0x0300 to 0x0700, in this case 0x0700) and the address of the DI object you are initializing (and that is the object already described above, defined as LPDIRECTINPUT lpdi; (remember this is the main DI interface object throughout the code).

After the DirectInput interface object is successfully created, we have to use it to create the keyboard and mouse input devices. As already mentioned for each input device to be operational, it must be first created, then you have to set its cooperative level, then you have to set its data format and finally acquire it. And this is exactly what we're doing in the remaining part of I_Init. Take a look:

void I_Init (HWND hwnd, HINSTANCE instance) { Log("; I_Init(); initialize DirectInput\n"); I_DeviceInit(); // create the direct input object if ((err = DirectInputCreate(instance, DIRECTINPUT_VERSION, &lpdi, NULL)) != DI_OK) LogErr(dierrCreate(err)); // create keyboard if ((err = lpdi->CreateDevice(GUID_SysKeyboard, &lpKeyboard, NULL)) != DI_OK) LogErr(dierrCreateDevice(err)); if ((err = lpKeyboard->SetCooperativeLevel(hwnd, DISCL_BACKGROUND|DISCL_NONEXCLUSIVE)) != DI_OK) LogErr(dierrCooperate(err)); if ((err = lpKeyboard->SetDataFormat(&c_dfDIKeyboard)) != DI_OK) LogErr(dierrSetDataFormat(err)); if ((err = lpKeyboard->Acquire()) != DI_OK) LogErr(dierrAcquire(err)); // create mouse if ((err = lpdi->CreateDevice(GUID_SysMouse, &lpMouse, NULL)) != DI_OK) LogErr(dierrCreateDevice(err)); if ((err = lpMouse->SetCooperativeLevel(hwnd, DISCL_BACKGROUND|DISCL_NONEXCLUSIVE)) != DI_OK) LogErr(dierrCooperate(err)); if ((err = lpMouse->SetDataFormat(&c_dfDIMouse)) != DI_OK) LogErr(dierrSetDataFormat(err)); if ((err = lpMouse->Acquire()) != DI_OK) LogErr(dierrAcquire(err)); }

Creating the Input Device. Two interface devices are created after the main DI object (lpdi) is initialized. Note, to create an input device, the CreateDevice function is used which is a member of the initialized DI object.

To create a keyboard device, pass GUID_SysKeyboard to CreateDevice(...) as the first parameter. To create a mouse device, GUID_SysMouse is used as the first parameter. These GUIDs are already defined by DI.

The second parameter is the address of the device that will be used to obtain input. As previously noted, the keyboard device is defined as lpKeyboard. The mouse device is defined as lpMouse. The third parameter of CreateDevice is not used, pass NULL to it.

Setting Cooperative Level. Setting cooperative level of a DI input device is accomplished with the SetCooperativeLevel member function of the device object. The window handle(HWND hwnd) is passed as the first parameter, but the key points here are the flags used to set cooperative levels of both input devices (keyboard and mouse).

DISCL_BACKGROUND and DISCL_NONEXCLUSIVE are OR-ed together. As DI documentation states, DISCL_BACKGROUND is used when the application requires "background access". If background access is granted, the device may be acquired at any time, even when the associated window is not the active window. What this basically means is that input can be aquired by your program even if its not currently active. Your program could be running in background but input would still be taken from the device you're setting this flag to.

DISCL_NONEXCLUSIVE means that the application requires non-exclusive access. Access to the device will not interfere with other applications that are accessing the same device. You see, we would have used DISCL_FOREGROUND (as opposed to DISCL_BACKGROUND) but if we do, each time our OpenGL window becomes inactive, the input devices are automatically unacquired (and lost). Just for the purpose of these tutorials we're making things simple.

However, if you're using this code for your own purposes, such as a game you're developing, you could specify the DISCL_FOREGROUND flag here. But then you will have to take care of reacquiring the input device interfaces, of course. Note also, from DI documentation: Applications must specify either DISCL_FOREGROUND or DISCL_BACKGROUND; it is an error to specify both or neither. Similarly, applications must specify either DISCL_EXCLUSIVE or DISCL_NONEXCLUSIVE.

The final two steps in creation of both input devices are setting data format and acquiring the device. These steps should be easy to understand. Look up more information about these processes in the MSDN documentation if you need to. Once the device is acquired, it's ready to be used by your program. How to get keyboard and mouse input from the two acquired devices is explained below.

Getting Keyboard and Mouse Input

So you've initialized Direct Input and acquired both the mouse and keyboard input devices. How exactly do we get input from these devices? First, I will explain keyboard input. The functions that manage input from the keyboard are I_GetKeyboard and I_ReacquireKeyboard. Let's take a look at the code:

void I_ReacquireKeyboard (void) { if ((err = lpKeyboard->Acquire()) != DI_OK) Log("di_ok\n"); else LogErr(dierrAcquire(err)); } void I_GetKeyboard (void) { if ((err = lpKeyboard->GetDeviceState( ((sizeof(unsigned char)) << 8), (void*)key.state)) != DI_OK) if ((err & DIERR_INPUTLOST) && (err & DIERR_NOTACQUIRED)) { // try to (re-)acquire the keyboard interface I_ReacquireKeyboard(); } else // ...some other error LogErr(dierrGetDeviceState(err)); }

To get input from keyboard, in your main loop, you would simply call I_GetKeyboard. What it does is it calls the GetDeviceState member function of the keyboard device and if the device happened to be unacquired it tries to reacquire it.

Note that I pass the keyboard array(containing 256 entries) key.state to GetDeviceState as the last parameter. If Direct Input identifies a key stroke, it places a value other than 0 into one of the keyboard array entries(this array is defined by the keyboard_t key struct).

For each of the 256 keyboard array entries DI defines a keyboard device constant. For example to specify the Escape key, DI defines it as DIK_ESCAPE. To specify the enter key, DIK_RETURN is defined. So then, to see if the Escape key has been pressed, you would code something like this:

if (key.state[DIK_ESCAPE]) { // Escape is down, do something... } There is a keyboard device constant for each key on the keyboard, defined in Dinput.h: Constant Note DIK_ESCAPE DIK_1 On main keyboard DIK_2 On main keyboard DIK_3 On main keyboard DIK_4 On main keyboard DIK_5 On main keyboard DIK_6 On main keyboard DIK_7 On main keyboard DIK_8 On main keyboard DIK_9 On main keyboard DIK_0 On main keyboard DIK_MINUS On main keyboard DIK_EQUALS On main keyboard DIK_BACK The backspace key DIK_TAB DIK_Q DIK_W DIK_E DIK_R DIK_T DIK_Y DIK_U DIK_I DIK_O DIK_P DIK_LBRACKET The [ key DIK_RBRACKET The ] key DIK_RETURN enter key on main keyboard DIK_LCONTROL Left ctrl key DIK_A DIK_S DIK_D DIK_F DIK_G DIK_H DIK_J DIK_K DIK_L DIK_SEMICOLON DIK_APOSTROPHE DIK_GRAVE Grave accent (`) key DIK_LSHIFT Left shift key DIK_BACKSLASH DIK_Z DIK_X DIK_C DIK_V DIK_B DIK_N DIK_M DIK_COMMA DIK_PERIOD On main keyboard DIK_SLASH Forward slash on main keyboard DIK_RSHIFT Right shift key DIK_MULTIPLY The * key on numeric keypad DIK_LMENU Left alt key DIK_SPACE spacebar DIK_CAPITAL caps lock key DIK_F1 DIK_F2 DIK_F3 DIK_F4 DIK_F5 DIK_F6 DIK_F7 DIK_F8 DIK_F9 DIK_F10 DIK_NUMLOCK DIK_SCROLL scroll lock DIK_NUMPAD7 DIK_NUMPAD8 DIK_NUMPAD9 DIK_SUBTRACT minus sign on numeric keypad DIK_NUMPAD4 DIK_NUMPAD5 DIK_NUMPAD6 DIK_ADD plus sign on numeric keypad DIK_NUMPAD1 DIK_NUMPAD2 DIK_NUMPAD3 DIK_NUMPAD0 DIK_DECIMAL period (decimal point) on numeric keypad DIK_F11 DIK_F12 DIK_F13 DIK_F14 DIK_F15 DIK_KANA On Japanese keyboard DIK_CONVERT On Japanese keyboard DIK_NOCONVERT On Japanese keyboard DIK_YEN On Japanese keyboard DIK_NUMPADEQUALS On numeric keypad (NEC PC98) DIK_CIRCUMFLEX On Japanese keyboard DIK_AT On Japanese keyboard DIK_COLON On Japanese keyboard DIK_UNDERLINE On Japanese keyboard DIK_KANJI On Japanese keyboard DIK_STOP On Japanese keyboard DIK_AX On Japanese keyboard DIK_UNLABELED On Japanese keyboard DIK_NUMPADENTER DIK_RCONTROL Right ctrl key DIK_NUMPADCOMMA comma on NEC PC98 numeric keypad DIK_DIVIDE Forward slash on numeric keypad DIK_SYSRQ DIK_RMENU Right alt key DIK_HOME DIK_UP up arrow DIK_PRIOR page up DIK_LEFT left arrow DIK_RIGHT right arrow DIK_END DIK_DOWN down arrow DIK_NEXT page down DIK_INSERT DIK_DELETE DIK_LWIN Left Windows key DIK_RWIN Right Windows key DIK_APPS Application key DIK_PAUSE

The following alternate names are available:

Alternate name Regular name Note DIK_BACKSPACE DIK_BACK backspace DIK_NUMPADSTAR DIK_MULTIPLY * key on numeric keypad DIK_LALT DIK_LMENU Left alt DIK_CAPSLOCK DIK_CAPITAL capslock DIK_NUMPADMINUS DIK__SUBTRACT Minus key on numeric keypad DIK_NUMPADPLUS DIK_ADD Plus key on numeric keypad DIK_NUMPADPERIOD DIK_DECIMAL Period key on numeric keypad DIK_NUMPADSLASH DIK__DIVIDE Forward slash on numeric keypad DIK_RALT DIK_RMENU Right alt DIK_UPARROW DIK_UP On arrow keypad DIK_PGUP DIK_PRIOR On arrow keypad DIK_LEFTARROW DIK_LEFT On arrow keypad DIK_RIGHTARROW DIK_RIGHT On arrow keypad DIK_DOWNARROW DIK_DOWN On arrow keypad DIK_PGDN DIK_NEXT On arrow keypad

Similarly, the mouse input relies on two functions I_GetMouse and I_ReacquireMouse. The I_GetMouse function is a bit more involved than I_GetKeyboard, it checks for a single-click (a pin click) and also constrains the mouse x and y coordinates by a bounding box of the screen (the code for this is commented and it's optional):

void I_ReacquireMouse (void) { Log("reacquiring mouse..."); if ((err = lpMouse->Acquire()) != DI_OK) Log("di_ok\n"); else LogErr(dierrAcquire(err)); } void I_GetMouse (void) { if ((err = lpMouse->GetDeviceState(sizeof(DIMOUSESTATE), (void*)&mse.state) != DI_OK)) if ((err & DIERR_INPUTLOST) && (err & DIERR_NOTACQUIRED)) { // try to (re-)acquire the mouse interface I_ReacquireMouse(); } else // ...some other error LogErr(dierrGetDeviceState(err)); mse.x += mse.state.lX; mse.y += mse.state.lY; // to constrain mouse cursor position to the screen bounding box // uncomment the following lines and define screenWidth and screenHeight /* if (mse.x <= 0) mse.x = 0; if (mse.x > screenWidth) mse.x = screenwidth; if (mse.y <= 0) mse.y = 0; if (mse.y > screenHeight) mse.y = screenHeight; */ mse.pin = 0; // has a single-click occured? if (mse.state.rgbButtons[0] && !mse.pinstate) { mse.pinstate = 1; mse.pin = 1; } // reset 'pin' state if (!mse.state.rgbButtons[0]) mse.pinstate = 0; }

The mse.pin variable works to identify a single left-button mouse click. The way it works is first you would call I_GetMouse. I_GetMouse checks if the left mouse button has been pressed.

If that's the case, mse.pin is set to 1 and you can test this value in the remaining block of your code to identify a single "pin" click. mse.pin is set to 1 only for the next code execution "run". It is automatically reset to 0 again, even if you keep holding the left mouse button down.

This functionality is very useful when developing a GUI system. Sometimes, you don't want the left mouse button to be constantly active.

To conclude this tutorial, here is how you would get input by using the Direct Input library presented here:

// somewhere during initialization; Initialize DI... I_Init(); // during shutdown sequence; Shutdown DI interface... I_Shutdown(); // during execution, get input and do something useful with it... I_Get(); // draw the mouse pointer at the current mouse x,y location DrawCursor(mse.x, mse.y); if (mse.pin) { // a single click occured at [mse.x, mse.y] } if (key.state[DIK_LEFT]) { // left arrow key is down; do something... } The source code contains files dierr.h, dierr.cpp, directinput.h, directinput.cpp.

© 2014 OpenGL Tutorials.

About | Privacy | Contact

Built with Tornado PHP FrameworkTornado PHP Framework with Wooden PHP Template template | Web design by Web Design by Greg.