Getting the Keyboard, the Mouse, or the Joystick

[Previous] [Next]

Once the input devices have been enumerated, you need to allow the user to select the input device he or she wants to use. The InputDeviceSelectProc callback routine below illustrates one way you can allow users to select the input device they want to use. This routine lets the user make a selection from the Select Input Device drop-down menu: joystick, keyboard, or keyboard with mouse.

The DirectInputDevice2 object represents input devices. Keep in mind that, as I mentioned earlier, Microsoft refers to many different input devices—including game pads and steering wheels—as joysticks. Therefore, my code also uses this terminology. I refer to all devices besides the keyboard and mouse as joysticks. Two GUIDs—GUID_SysKeyboard and GUID_SysMouse—are predefined for the keyboard and mouse. When creating DirectInputDevice2 objects for any devices other than the system mouse or keyboard, you must use the device's instance GUID, which you obtained via your call to the IDirectInput7::EnumDevices method.

After the user has determined which device he or she wants to create, the CreateInputDevice routine is called for the keyboard, the mouse, or the joystick.

 //------------------------------------------------------------------- // Name: InputDeviceSelectProc // Desc: Dialog procedure for selecting an input device //------------------------------------------------------------------- BOOL CALLBACK InputDeviceSelectProc( HWND hWnd, UINT msg,                                      WPARAM wParam, LPARAM lParam ) {     HWND hwndKeyboardButton = GetDlgItem( hWnd, IDC_KEYBOARD );     HWND hwndMouseButton    = GetDlgItem( hWnd, IDC_MOUSE );     HWND hwndJoystickButton = GetDlgItem( hWnd, IDC_JOYSTICK );     if( WM_INITDIALOG == msg )     {         SendMessage( hwndKeyboardButton, BM_SETCHECK,                       g_bUseKeyboard, 0L);         SendMessage( hwndMouseButton,    BM_SETCHECK,                       g_bUseMouse,    0L);         SendMessage( hwndJoystickButton, BM_SETCHECK,                       g_bUseJoystick, 0L);                  EnableWindow( hwndJoystickButton,                        (g_guidJoystick != GUID_NULL) );         return TRUE;     }     if( WM_COMMAND == msg && IDOK == LOWORD(wParam) )     {         // Destroy the old device.         pCMyApp->DestroyInputDevice();         // Check the dialog controls to see which type of device          // to create.         g_bUseKeyboard = SendMessage(hwndKeyboardButton,                                      BM_GETCHECK,0, 0L);         g_bUseMouse    = SendMessage(hwndMouseButton,                                      BM_GETCHECK,0, 0L);         g_bUseJoystick = SendMessage(hwndJoystickButton,                                      BM_GETCHECK,0, 0L);              if( g_bUseKeyboard )         {             pCMyApp->CreateInputDevice( GetParent(hWnd),                                     g_Keyboard_pDI,                                     g_Keyboard_pdidDevice2,                                     GUID_SysKeyboard,                                      &c_dfDIKeyboard,                                      DISCL_NONEXCLUSIVE |                                     DISCL_FOREGROUND );             g_Keyboard_pdidDevice2->Acquire();         }                      if( g_bUseMouse )          {             pCMyApp->CreateInputDevice( GetParent(hWnd), g_pDI,                                      g_pdidDevice2,                                     GUID_SysMouse,                                      &c_dfDIMouse,                                      DISCL_EXCLUSIVE |                                     DISCL_FOREGROUND );             g_pdidDevice2->Acquire();             pCMyApp->CreateInputDevice( GetParent(hWnd),                                     g_Keyboard_pDI,                                      g_Keyboard_pdidDevice2,                                     GUID_SysKeyboard,                                      &c_dfDIKeyboard,                                      DISCL_NONEXCLUSIVE |                                     DISCL_FOREGROUND );             g_Keyboard_pdidDevice2->Acquire();         }                  if( g_bUseJoystick )          {             pCMyApp->CreateInputDevice( GetParent(hWnd), g_pDI,                                      g_pdidDevice2,                                     g_guidJoystick,                                      &c_dfDIJoystick,                                      DISCL_EXCLUSIVE |                                     DISCL_FOREGROUND );             g_pdidDevice2->Acquire();             // Set the range of the joystick axes tp [-1000,+1000].             DIPROPRANGE diprg;              diprg.diph.dwSize       = sizeof(DIPROPRANGE);              diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);              diprg.diph.dwHow        = DIPH_BYOFFSET;              diprg.lMin              = -10;              diprg.lMax              = +10;              diprg.diph.dwObj = DIJOFS_X;    // Set the x-axis range.             g_pdidDevice2->SetProperty( DIPROP_RANGE, &diprg.diph );             diprg.diph.dwObj = DIJOFS_Y;    // Set the y-axis range.             g_pdidDevice2->SetProperty( DIPROP_RANGE, &diprg.diph );             // Set the dead zone for the joystick axes (because many              // joysticks aren't perfectly calibrated to be 0 when              // centered).             DIPROPDWORD dipdw;              dipdw.diph.dwSize       = sizeof(DIPROPDWORD);              dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);              dipdw.diph.dwHow        = DIPH_DEVICE;              dipdw.dwData            = 1000; // Here, 1000 = 10%             dipdw.diph.dwObj = DIJOFS_X; // Set the x-axis dead zone.             g_pdidDevice2->SetProperty( DIPROP_DEADZONE,                                         &dipdw.diph );             dipdw.diph.dwObj = DIJOFS_Y; // Set the y-axis dead zone.             g_pdidDevice2->SetProperty( DIPROP_RANGE, &dipdw.diph );         }         EndDialog( hWnd, IDOK );         return TRUE;     }     if( WM_COMMAND == msg && IDCANCEL == LOWORD(wParam) )     {         EndDialog( hWnd, IDCANCEL );         return TRUE;     }     return FALSE; } 

