Part D - DirectInput

Keyboard and Mouse Input

Introduce direct access to the standard keyboard and mouse devices
Describe hardware interrupts and DirectInput

Sample | Programming | Settings | Window | Keyboard | Mouse | Context
Design | Camera | Exercises


A game can capture user input through a variety of devices and techniques.  The most common devices are keyboards, mice, and controllers.  On these devices, input takes the form of scan codes that pass to the CPU.  The samples in the preceding chapters use the Windows messaging loop to transfer keyboard input to the application.  This technique involves converting the keyboard scan codes into virtual key code messages, translating those messages into character messages, dispatching those messages, receiving them in the application's window procedure, and finally saving the processed input in the application.  Capturing input through the event message loop is relatively inefficient.  As an alternative, we can transfer the input directly from the device to the application. 

In this chapter, we introduce direct access to keyboard and mouse devices through the DirectInput API of DirectX. This API provides the methods for polling the devices directly and transferring the scan codes that they generate to an application.


Keyboard-Mouse Sample

The Keyboard-Mouse Sample changes the keyboard access method and introduces

  • mouse control of the camera
  • mouse control of the rotation of the spinning boxes

The user can now adjust the camera's position and orientation by moving the mouse.  The user can also induce jumps in the rotation of the left and right boxes by pressing the left and right mouse buttons. 


Programming Issues

Incorporating DirectInput into the framework adds a new component to its Direct API branch.  This component connects to the input devices directly and transfers to the application the input data accumulated between successive frames. 

The issues related to introducing DirectInput include:

  • understanding the way in which input devices communicate with the CPU
  • understanding the difference between DirectInput and Windows messaging
  • addressing the COM aspects of DirectInput
  • identifying the steps involved in setting up a DirectInput device
  • understanding how input is retrieved from a DirectInput device
  • identifying the library files required to use DirectInput

Communication with the CPU

Input hardware communicates directly with the CPU.  Keyboards and mice communicate with the CPU by generating hardware interrupts.  The CPU, after executing a program instruction, checks its interrupt line to see if any interrupt is pending.  If an interrupt is pending, the CPU extracts the 8-bit interrupt number on the data bus and uses that number to locate the interrupt function in the Interrupt Descriptor Table.  This table holds the addresses of the functions that manage interrupts.  In the case of a keyboard interrupt, the interrupt function reads the scan code on the keyboard port.  The interrupt function converts the scan code into its equivalent ASCII code, if possible, and stores both the scan code and the ASCII value in the keyboard buffer on system memory.  The CPU then returns to the next program instruction in the instruction queue and executes that instruction. 

interrupt

In order to interpret the data retrieved from the keyboard or mouse buffer, we need to know the format in which that data is stored in the buffer.

DirectInput and Windows Messaging

DirectInput provides direct access to the input buffers that hold the data collected by the input devices, which enables significantly faster access to this data than the traditional Windows messaging system, particularily in a full-scale applications.  Moreover, DirectInput can support input devices that the Windows API does not recognize. 

DirectInput is useful with real-time applications such as games and simulations, but provides no improvement over Windows messaging for text entry applications. 

DirectInput vs XInput and Raw Input

DirectInput generally extends and improves input performance.  By uncoupling input from the Windows messaging loop, it provides lower latency and buffered input for presses shorter than a single frame.  It lets an application retrieve data from input devices even if that application is running in the background.  It provides full support for any type of input device including one that has force feedback.  DirectInput can retrieve input data without knowing what kind of device is generating that data. 

Microsoft stopped upgrading DirectInput with DirectX 8.  Microsoft then started promoting XInput in its place for XBox360 controller devices and moved the keyboard and mouse functionality of DirectInput into the main Windows API in the form of RawInput.  Microsoft now recommends using Raw Input with the Windows message loop for both keyboard and mouse data.  The Windows message loop can now trap RawInput data for improved device-independent functionality where programmed.  RawInput supports both immediate and buffered input, but requires registration of devices to make that raw input available to an application. 

We implement DirectInput only in this sample but not Windows messaging with RawInput.

COM Considerations

DirectInput works with COM technology and implements two types of COM objects for keyboard and mouse input:

  • DirectInput COM object - provides methods for enumerating the input devices installed on the host system
  • DirectInput device COM object- represents an input device

Input Retrieval

A DirectInput device retrieves data from an input buffer in the form of a data packet.  The retrieval process may be immediate or buffered. An immediate process retrieves only the current state of the device.  A buffered process retrieves all of the data stored in the input buffer since the last retrieval. 

We use buffered input for both keyboard and mouse data.

Setting Up and Closing an Input Device

Setting up a DirectInput device for keyboard and mouse input involves seven distinct steps in the following order:

  1. Retrieve an interface to the DirectInput COM object
  2. Enumerate the installed devices and select a specific device - optional
  3. Retrieve an interface to the selected input device
  4. Set the cooperative level for the device - describe how to share it with other applications
  5. Define the format for the device - describe the data packet associated with the device
  6. Set the properties of the device - for buffered data, set the size of the input buffer
  7. Acquire the device - notify the operating system that the application is ready to retrieve data

Closing access to a DirectInput device properly involves un-acquiring the device, releasing the interface to the device, and finally releasing the interface to the DirectInput COM object itself. 

Header Files

The class definitions and the prototypes for the DirectInput functions are listed in the dinput.h header file. 

#include <dinput.h>

To identify the input devices, we expose the system GUIDs by bracketing this include with the INITGUID macro. 

#define INITGUID
#include <dinput.h>
#undef INITIGUID

The most recent version of the DirectInput library is dinput8.lib.


Settings

Framework-Wide Settings

The framework's configuration file defines the macro for

  • the maximum number of mouse buttons available

and the enumeration constants for

  • the mouse buttons
  • the mouse button states
  • the mouse motion values
 /* The Pre-Defined Configuration
  *
  * Configuration.h
  */

 // ...

 // Sizes of context arrays
 //
 // ...
 #define MAX_KEYS        256
 #define MAX_M_BUTTONS     3

 typedef enum Integer {
     // ...
     GF_MS_POSX,
     GF_MS_POSY,
     GF_MS_ORIZ,
     // ...
 } Integer;

 // ...

 typedef enum BoolArray {
     GF_KB_KEYS, // key states
     GF_MS_BTNS  // mouse button states
 } BoolArray;

 // ...

 // Mouse Buttons
 //
 typedef enum MouseButton {
     LEFT_BUTTON,
     RIGHT_BUTTON
 } MouseButton;

 // ...

Model Settings

The Model settings define the mouse motion conversion factors

 /* Header for Configuring the Model Branch
  *
  * ModelSettings.h
  */

 // ...

 // mouse motion conversion factors
 #define MOUSE_SPEED             10
 #define MOUSE_BUTTON_SCALE      10

 // ...

Window API

The Window API-specific settings define the window and dialog box captions:

 /* Configuration Settings for the Window API Branch
  *
  * WindowSettings.h
  */

 // ...

 // Window captions
 //
 #if Z_AXIS == NEAR_TO_FAR
 #define WND_NAME L"Keyboard Mouse (Z Axis Near to Far)" 
 #elif Z_AXIS == FAR_TO_NEAR
 #define WND_NAME L"Keyboard Mouse (Z Axis Far to Near)"
 #endif
 #define DLG_NAME L"Configure fwk4gps - Keyboard Mouse"

 // ...


Window

The Window class of the Window API branch no longer traps any input from the keyboard apart from the Escape and F1 key presses.  This upgrade affects only the implementation of this class. 

Implementation

wndProc

The wndProc() window procedure is considerably reduced in size:

 
 LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {

     static bool   quit   = false;
     iEngine*      engine = EngineAddress();
     iWindow*      window = WindowAddress();

     switch (msg) {
       // ...

       case WM_KEYDOWN:
         switch (wp) {
           case VK_ESCAPE:
             // close the main window and ...
             PostMessage(hwnd, WM_CLOSE, 0, 0);
             // prepare to quit
             quit = true;
             break;

           case VK_F1:
             // select the new context
             // - deactivate and post a user message
             PostMessage(hwnd, WM_ACTIVATEAPP, 0, 0);
             PostMessage(hwnd, WM_USER, 0, 0);
             break;
         }
         break;

       // ...
     }

     return DefWindowProc(hwnd, msg, wp, lp);
 }

Keyboard

The Keyboard class of the Direct API branch provides keyboard access through the DirectInput API. 

Interface

The iKeyboard interface exposes six virtual methods to the framework:

  • setup() - connects the Keyboard object to the keyboard device
  • retrieveInput() - retrieves the input data from the Keyboard object
  • suspend() - suspends the connection between the Keyboard object and the keyboard device
  • restore() - restores the connection between the Keyboard object and the keyboard device
  • release() - disconnects the Keyboard object from the keyboard device
  • Delete() - deletes the Keyboard object
 class iKeyboard {
   public:
     virtual bool  setup(void* hwnd)                   = 0;
     virtual bool  restore()                           = 0;
     virtual void  retrieveInput()                     = 0;
     virtual void  suspend()                           = 0;
     virtual void  release()                           = 0;
     virtual void  Delete() const                      = 0;
 };

 extern "C"
 iKeyboard* CreateKeyboard(void*, iContext*);
 

Class Definition

The Keyboard class is a singleton that derives from the Keyboard interface and wraps the DirectInput API calls for a buffered keyboard device.  This class includes two instance pointers:

  • di - points to an interface to the DirectInput object
  • keyboard - points to an interface to a DirectInput keyboard device
 class Keyboard : public iKeyboard {

     static const int SAMPLE_BUFFER_SIZE =  30;

     iContext*            context;      // points to the Context object
     LPDIRECTINPUT8       di;           // points to Direct Input object
     LPDIRECTINPUTDEVICE8 keyboard;     // points to Direct Input Keyboard

     Keyboard(void*, iContext*);
     Keyboard(const Keyboard& k);            // prevents copying
     Keyboard& operator=(const Keyboard& k); // prevents assignment
     virtual ~Keyboard();

   public:
     friend iKeyboard* CreateKeyboard(void*, iContext*);
     bool   setup(void*);
     bool   restore();
     void   retrieveInput();
     void   suspend();
     void   release();
     void   Delete() const { delete this; }
 };

Implementation

The CreateKeyboard() function creates the Keyboard object on dynamic memory:

 const GUID GUID_NULL = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };

 iKeyboard* CreateKeyboard(void* hinst, iContext* c) {
     return new Keyboard(hinst, c);
 }

The definition of the GUID_NULL constant is necessary since simple assignment to zero does not apply to instances of structs.

Construction

The constructor retrieves a pointer to a DirectInput object and initializes the address of the keyboard device:

 Keyboard::Keyboard(void* hinst, iContext* c) : context(c) {

     // acquire an Interface to DirectInput object for this application
     di = NULL;
     if (FAILED(DirectInput8Create((HINSTANCE)hinst, DIRECTINPUT_VERSION,
      IID_IDirectInput8, (void**)&di, NULL))) {
         error(L"Keyboard::00 Failed to obtain an Interface to Direct "
          L"Input");
     }

     keyboard   = NULL;
 }

Setup

The setup() method on the Keyboard object connects to the keyboard device.  Setup consists of five steps:

  1. the call to the CreateDevice() method on the interface to the DirectInput COM object retrieves the address of an interface to the COM object that represents the keyboard device
  2. the call to the SetDataFormat() method on the interface to the keyboard device defines the data format for the device
  3. the call to the SetCooperativeLevel() method on the interface to the keyboard device specifies how the device is shared with other applications
  4. the call to the SetProperties() method on the interface to the keyboard device specifies the size of the buffer for collecting the input data from the device
  5. a call to the Acquire() method on the interface to the keyboard device notifies the operating system that the application is ready to receive input data from the device
 bool Keyboard::setup(void* hwnd) {

     bool rc = false;

     // release the keyboard to start from scratch
     release();

     // obtain Interface to the keyboard
     if (FAILED(di->CreateDevice(GUID_SysKeyboard, &keyboard, NULL)))
         error(L"Keyboard::10 Failed to obtain an Interface to system "
          L"keyboard");
     // set the data format for the keyboard data
     else if (FAILED(keyboard->SetDataFormat(&c_dfDIKeyboard))) {
         release();
         error(L"Keyboard::11 Failed to set the data format for keyboard");
     }
     // set the cooperative level
     else if (FAILED(keyboard->SetCooperativeLevel((HWND)hwnd,
      DISCL_NONEXCLUSIVE | DISCL_FOREGROUND))) {
         release();
         error(L"Keyboard::12 Failed to set the cooperative level for "
          L"keyboard");
     }
     else {
         // set the size of the keyboard buffer
         //
         // property struct consists of a header and a data member
         DIPROPDWORD dipdw;
         // property struct header
         // - size of enclosing structure
         dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
         // - always size of DIPROPHEADER
         dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
         // - identifier for property in question - 0 for entire device
         dipdw.diph.dwObj        = 0;
         // - DIPH_DEVICE since entire device is involved
         dipdw.diph.dwHow        = DIPH_DEVICE;
         // property struct data member (takes a single word of data)
         // - the buffer size goes here
         dipdw.dwData            = SAMPLE_BUFFER_SIZE;

         // set the size of the buffer
         if (FAILED(keyboard->SetProperty(DIPROP_BUFFERSIZE,
          &dipdw.diph))) {
             release();
             error(L"Keyboard::13 Failed to set size of keyboard buffer");
         }
         // flush buffer: data currently in the buffer will be ignored
         else {
             // try to acquire the keyboard
             if (SUCCEEDED(keyboard->Acquire()))
                 retrieveInput();
             rc = true;
         }
     }

     return rc;
 }

The CreateDevice() method on the interface to the DirectInput object receives as its first parameter the GUID that identifies the device and returns through its second parameter the address of the interface to the COM object that represents the device.  The third argument is NULL for non-aggregated applications.  The pre-defined macro GUID_SysKeyboard holds the GUID for the system keyboard. 

The SetDataFormat() method on the interface to the keyboard device receives as its parameter the address of an instance of a DIDATAFORMAT struct.  This instance defines the format of the data arriving from the keyboard device.  The DirectInput header file includes a pre-defined global instance - c_dfDIKeyboard - that describes this data format. 

The SetCooperativeLevel() function on the interface to the keyboard device receives as its first parameter a handle to the application window and as its second parameter a set of flags that describes the device's behavior.  DISCL_FOREGROUND access specifies that the keyboard device is unacquired as soon as the application moves into the background.  DISCL_NONEXCLUSIVE specifies that access to the keyboard device does not interfere with other applications that are using the same device. 

The SetProperties() method on the interface to the keyboard device receives as its first parameter the name of the property to be set - DIPROP_BUFFERSIZE - and as its second parameter the address of the property header within the property struct.  Note that this address is not the address of the instance of the property struct, but of the instance of the property header enclosed within the property struct. 

The property struct is a DIPROPDWORD struct, which has two members: a DIPROPHEADER struct that holds the property header and a DWORD that holds the data that is specific to this property struct.  The DIPROPHEADER struct has four members: a DWORD that holds the size of the enclosing struct, a DWORD that holds the size of the header struct, a DWORD identifying the property (0 for the entire device), and a DWORD specifying how the property should be interpreted (DIPH_DEVICE for the entire device).

Retrieve Input

The retrieveInput() method on the Keyboard object retrieves all of the data in the input buffer of the keyboard device: 

void Keyboard::retrieveInput() {

    HRESULT hr;
    DWORD items = SAMPLE_BUFFER_SIZE;
    DIDEVICEOBJECTDATA dod[SAMPLE_BUFFER_SIZE];

    if (keyboard) {
        hr = keyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), dod,
         &items, 0);
        // if keyboard is lost, try to re-acquire it
        if (DIERR_INPUTLOST == hr && SUCCEEDED(keyboard->Acquire()))
            hr = keyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
             dod, &items, 0);
        if (SUCCEEDED(hr))
            for (DWORD i = 0; i < items; i++) {
                int k = -1;
                // note that not all keys have been included in this table -
                // only those keys that are mappable as described in the
                // ModelSettings header
                //
               switch (dod[i].dwOfs) {
                  case DIK_A: k = KEY_A; break;
                  case DIK_B: k = KEY_B; break;
                  case DIK_C: k = KEY_C; break;
                  case DIK_D: k = KEY_D; break;
                  case DIK_E: k = KEY_E; break;
                  case DIK_F: k = KEY_F; break;
                  case DIK_G: k = KEY_G; break;
                  case DIK_H: k = KEY_H; break;
                  case DIK_I: k = KEY_I; break;
                  case DIK_J: k = KEY_J; break;
                  case DIK_K: k = KEY_K; break;
                  case DIK_L: k = KEY_L; break;
                  case DIK_M: k = KEY_M; break;
                  case DIK_N: k = KEY_N; break;
                  case DIK_O: k = KEY_O; break;
                  case DIK_P: k = KEY_P; break;
                  case DIK_Q: k = KEY_Q; break;
                  case DIK_R: k = KEY_R; break;
                  case DIK_S: k = KEY_S; break;
                  case DIK_T: k = KEY_T; break;
                  case DIK_U: k = KEY_U; break;
                  case DIK_V: k = KEY_V; break;
                  case DIK_W: k = KEY_W; break;
                  case DIK_X: k = KEY_X; break;
                  case DIK_Y: k = KEY_Y; break;
                  case DIK_Z: k = KEY_Z; break;
                  case DIK_1: k = KEY_1; break;
                  case DIK_2: k = KEY_2; break;
                  case DIK_3: k = KEY_3; break;
                  case DIK_4: k = KEY_4; break;
                  case DIK_5: k = KEY_5; break;
                  case DIK_6: k = KEY_6; break;
                  case DIK_7: k = KEY_7; break;
                  case DIK_8: k = KEY_8; break;
                  case DIK_9: k = KEY_9; break;
                  case DIK_0: k = KEY_0; break;
                  case DIK_F1:  k = KEY_F1;  break;
                  case DIK_F2:  k = KEY_F2;  break;
                  case DIK_F3:  k = KEY_F3;  break;
                  case DIK_F4:  k = KEY_F4;  break;
                  case DIK_F5:  k = KEY_F5;  break;
                  case DIK_F6:  k = KEY_F6;  break;
                  case DIK_F7:  k = KEY_F7;  break;
                  case DIK_F8:  k = KEY_F8;  break;
                  case DIK_F9:  k = KEY_F9;  break;
                  case DIK_F10: k = KEY_F10; break;
                  case DIK_F11: k = KEY_F11; break;
                  case DIK_F12: k = KEY_F12; break;
                  case DIK_SPACE : k = KEY_SPACE; break;
                  case DIK_RETURN: k = KEY_ENTER; break;
                  case DIK_UP    : k = KEY_UP;     break;
                  case DIK_DOWN  : k = KEY_DOWN;   break;
                  case DIK_PRIOR : k = KEY_PGUP;  break;
                  case DIK_NEXT  : k = KEY_PGDN;   break;
                  case DIK_LEFT  : k = KEY_LEFT;   break;
                  case DIK_RIGHT : k = KEY_RIGHT;  break;
                  case DIK_NUMPAD1:  k = KEY_NUM1; break;
                  case DIK_NUMPAD2:  k = KEY_NUM2; break;
                  case DIK_NUMPAD3:  k = KEY_NUM3; break;
                  case DIK_NUMPAD4:  k = KEY_NUM4; break;
                  case DIK_NUMPAD5:  k = KEY_NUM5; break;
                  case DIK_NUMPAD6:  k = KEY_NUM6; break;
                  case DIK_NUMPAD7:  k = KEY_NUM7; break;
                  case DIK_NUMPAD8:  k = KEY_NUM8; break;
                  case DIK_NUMPAD9:  k = KEY_NUM9; break;
                  case DIK_ESCAPE    : k = KEY_ESCAPE; break;
                  case DIK_SEMICOLON : k = KEY_SEMICOLON; break;
                  case DIK_APOSTROPHE: k = KEY_APOSTROPHE; break;
                  case DIK_LBRACKET  : k = KEY_O_BRACKET; break;
                  case DIK_RBRACKET  : k = KEY_C_BRACKET; break;
                  case DIK_BACKSLASH : k = KEY_BACKSLASH; break;
                  case DIK_COMMA     : k = KEY_COMMA; break;
                  case DIK_PERIOD    : k = KEY_PERIOD; break;
                  case DIK_SLASH     : k = KEY_SLASH; break;
                  case DIK_MULTIPLY  : k = KEY_TIMES; break;
                  case DIK_GRAVE     : k = KEY_GRAVE; break;
                  case DIK_MINUS     : k = KEY_MINUS; break;
                  case DIK_UNDERLINE : k = KEY_UNDERSCORE; break;
                  case DIK_EQUALS    : k = KEY_EQUALS; break;
                  case DIK_ADD       : k = KEY_PLUS; break;
                }
                if (k != -1)
                    context->set(GF_KB_KEYS, k, !!(dod[i].dwData & 0x80));
            }
    }
}

The items variable initially holds the maximum number of key events that can be retrieved from the buffer in a single call.  The GetDeviceData() method on the interface to the keyboard device subsequently changes this variable to hold the number of key events that have actually been retrieved from the buffer. 

The DIDEVICEOBJECTDATA struct holds the data retrieved from the input buffer.  Each element of the array holds the data for a single event, which may be a key press or a key release.  The struct contains four data members, two of which are relevant here.  The dwOfs member identifies the offset into the data structure for the specified key.  The offset for each key is uniquely defined.  The highest bit of the low byte of the dwData member identifies the type of event: 1 for a key press, 0 for a key release.  We extract this highest bit by masking the data member with 0x80. 

The GetDeviceData() method on the interface to the keyboard device populates the DIDEVICEOBJECTDATA array with the data for the first items events stored in the keyboard buffer.  Since the connection to the keyboard might be lost at any time, the GetDeviceData() method may fail, in which case, we attempt to re-aquire the keyboard and retrieve the data in the input buffer. 

The keyboard device constants (DIK_*) are defined in the Keyboard_Device enumeration in the DirectInput header file.

Suspend

The suspend() method on the Keyboard object notifies the operating system that the application not longer accesses the keyboard device: 

 void Keyboard::suspend() {
     if (keyboard) keyboard->Unacquire();
 }

Restore

The restore() method on the Keyboard object notifies the operating system that the application is ready to access the keyboard device: 

 bool Keyboard::restore() {

     bool rc = true;

     if (keyboard) {
         HRESULT hr = keyboard->Acquire();
         if (hr != S_OK && hr != S_FALSE && hr != DIERR_OTHERAPPHASPRIO) {
             release();
             error(L"Keyboard::70 Failed to re-acquire the keyboard");
             rc = false;
         }
     }

     return rc;
 }

Release

The release() method on the Keyboard object disconnects the current object from the keyboard device: 

 void Keyboard::release() {
     suspend();
     if (keyboard) {
         keyboard->Release();
         keyboard = NULL;
     }
 }

Note that this method notifies the operating system that the application not longer accesses the device before releasing the interface to the device. 

Destructor

