Be Friendly
Search This Site for Tutorials About...

Creating a DirectInput Application (Adding Keyboard & Mouse Controls)

Just for fun: How to Make French Toast

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 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 DI 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.

Did this article help you learn something new?

I enjoy writing for the Internet audiences because my thoughts are instantly published to the entire world. My work consists of writing educational articles about making websites to help people learn. If you enjoyed reading this article, or learned something new, then I have succeeded.

If the content on this website somehow helped you to learn something new, please let others know about it by sharing it on your website or on your Facebook page with your friends. In addition you can help by making a donation or bookmarking this site.