If the user requests the use of a keyboard, this routine just calls the CreateInputDevice routine with the global variable c_dfDIKeyboard and with GUID_SysKeyboard, which is the system keyboard's GUID.

If the user requests the use of a mouse, this routine calls the CreateInputDevice routine with the global variable c_dfDIMouse and with GUID_SysMouse, which is the system mouse's GUID.

If the user requests the use of a joystick, this routine calls the CreateInputDevice routine with the global variable c_dfDIJoystick and the g_guidJoystick global variable specifying the joystick to use.

The code calls the IDirectInputDevice2::SetProperty method to set the device's properties (such as the size or the value specified in dwValue). In DirectInput, properties define a device's data buffer size, the range and granularity of the values received from an axis, the saturation values of an axis (discussed below), the dead zone, and whether axis data is absolute or relative.

In the routine above, you called the method four times: first to set the x-axis range, second to set the y-axis range, third to set the x-axis dead zone, and fourth to set the y-axis dead zone.

NOTE
The saturation property of an axis allows you to define tolerance zones at the extremes of a range. Normally, a force-feedback joystick that doesn't support saturation reports axis values to an application depending on the exact position of the stick. A device that supports saturation reports axis values in the same way but allows for tolerance zones near the minimum and maximum of the axis's range. Such a device reports an axis value in the tolerance zones as a minimum or a maximum axis value, regardless of the actual value. For example, if an application defines positive and negative saturation value of 7 on an axis with a range 0 through 10, the device will report the maximum axis value (10) as a user moves the stick past the position that would normally cause the device to report the value 7.

Before you get or set a property, you need to set up a property structure, which consists of a DIPROPHEADER structure followed by one or more data elements. The DIPROHEADER structure defines the size of the property and indicates how the data will be interpreted. These are the predefined property structures:

  • DIPROPDWORD Consists of a DIPROPHEADER structure and a DWORD data member for properties that require a single value, such as a buffer size.
  • DIPROPRANGE Used for range properties that require maximum and minimum values.
  • DIPROPGUIDANDPATH Allows applications to perform operations that are not supported by DirectInput.
  • DIPROPSTRING Allows applications to set Unicode string properties.

In addition, before you call the IDirectInputDevice2::SetProperty method, you must initialize the DIPROPHEADER structure by setting its members, which define the size of the property structure, the size of the DIPROPHEADER structure, a value that identifies the object whose property you want to set, and code indicating how the object identifier should be interpreted. Once you've done so, you can get or set properties for a whole device (as opposed to a device object) by setting the dwObj DIRPOPHEADER member to 0 and the dwHow member to the value DIPH_DEVICE. To get or set individual properties for a device object (such as a particular axis) fill the dwObj and dwHow with values to identify the object.

The CreateInputDevice routine, shown below, calls the IDirectInput7::CreateDeviceEx method to create the desired DirectInput device. This method is defined as follows:

 HRESULT CreateDeviceEx(     REFGUID rguid,                                    REFIID riid,     LPVOID *pvOut,       LPUNKNOWN pUnkOuter                           ); 

ParameterDescription
rguidReference to (C++) or address of (C) the instance GUID for the desired input device. The GUID is retrieved through the IDirectInput::EnumDevices method, or it can be one of the following predefined GUIDs:

GUID_SysKeyboard The default system keyboard

GUID_SysMouse The default system mouse

For the preceding GUID values to be valid, the application must define INITGUID before all other preprocessor directives at the beginning of the source file, or link to dxguid.lib.

riidGUID for the desired interface. Currently accepted values are IID_IDirectInputDevice, IID_IDirectInputDevice2, and IID_IDirectInputDevice7.
pvOutAddress of a variable to receive the interface pointer if successful.
pUnkOuterAddress of the controlling object's IUnknown interface for COM aggregation, or NULL if the interface is not aggregated. Most callers will pass NULL.

The routine itself follows.

 //------------------------------------------------------------------- // Name: CreateInputDevice // Desc: Creates a DirectInput device //------------------------------------------------------------------- HRESULT CMyD3DApplication::CreateInputDevice( HWND hWnd,                      LPDIRECTINPUT7       pDI,                         LPDIRECTINPUTDEVICE2 pDIdDevice,                      GUID guidDevice,                            const DIDATAFORMAT* pdidDataFormat,  DWORD dwFlags ) {     PrintMessage(NULL, "CD3DApplication::CreateInputDevice()",NULL,                   LOGFILE_ONLY);     // Obtain an interface to the input device.     if( FAILED( pDI->CreateDeviceEx( guidDevice,                                       IID_IDirectInputDevice2,                                      (VOID**)&pDIdDevice, NULL ) ) )     {         PrintMessage(NULL, "CD3DApplication::CreateInputDevice() _ "                      "CreateDeviceEx FAILED", NULL, LOGFILE_ONLY);         DisplayError( "CreateDeviceEx() failed" );         return E_FAIL;     }     else         PrintMessage(NULL, "CD3DApplication::CreateInputDevice() - "                      "CreateDeviceEx ok", NULL, LOGFILE_ONLY);     // Set the device data format. A data format specifies which     // controls on a device you're interested in and indicates how      // they should be reported.     if( FAILED( pDIdDevice->SetDataFormat( pdidDataFormat ) ) )     {         DisplayError( "SetDataFormat() failed" );         return E_FAIL;     }     // Set the cooperative level to let DirectInput know how this      // device should interact with the system and with other      // DirectInput applications.     if( FAILED( pDIdDevice->SetCooperativeLevel( hWnd, dwFlags ) ) )     {         DisplayError( "SetCooperativeLevel() failed" );         return E_FAIL;     }     if(guidDevice == GUID_SysKeyboard)         g_Keyboard_pdidDevice2 = pDIdDevice;     else         g_pdidDevice2 = pDIdDevice;     return S_OK; } 

Once the device has been created, call the IDirectInputDevice2::SetDataFormat method. Here's the declaration for this method:

 BOOL IDirectInputDevice::SetDataFormat(     LPCDIDATAFORMAT lpdf ); 

IDirectInputDevice2::SetDataFormat has one parameter: lpdf. This parameter accepts the address of a structure that defines the data format that DirectInputDevice2 should return. You can define your own DIDATAFORMAT structure, or you can use one of the predefined global constants: c_dfDIKeyboard, c_dfDIMouse, c_dfDIMouse2, c_dfDIJoystick, or c_dfDIJoystick2. These predefined constants suffice to allow your application to use most off-the-shelf devices; generally, you won't need to define a custom DIDATAFORMAT structure.

Before your application acquires and uses an input or output device, you must call the SetDataFormat method to tell DirectInput what device objects your application will use and how the data will be arranged.

The next step you need to perform before you can access the DirectInput device (in this case, the keyboard) is to use the method IDirectInputDevice::SetCooperativeLevel to set the device's behavior. This method sets the device's exclusivity, which determines how the input from the device is shared with other applications. Call the SetCooperativeLevel method with the handle of the window you want to associate with the device. For your first device—the keyboard—you must use the DISCL_NONEXCLUSIVE flag in the dwFlags parameter because DirectInput doesn't support exclusive access to keyboard devices. If it did, the user wouldn't even be able to use the Ctrl+Alt+Esc sequence to restart a computer with a DirectInput application running. If you're running in windowed mode, you also need to use the keyboard for any other existing Windows processes. The code following this discussion is used to set the device's cooperative level.

The next routine you'll need to define to properly clean up after your application, DestroyInputDevice, is the last routine you call when you're finished with the DirectInput device. This routine calls the IDirectInputDevice2::Unacquire and IDirectInputDevice2::Release methods to clean up the objects you created for handling the input devices.

 //------------------------------------------------------------------- // Name: DestroyInputDevice // Desc: Releases the DirectInput device //------------------------------------------------------------------- VOID CMyD3DApplication::DestroyInputDevice() {     // Unacquire and release the device's interfaces.     if( g_pdidDevice2 )     {         g_pdidDevice2->Unacquire();         g_pdidDevice2->Release();         g_pdidDevice2 = NULL;     }     // Keyboard     if( g_Keyboard_pdidDevice2 )     {         g_Keyboard_pdidDevice2->Unacquire();         g_Keyboard_pdidDevice2->Release();         g_Keyboard_pdidDevice2 = NULL;     }      } 

With the devices enumerated, and once the user has selected an input device, you need to call a routine to handle the user input. The routine to handle the user input, CMyD3DApplication::UpdateControls, is shown here.

 VOID CMyD3DApplication::UpdateControls() {     int i;     int look_up = 0;     int look_down = 0;     // Get the relative time, in seconds.     FLOAT fTime = ( timeGetTime() - GetBaseTime() ) * 0.001f;     g_fCurrentTime = timeGetTime() * 0.001f;     if( (g_Keyboard_pdidDevice2) || (g_pdidDevice2) )     {         HRESULT      hr;         BYTE         diks[256]; // DInput keyboard state buffer         DIMOUSESTATE dims;      // DInput mouse state structure         DIJOYSTATE   dijs;      // DInput joystick state structure         // Read the current keyboard state.         if( g_bUseKeyboard )         {             g_Keyboard_pdidDevice2->Acquire();             hr = g_Keyboard_pdidDevice2->GetDeviceState(sizeof(diks),                                                         &diks );         }         // Read the current mouse and keyboard state.         if( g_bUseMouse )         {             g_Keyboard_pdidDevice2->Acquire();             hr=g_Keyboard_pdidDevice2->GetDeviceState(sizeof(diks),                                                       &diks );             g_pdidDevice2->Acquire();                 hr = g_pdidDevice2->GetDeviceState(sizeof(DIMOUSESTATE),                                                       &dims);         }         // Read the current joystick state.         if( g_bUseJoystick )          {             // Poll the device before reading the current state.              // Polling is required for some devices (joysticks) but              // has no effect for others (keyboard and mouse). Note:              // Polling uses a DIDevice2 interface for the device.                      g_pdidDevice2->Poll();             g_pdidDevice2->Acquire();                 hr = g_pdidDevice2->GetDeviceState( sizeof(DIJOYSTATE),                                                        &dijs );         }                              // Check whether the input stream has been interrupted. If          // it has been interrupted, reacquire the input device and         // try again.         if( hr == DIERR_INPUTLOST )         {             PrintMessage(NULL, "DIERR_INPUTLOST", NULL,                          LOGFILE_ONLY);                              hr = g_pdidDevice2->Acquire();             if( FAILED(hr) )             {                 PrintMessage(NULL, "Acquire input device FAILED",             NULL, LOGFILE_ONLY);                 return; // S_OK;             }         }           // Read keyboard input only.         if( g_bUseKeyboard )         {             Controls.bLeft       = diks[DIK_LEFT]        && 0x80;             Controls.bRight      = diks[DIK_RIGHT]       && 0x80;             Controls.bForward    = diks[DIK_UP]          && 0x80;             Controls.bBackward   = diks[DIK_DOWN]        && 0x80;             Controls.bUp         = diks[DIK_NUMPADPLUS]  && 0x80;             Controls.bDown       = diks[DIK_NUMPADMINUS] && 0x80;             Controls.bHeadUp     = diks[DIK_PGUP]        && 0x80;             Controls.bHeadDown   = diks[DIK_PGDN]        && 0x80;             Controls.bStepLeft   = diks[DIK_COMMA]       && 0x80;             Controls.bStepRight  = diks[DIK_PERIOD]      && 0x80;             Controls.bFire       = diks[DIK_RCONTROL]    && 0x80;             Controls.bScores     = diks[DIK_S]           && 0x80;             Controls.bPrevWeap   = diks[DIK_INSERT]      && 0x80;             Controls.bNextWeap   = diks[DIK_DELETE]      && 0x80;             Controls.bTravelMode = diks[DIK_SPACE]       && 0x80;         }         // Read mouse and keyboard input.         if( g_bUseMouse )         {             // On really fast computers, the mouse appears to be              // still most of the time and moves in jumps. To combat              // this, we'll keep 0.1 second of persistence for any              // up/down values we read.             static FLOAT fUpTime = 0.0f;             static FLOAT fDnTime = 0.0f;             if( dims.lY < 0 ) fDnTime = 0.0f, fUpTime =  g_fCurrentTime+0.1f;             if( dims.lY > 0 ) fUpTime = 0.0f, fDnTime =  g_fCurrentTime+0.1f;             if(bMouseLookOn == TRUE )             {                 if(bMouseLookup_is_mouse_forward == TRUE)                 {                     look_up   = fDnTime-g_fCurrentTime > 0.0f;                     look_down = fUpTime-g_fCurrentTime > 0.0f;                 }                 else                 {                     look_down  = fDnTime-g_fCurrentTime > 0.0f;                     look_up    = fUpTime-g_fCurrentTime > 0.0f;                 }             }             Controls.bLeft       = dims.lX<0;             Controls.bRight      = dims.lX>0;             Controls.bForward    = diks[DIK_UP]       && 0x80;             Controls.bBackward   = diks[DIK_DOWN]     && 0x80;             Controls.bUp         = diks[DIK_ADD]      && 0x80;             Controls.bDown       = diks[DIK_SUBTRACT] && 0x80;             Controls.bHeadUp     = look_up;             Controls.bHeadDown   = look_down;             Controls.bStepLeft   = diks[DIK_COMMA]    && 0x80;             Controls.bStepRight  = diks[DIK_PERIOD]   && 0x80;             Controls.bFire       = dims.rgbButtons[0] && 0x80;             Controls.bScores     = diks[DIK_S]        && 0x80;             Controls.bPrevWeap   = diks[DIK_INSERT]   && 0x80;             Controls.bNextWeap   = diks[DIK_DELETE]   && 0x80;             Controls.bTravelMode = diks[DIK_SPACE]    && 0x80;         }         // Read joystick input.         if( g_bUseJoystick )         {             Controls.bLeft       = dijs.lX<0;             Controls.bRight      = dijs.lX>0;             Controls.bForward    = dijs.lY<0;             Controls.bBackward   = dijs.lY>0;             Controls.bUp         = diks[DIK_ADD]      && 0x80;             Controls.bDown       = diks[DIK_SUBTRACT] && 0x80;             Controls.bHeadUp     = look_up;             Controls.bHeadDown   = look_down;             Controls.bStepLeft   = diks[DIK_COMMA]    && 0x80;             Controls.bStepRight  = diks[DIK_PERIOD]   && 0x80;             Controls.bFire       = dijs.rgbButtons[0] && 0x80;             Controls.bScores     = diks[DIK_S]        && 0x80;             Controls.bPrevWeap   = diks[DIK_INSERT]   && 0x80;             Controls.bNextWeap   = diks[DIK_DELETE]   && 0x80;             Controls.bTravelMode = diks[DIK_SPACE]    && 0x80;             }         for(i = 0; i < 256; i++)         {             if( (diks[i] && 0x80) == FALSE)                  DelayKey2[i] = FALSE;         }         if(RRAppActive == TRUE)         {             MovePlayer(&Controls);         }              } } 

This routine verifies that you have created a keyboard, mouse, or joystick DirectInput device. If you have a joystick, it calls the IDirectInputDevice2::Poll method. This method has no effect on keyboards or mouse devices, but for joysticks and other similar devices, it will update the device state, generate input events (if the buffer data is enabled), or set notification events (if notification is enabled).

The IDirectInputDevice7::Acquire method is then used to get access to the input device. Before acquiring a device, however, you must set a data format with the IDirectInputDevice7::SetDataFormat method covered earlier in the chapter.

You can define your own DIDATAFORMAT structure or use one of the following predefined global constants:

  • c_dfDIKeyboard
  • c_dfDIMouse
  • c_dfDIMouse2
  • c_dfDIJoystick
  • c_dfDIJoystick2

As mentioned earlier, you should rarely need to create your own DIDATAFORMAT structure. The five predefined global variables listed here will handle almost any device you'll need to deal with.

Once the format is set and the device is acquired, you can use the IDirectInputDevice2::GetDeviceState method to get the immediate data on the attached input device. This method fills a DIDATAFORMAT structure, which is defined as follows:

 typedef struct DIDATAFORMAT {      DWORD dwSize;      DWORD dwObjSize;      DWORD dwFlags;      DWORD dwDataSize;      DWORD dwNumObjs;      LPDIOBJECTDATAFORMAT rgodf;  } DIDATAFORMAT, *LPDIDATAFORMAT;   typedef const DIDATAFORMAT *LPCDIDATAFORMAT; 

This structure has the following members.

dwSize Size of this structure, in bytes.

dwObjSize Size of the DIOBJECTDATAFORMAT structure, in bytes.

dwFlags Flags describing other attributes of the data format. This value can be one of the following flags:

  • DIDF_ABSAXIS The axes are in absolute mode. Setting this flag in the data format is equivalent to manually setting the axis mode property by using the IDirectInputDevice7::SetProperty method. This flag can't be combined with the DIDF_RELAXIS flag.
  • DIDF_RELAXIS The axes are in relative mode. Setting this flag in the data format is equivalent to manually setting the axis mode property by using the IDirectInputDevice7::SetProperty method. This flag can't be combined with the DIDF_ABSAXIS flag.

dwDataSize Size of a data packet returned by the device, in bytes. This value must be a multiple of 4 and must exceed the largest offset value for an object's data within the data packet.

dwNumObjs Number of objects in the rgodf array.

rgodf Address to an array of DIOBJECTDATAFORMAT structures. Each structure describes how one object's data should be reported in the device data. Typical errors include placing two pieces of information in the same location and placing one piece of information in more than one location.

The DIDATAFORMAT structure is filled with information about the device.

The declaration for the IDirectInputDevice2::GetDeviceState method follows:

 HRESULT GetDeviceState(     DWORD cbData,       LPVOID lpvData,  ); 

The lpvData parameter is used to indicate the address of a structure for the function to fill with information about the device's state. The cbData member is used to specify the size, in bytes, of the buffer in the lpvData parameter.

At this point, you'll have all the information you need about the type of device you've found, and you'll be able to read the various states of the devices' axes, buttons, and so on. In the CMyD3DApplication::UpdateControls routine, the diks variable holds the keyboard information, the dijs variable holds the joystick information, and the dims variable holds the mouse information.

As an example, the line

 Controls.bFire        = diks[DIK_LCONTROL]    && 0x80; 

assigns TRUE or FALSE (1 or 0) to the Controls.bFire member. If the left CONTROL key is pressed, the member is set to TRUE. If the left CONTROL key is not pressed, the member is set to FALSE. The remainder of the CMyD3DApplication::UpdateControls routine sets the members of the Controls structure to the settings received from the keyboard, mouse and keyboard, or joystick devices, depending on what devices are currently selected from the IDD_SELECTINPUTDEVICE pop-up window.

You now have all the code you need to implement support for any type of input device.



Inside Direct3D
Inside Direct3D (Dv-Mps Inside)
ISBN: 0735606137
EAN: 2147483647
Year: 1999
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net