The destructor disconnects the current object from the keyboard device and releases the interface to the DirectInput object:

 Keyboard::~Keyboard() {
     release();
     if (di) {
         di->Release();
         di = NULL;
     }
 }

Mouse

The Mouse class of the Direct API branch provides mouse access through the DirectInput API. 

Interface

The iMouse interface exposes six virtual methods to the framework:

  • setup() - connects the Mouse object to the mouse device
  • restore() - restores the connection between the Mouse object and the mouse device
  • retrieveInput() - retrieves the input data from the Mouse object
  • suspend() - suspends the connection between the Mouse object and the mouse device
  • release() - releases the connection between the Mouse object and the mouse device
  • Delete() - deletes the Mouse object
 class iMouse {
   public:
     virtual bool setup(void* hwnd)                                   = 0;
     virtual bool restore()                                           = 0;
     virtual void retrieveInput()                                     = 0;
     virtual void suspend()                                           = 0;
     virtual void release()                                           = 0;
     virtual void Delete() const                                      = 0;
 };

 extern "C"
 iMouse* CreateMouse(void*, iContext*);
 

Class Definition

The Mouse class is a singleton that derives from the Mouse interface and wraps the directInput calls for a buffered mouse device.  This class includes two instance pointers:

  • di - points to an interface to the DirectInput object
  • mouse - points to an interface to a DirectInput mouse device
 class Mouse : public iMouse {

     static const int SAMPLE_BUFFER_SIZE = 30;

     iContext*            context;  // points to the Context object
     LPDIRECTINPUT8       di;       // points to the Direct Input object
     LPDIRECTINPUTDEVICE8 mouse;    // points to the Direct Input mouse

     Mouse(void*, iContext*);
     Mouse(const Mouse& m);            // prevents copying
     Mouse& operator=(const Mouse& m); // prevents assignment
     virtual ~Mouse();

   public:
     friend iMouse* CreateMouse(void*, iContext*);
     bool   setup(void*);
     bool   restore();
     void   retrieveInput();
     void   suspend();
     void   release();
     void   Delete() const { delete this; }
 };

Implementation

The CreateMouse() function creates a Mouse object on dynamic memory:

 iMouse* CreateMouse(void* hinst, iContext* c) {
     return new Mouse(hinst, c);
 }

Construction

The Mouse constructor retrieves the address of an interface to the DirectInput COM object and initializes the address of the mouse device:

 Mouse::Mouse(void* hinst, iContext* c) : context(c) {
     // acquire an interface to the DirectInput object for this application
     di = NULL;
     if (FAILED(DirectInput8Create((HINSTANCE)hinst, DIRECTINPUT_VERSION,
      IID_IDirectInput8, (void**)&di, NULL))) {
          error(L"Mouse::00 Unable to obtain an interface to DirectInput");
     }
     mouse  = NULL;
 }

Setup

The setup() method on the Mouse object connects to the mouse device.  Setup consists of five steps:

  1. the call to the CreateDevice() method on the DirectInput object retrieves the address of the interface to the COM object that represents the mouse device
  2. the call to the SetDataFormat() method on the interface to the mouse device defines the data format for input from the device
  3. the call to the SetCooperativeLevel() method on the mouse device defines how the device is shared with other applications
  4. the call to the SetProperties() method on the interface to the mouse device specifies the size of the buffer that collects the input data from the device
  5. the call to the Acquire() method on the interface to the mouse device notifies the operating system that the current application is ready to receive data from the device
 bool Mouse::setup(void* hwnd) {

     bool rc = false;

     // release the mouse object to start from scratch
     release();

     // obtain Interface to the mouse
     if (FAILED(di->CreateDevice(GUID_SysMouse, &mouse, NULL)))
         error(L"Mouse::10 Failed to obtain an Interface to system"
          L"mouse");
     // set the data format for mouse data
     else if (FAILED(mouse->SetDataFormat(&c_dfDIMouse))) {
         release();
         error(L"Mouse::11 Failed to set the data format for mouse");
     }
     // set the cooperative level
     else if (FAILED(mouse->SetCooperativeLevel(hwnd,
      DISCL_NONEXCLUSIVE | DISCL_FOREGROUND))) {
         release();
         error(L"Mouse::12 Failed to set cooperative level for mouse");
     }
     else {
         // set the size of the mouse buffer
         //
         // proerty structure consists of a header and a data member
         DIPROPDWORD dipdw;
         // property header
         // - size of enclosing structure
         dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
         // - always size of DIPROPHEADER
         dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
         // - identifier for property in question - 0 for entire device
         dipdw.diph.dwObj        = 0;
         // - DIPH_DEVICE since entire device is involved
         dipdw.diph.dwHow        = DIPH_DEVICE;
         // property data member (takes a single word of data)
         // - the buffer size goes here
         dipdw.dwData            = SAMPLE_BUFFER_SIZE;

         // set the buffer size
         if (FAILED(mouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph))) {
             release();
             error(L"Mouse::13 Failed to set the buffer size");
         }
         // flush the buffer: data currently in buffer will be ignored
         else {
             // try to acquire the mouse
             if (SUCCEEDED(mouse->Acquire()))
                 retrieveInput();
             rc = true;
         }
     }

     return rc;
 }

The CreateDevice() function on the DirectInput object receives as its first parameter the GUID that identifies the mouse and returns through its second parameter the address of the interface to the COM object that represents the mouse device.  The third argument is NULL for non-aggregated applications. 

The pre-defined macro GUID_SysMouse holds the GUID for the system mouse. 

The SetDataFormat() method on the interface to the mouse device receives as its parameter the address of an instance of a DIDATAFORMAT struct.  This instance defines the format of the data in the mouse buffer.  The DirectInput header file includes a pre-defined global instance of this struct - c_dfDIMouse - that describes this data format. 

The SetCooperativeLevel() method on the interface to the mouse device receives as its first parameter a handle to the application window and as its second parameter a set of flags that describe the device's behavior.  DISCL_FOREGROUND access specifies that the mouse device is unacquired as soon as the application moves into the background.  DISCL_NONEXCLUSIVE specifies that access to the mouse device does not interfere with other applications that are using the mouse device. 

The SetProperties() method on the interface to the mouse device receives as its first parameter the name of the property to be set - DIPROP_BUFFERSIZE - and as its second parameter the address of the property header within the property struct.  (See the description under the Keyboard class for more details.)

retrieveInput

The retrieveInput() method on the Mouse object retrieves the data stored in the input buffer for the mouse device and stores it in the Context object:

 void Mouse::retrieveInput() {

     HRESULT hr;
     DWORD items = SAMPLE_BUFFER_SIZE;
     DIDEVICEOBJECTDATA dod[SAMPLE_BUFFER_SIZE];

     if (mouse) {
         hr = mouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), dod,
          &items, 0);
         // try to re-acquire if lost
         if (DIERR_INPUTLOST == hr && SUCCEEDED(mouse->Acquire()))
             hr = mouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), dod,
              &items, 0);
         if (SUCCEEDED(hr)) {
             bool button[MAX_M_BUTTONS] = {0};
             int x = 0, y = 0, z = 0;
             for (DWORD i = 0; i < items; i++) {
                 switch (dod[i].dwOfs) {
                   case DIMOFS_BUTTON0:
                       button[0] = (dod[i].dwData & 0x80) == 0x80;
                       break;
                   case DIMOFS_BUTTON1:
                       button[1] = (dod[i].dwData & 0x80) == 0x80;
                       break;
                   case DIMOFS_X:
                       x += dod[i].dwData;
                       break;
                   case DIMOFS_Y:
                       y += dod[i].dwData;
                       break;
                   case DIMOFS_Z:
                       z += dod[i].dwData;
                       break;
                 }
             }
             for (int i = 0; i < MAX_M_BUTTONS; i++)
                 context->set(GF_MS_BTNS, i, button[i]);
             context->set(GF_MS_POSX, x);
             context->set(GF_MS_POSY, y);
             context->set(GF_MS_ORIZ, z);
         }
     }
 }

See the description under the Keyboard class for details regarding the structure of the data received from the mouse device.

Note that the displacement and rotation data extracted from the buffer are cumulative while the button data is the most recent data. 

Suspend

The suspend() method on the Mouse object notifies the operating system that the application has stopped receiving input from the mouse device: 

 void Mouse::suspend() {
     if (mouse) mouse->Unacquire();
 }

Restore

The restore() method on the Mouse object notifies the operating system that the application is ready to receive input from the mouse device. 

 bool Mouse::restore() {

     bool rc = true;

     if (mouse) {
         HRESULT hr = mouse->Acquire();
         if (hr != S_OK && hr != S_FALSE && hr != DIERR_OTHERAPPHASPRIO) {
             release();
             error(L"Mouse::70 Failed to re-acquire the mouse");
             rc = false;
         }
     }

     return rc;
 }

Release

The release() method on the Mouse object releases the connection to the mouse device: 

 void Mouse::release() {
     suspend();
     if (mouse) {
         mouse->Release();
         mouse = NULL;
     }
 }

Note that this method notifies the operating system that the application has stopped receiving input from the mouse device before releasing the interface to it. 

Destructor

The destructor releases the mouse device and the interface to the DirectInput object

 Mouse::~Mouse() {
     release();
     if (di) {
         di->Release();
         di = NULL;
     }
 }

Context

The Context class holds the current mouse states and accumulated mouse motion values.  This upgrade only affects the implementation of this class.

Implementation

Constructor

The constructor allocates memory for the mouse button states:

 Context::Context() {

     // ...

     // allocate context memory for button states
     alloc(GF_MS_BTNS, MAX_M_BUTTONS);

     // ...
 }

Design

The Design component now polls mouse input in addition to keyboard input.  The implementation alone is affected by this upgrade. 

The Design object supervises the two new components of the framework.  The Design object creates the Keyboard and Mouse objects and eventually destroys them.  The Design object manages the flow of control through these objects. 

Class Definition

The Design class includes instance pointers to the Keyboard and Mouse objects:

 class Design : public iDesign {

     iContext*     context;     // points to the Context object
     iCoordinator* coordinator; // points to the Coordinator object
     iDisplay*     display;     // points to the Display object
     iSoundCard*   soundCard;   // points to the SoundCard object
     iKeyboard*    keyboard;    // points to the Keyboard object
     iMouse*       mouse;       // points to the Mouse object

     // ...

   public:
     // ...
 };

Implementation

Construction

The constructor creates the Keyboard and Mouse objects:

 Design::Design(void* hinst, iContext* c) : context(c) {

     coordinator = CreateCoordinator(context);
     display     = CreateDisplay(context);
     soundCard   = CreateSoundCard(context);
     keyboard    = CreateKeyboard(hinst, context);
     mouse       = CreateMouse(hinst, context);

     // ...
 }

Setup

The setup() method on the Design object sets up the Keyboard and Mouse objects:

 bool Design::setup(void* hwnd) {

     bool rc = false;

     // setup the mouse - optional
     if (!mouse->setup(hwnd))
         error(L"Design::08 Failed to setup the mouse object");

     // setup the keyboard - necessary
     if (!keyboard->setup(hwnd))
         error(L"Design::09 Failed to setup the keyboard object");

     // setup the graphics card
     else if (!display->setup(hwnd))
         error(L"Design::10 Failed to set up the Display object");

     else
         rc = true;

     return rc;
 }

Note that this method does not fail if the mouse setup fails, but does fail if the keyboard setup fails.

Update

The update() method on the Design object captures the keyboard and mouse input since the last frame and responds to any mouse button presses:

 void Design::update(int now) {

     // retrieve user input, if any
     keyboard->retrieveInput();
     mouse->retrieveInput();
     coordinator->update(now);

     // ...

     // add changes introduced by mouse input
     dr = context->pressed(LEFT_BUTTON)  * delta * MOUSE_BUTTON_SCALE;
     ds = context->pressed(RIGHT_BUTTON) * delta * MOUSE_BUTTON_SCALE;

     // ...
 }

The MOUSE_BUTTON_SCALE factor adjusts the intensity of the effect and is defined in the ModelSettings.h file.

Suspend

The suspend() method on the Design object suspends the Keyboard and Mouse objects:

 void Design::suspend() {
     coordinator->suspend();
     display->suspend();
     mouse->suspend();
     keyboard->suspend();
 }

Restore

The restore() method on the Design object restores the Keyboard and Mouse objects:

 void Design::restore(int now) {
     keyboard->restore();
     mouse->restore();
     display->restore();
     coordinator->restore(now);
     lastUpdate = now;
 }

Release

The release() method on the Design object releases the Keyboard and Mouse objects:

 void Design::release() {
     coordinator->release();
     display->release();
     keyboard->release();
     mouse->release();
 }

Destructor

The destructor deletes the Keyboard and Mouse objects:

 Design::~Design() {
     coordinator->Delete();
     display->Delete();
     soundCard->Delete();
     mouse->Delete();
     keyboard->Delete();
 }

Camera

The Camera class now polls mouse input as well.  This upgrade only affects the Camera implementation. 

Implementation

Update

The update() method on the Camera object responds to motion in the x and y directions of the mouse and rotation of the mouse wheel:

 void Camera::update(int delta) {

     int dx = 0, // strafe left/right
         dy = 0, // fly up/down
         dz = 0; // advance/retreat
     int rx = 0, // pitch up/down
         ry = 0, // yaw left/right
         rz = 0; // roll
     // ...

     // mouse input
     int mx = context->get(GF_MS_DSPX);
     int my = context->get(GF_MS_DSPY);
     int mz = context->get(GF_MS_ROTZ);
     if (mx)
         ry += mx * MOUSE_SPEED;
     if (my)
         rx += my * MOUSE_SPEED;
     if (mz)
         dy += mz * MOUSE_SPEED;

     // ...
 }

Exercises

  • Read the Wikipedia article on Direct Input
  • Read the DirectX Documentation on DirectInput



Previous Reading  Previous: 3D Sound Next: Controller   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo