Entrées clavier et souris avec DirectInput

 

souris

Intro :

DirectInput est un composant de DirectX qui permet de gérer les entrées clavier (touches) et de la souris (position).

Bien qu’il soit tendance à n’être plus utilisé, il est toujours utile car il permet de capturer directement les entrées clavier à partir des drivers périphériques correspondants.

Prérequis :

– Savoir un peu lire du C++.

– Savoir initialiser DirectX 10.1.

– Savoir utiliser la classe Singleton.

Explications :

Voici le fichier InputManager.h :

#ifndef INPUT_MANAGER_H
#define INPUT_MANAGER_H

#define DIRECTINPUT_VERSION 0x0800

#include <dinput.h>
#include <map>
#include <vector>

#include "Singleton.h"
#include "System.h"

class KeyEvent
{
public:
    KeyEvent(int kc, wchar_t txt) : key(kc), text(txt) {}
    virtual ~KeyEvent() {}

    // L'ID de la touche
    const int key;
    // La caractère correspondant de la touche
    const wchar_t text;
};

class KeyListener
{
public:
    virtual ~KeyListener() {}

    virtual void RepetitiveKeyPressed(const KeyEvent &arg) = 0;
    virtual void RepetitiveKeyReleased(const KeyEvent &arg) = 0;

    virtual void SoloKeyPressed(const KeyEvent &arg) = 0;
    virtual void SoloKeyReleased(const KeyEvent &arg) = 0;
};

class InputManager : public Singleton<InputManager>
{
public:
    enum Modifier
    {
        SHIFT = 0x0000001,
        CTRL  = 0x0000010,
        ALT   = 0x0000100
    };

    InputManager();
    virtual ~InputManager();

    bool Initialize(HINSTANCE hInstance, HWND hwnd, int iScreenWidth, int iScreenHeight);

    void Frame();

    void GetMouseLocation(int& mouseX, int& mouseY);

    bool IsKeyDown(char key);
    bool IsMouseButtonDown(unsigned int button);
    bool IsModifierDown(Modifier modifier);

    void SetBuffered(bool bBuffered);

    // Enregistre le receveur de touches clavier
    void SetKeyListener(KeyListener* pKeyListener);

    // Renvoie la signification de la clé, ex : "F1" pour la touche F1
    std::string GetKeyCodeAsString(char key);

    // Renvoie la touche correspondante à la clé
    wchar_t GetKeyCodeAsWChar(char key);

private:    
    bool ReadKeyboard();
    bool ReadMouse();
    void ProcessInput();

    void UpdateBuffered();
    void UpdateNonBuffered();

private:
    IDirectInput8* m_pDI;
    IDirectInputDevice8* m_pKeyboard;
    IDirectInputDevice8* m_pMouse;

    unsigned char m_keyboardState[256];

    DIMOUSESTATE m_mouseState;

    int m_iScreenWidth;
    int m_iScreenHeight;

    int m_iMouseX;
    int m_iMouseY;

    wchar_t m_deadkey;

    bool m_bBufferedMode;

    unsigned int m_iModifiers;

    std::map<int, bool> m_pressedKeys;
 
    /* Pourquoi ici un unique objet ?
    Car on a besoin que d'un seul KeyListener car
    c'est soit la console, soit la messagerie qui ont besoin des entrées clavier
    à la fois */
    KeyListener* m_pKeyListener;

    // Variables conçues pour que l'appel à la callback KeyListener soit appelée moins de fois dans le temps
    unsigned long m_iElapsedTime;
    unsigned long m_iLastTime;
    unsigned int m_iWaitTime;

    // Variables conçues pour la touche appuyée soit repétée quand on reste longtemps appuyée dessus
    unsigned long m_iElapsedTime2;
    unsigned long m_iLastTime2;
    unsigned int m_iWaitTime2;

    bool m_bCanRepeatKey;
    unsigned int m_iCurrentKeyCode;
};

#endif

 

Voici le fichier InputManager.cpp :


#include "Console.h"
#include "InputManager.h"

#include <sstream>
#include <DxErr.h>

template<> InputManager* Singleton<InputManager>::ms_instance = nullptr;

InputManager::InputManager() :
m_pDI(nullptr),
m_pKeyboard(nullptr),
m_pMouse(nullptr),
m_iMouseX(0),
m_iMouseY(0),
m_deadkey('\0'),
m_bBufferedMode(false),
m_iModifiers(0),
m_pKeyListener(nullptr),
m_bCanRepeatKey(false),
m_iWaitTime(INPUT_MANAGER_REPETITIVE_WAIT_TIME),
m_iLastTime(0),
m_iElapsedTime(0),
m_iWaitTime2(INPUT_MANAGER_REPETITIVE_WAIT_TIME_2),
m_iLastTime2(0),
m_iElapsedTime2(0),
m_iCurrentKeyCode(0)
{
    SetBuffered(true);
}

InputManager::~InputManager()
{
    if (m_pMouse)
    {
        m_pMouse->Unacquire();
        SAFE_RELEASE(m_pMouse);
    }

    if (m_pKeyboard)
    {
        m_pKeyboard->Unacquire();
        SAFE_RELEASE(m_pKeyboard);
    }

    if (m_pDI)
    {
        SAFE_RELEASE(m_pDI);
    }

}

bool InputManager::Initialize(HINSTANCE hInstance, HWND hwnd, int iScreenWidth, int iScreenHeight)
{
    HRESULT hr;

    m_iScreenWidth = iScreenWidth;
    m_iScreenHeight = iScreenHeight;
    
    /****** Setup du clavier ******/
    hr= DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pDI, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pDI->CreateDevice(GUID_SysKeyboard, &m_pKeyboard, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pKeyboard->SetDataFormat(&c_dfDIKeyboard);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /***********/
    
    // On configure la taille du buffer qui sert à sauvegarder l'état
    // des touches du clavier entrées
    DIPROPDWORD dipdw;
    dipdw.diph.dwSize  = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj = 0;
    dipdw.diph.dwHow = DIPH_DEVICE;
    dipdw.dwData = INPUT_MANAGER_BUFFER_SIZE;

    hr = m_pKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /***********/
    hr = m_pKeyboard->Acquire();
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /****** Setup de la souris ******/

    hr = m_pDI->CreateDevice(GUID_SysMouse, &m_pMouse, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->SetDataFormat(&c_dfDIMouse);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->Acquire();
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    return true;
}

void InputManager::SetKeyListener(KeyListener* pKeyListener)
{
    assert(pKeyListener);

    m_pKeyListener = pKeyListener;
}

bool InputManager::IsModifierDown(Modifier modifier)
{
    return m_iModifiers & MKF_MODIFIERS;
}

void InputManager::Frame()
{
    ReadKeyboard();

    ReadMouse();

    ProcessInput();
}

void InputManager::GetMouseLocation(int& mouseX, int& mouseY)
{
    mouseX = m_iMouseX;
    mouseY = m_iMouseY;
}

bool InputManager::ReadKeyboard()
{
    if (m_bBufferedMode)
    {
        UpdateBuffered();
    }
    else
    {
        UpdateNonBuffered();
    }

    return true;
}

/* setcapture & getcapture (essayer) */
void InputManager::UpdateBuffered()
{
    DIDEVICEOBJECTDATA diBuff[INPUT_MANAGER_BUFFER_SIZE];
    DWORD entries = INPUT_MANAGER_BUFFER_SIZE;

    HRESULT hr;
    hr = m_pKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), diBuff, &entries, 0);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            ZeroMemory(m_keyboardState, sizeof(m_keyboardState));
            m_pKeyboard->Acquire();
        }
    }

    // Pour la touche soit repétée quand on reste longtemps appuyée dessus
    if (m_iCurrentKeyCode != 0)
    {
        m_iElapsedTime2 = GetTickCount() - m_iLastTime2;
        if (m_iElapsedTime2 > m_iWaitTime2)
        {
            m_bCanRepeatKey = true; 

            m_iLastTime2 = GetTickCount();
        }
    }
    else
    {
        // On reset le timer au-dessus
        m_iLastTime2 = GetTickCount();
    }

    if (!SYSTEM->HasFocus())
    {
        m_bCanRepeatKey = false;
        m_iElapsedTime2 = 0;
        m_iCurrentKeyCode = 0;
    }

    // On parcourt les touches appuyées
    for (unsigned int i = 0; i < entries; i++)
    {
        int iKeyCode = diBuff[i].dwOfs;

        if (diBuff[i].dwData & 0x80)
        {        
            m_pressedKeys[iKeyCode] = true;

            m_iCurrentKeyCode = diBuff[i].dwData;    

            if (iKeyCode == DIK_LCONTROL || iKeyCode == DIK_RCONTROL)
            {
                m_iModifiers |= CTRL;
            }
            else if (iKeyCode == DIK_LSHIFT || iKeyCode == DIK_RSHIFT)
            {
                m_iModifiers |= SHIFT;
            }
            else if (iKeyCode == DIK_LMENU || iKeyCode == DIK_RMENU)
            {
                m_iModifiers |= ALT;
            }

            if (m_pKeyListener)
            {
                m_pKeyListener->SoloKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }
        }
        else
        {
            m_pressedKeys[iKeyCode] = false;
            m_bCanRepeatKey = false;
            m_iCurrentKeyCode = 0;

            if (iKeyCode == DIK_LCONTROL || iKeyCode == DIK_RCONTROL)
            {
                m_iModifiers &= ~CTRL;
            }
            else if (iKeyCode == DIK_LSHIFT || iKeyCode == DIK_RSHIFT)
            {
                m_iModifiers &= ~SHIFT;
            }
            else if (iKeyCode == DIK_LMENU || iKeyCode == DIK_RMENU)
            {
                m_iModifiers &= ~ALT;
            }

            if (m_pKeyListener)
            {
                m_pKeyListener->SoloKeyReleased( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }
        }
    }

    /********** Entrées répétées ***********/

    // On cherche quelle touche du clavier a été appuyée
    int iKeyCode = 0;
    for (auto it = m_pressedKeys.begin(); it != m_pressedKeys.end(); it++)
    {
        bool bPressed = it->second;
        if (bPressed)
        {
            iKeyCode = it->first;
        }
    }

    if (m_pKeyListener && iKeyCode != 0)
    {
        m_iElapsedTime = GetTickCount() - m_iLastTime;

        // On appel la callback a une certaine fréquence de temps
        if (m_iElapsedTime > m_iWaitTime)
        {
            m_pKeyListener->RepetitiveKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );

            if (m_bCanRepeatKey)
            {
                m_pKeyListener->SoloKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }

            m_iLastTime = GetTickCount();
        }
    }
}

void InputManager::UpdateNonBuffered()
{    
    HRESULT hr;

    hr = m_pKeyboard->GetDeviceState(sizeof(m_keyboardState), (LPVOID)&m_keyboardState);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            ZeroMemory(m_keyboardState, sizeof(m_keyboardState));
            m_pKeyboard->Acquire();
        }
    }
}

bool InputManager::ReadMouse()
{
    HRESULT hr;

    hr = m_pMouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            m_pMouse->Acquire();
        }
        else
        {
            return false;
        }
    }

    return true;
}

void InputManager::ProcessInput()
{
    m_iMouseX += m_mouseState.lX;
    m_iMouseY += m_mouseState.lY;

    if (m_iMouseX < 0)
    {
        m_iMouseX = 0;
    }

    if (m_iMouseY < 0)  
    {
        m_iMouseY = 0;
    }
    
    if (m_iMouseX > m_iScreenWidth)  
    {
        m_iMouseX = m_iScreenWidth;
    }

    if (m_iMouseY > m_iScreenHeight)
    {
        m_iMouseY = m_iScreenHeight;
    }
    
    return;
}

bool InputManager::IsKeyDown(char key)
{
    return KEYDOWN(m_keyboardState, key);
}

bool InputManager::IsMouseButtonDown(unsigned int button)
{
    return (m_mouseState.rgbButtons[button] & 0x80) != 0;
}

std::string InputManager::GetKeyCodeAsString(char key)
{
    std::string sKeyCode;

    char temp[256];

    DIPROPSTRING prop;
    prop.diph.dwSize = sizeof(DIPROPSTRING);
    prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    prop.diph.dwObj = static_cast<DWORD>(key);
    prop.diph.dwHow = DIPH_BYOFFSET;

    if (SUCCEEDED(m_pKeyboard->GetProperty(DIPROP_KEYNAME, &prop.diph)))
    {
        if (WideCharToMultiByte(CP_ACP, 0, prop.wsz, -1, temp, sizeof(temp), nullptr, nullptr))
        {
            return sKeyCode.assign(temp);
        }
    }

    std::stringstream ss;
    ss << "Key_" << (int) key;

    return sKeyCode.assign(ss.str());
}

wchar_t InputManager::GetKeyCodeAsWChar(char key)
{
    BYTE keyState[256];
    HKL layout = GetKeyboardLayout(0);

    if (GetKeyboardState(keyState) == 0)
    {
        return 0;
    }

    unsigned int vk = MapVirtualKeyEx(key, 3, layout);
    if (vk == 0)
    {
        return 0;
    }

    WCHAR buff[3] = {0};
    int ascii = ToUnicodeEx(vk, key, keyState, buff, 3, 0, layout);

    if(ascii == 1 && m_deadkey != '\0' )
    {
        WCHAR wcBuff[3] = {buff[0], m_deadkey, '\0'};
        WCHAR out[3];
        
        m_deadkey = '\0';
        if (FoldStringW(MAP_PRECOMPOSED, (LPWSTR)wcBuff, 3, (LPWSTR)out, 3))
        {
            return out[0];
        }
    }
    else if (ascii == 1)
    {        
        m_deadkey = '\0';
        return buff[0];
    }
    else if (ascii == 2)
    {
        switch (buff[0])    
        {
            case 0x5E: // Circumflex accent: â
                m_deadkey = 0x302;
                break;
            case 0x60: // Grave accent: à
                m_deadkey = 0x300;
                break;
            case 0xA8: // Diaeresis: ü
                m_deadkey = 0x308;
                break;
            case 0xB4: // Acute accent: é
                m_deadkey = 0x301;
                break;
            case 0xB8: // Cedilla: ç
                m_deadkey = 0x327;
                break;
            default:
                m_deadkey = buff[0];
                break;
        }
    }

    return 0;
}

void InputManager::SetBuffered(bool bBuffered)
{
    m_bBufferedMode = bBuffered;
}

 

Résumé :

Avec l’interface DirectInput, on peut élaborer un système d’entrée clavier souris tout simple.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *