-2

I am using raw input for keyboard input to differentiate between keyboards (a local co-op multi player game where each player can connect their own keyboard). But when the game starts up I am having a hard time trying to detect which keyboard has numlock and capslock and scroll lock turned on. How do I go about polling that information when I get a GIDC_ARRIVAL from WM_INPUT_DEVICE_CHANGE messages that i get from using RIDEV_DEVNOTIFY.

I thought about using GetAsyncKeyState and GetKeyboardState and GetKeyState but they don't work with multi keyboards

Here is a sample program test prototype for raw input

#ifndef NOMINMAX
#define NOMINMAX
#endif

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

//windows
#include <windows.h>
#include <dbt.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

typedef uint8_t  u8;
typedef uint32_t u32;
typedef int64_t  s64;
typedef float    f32;

#define DEFAULT_SCREEN_WIDTH 1280
#define DEFAULT_SCREEN_HEIGHT 720

#define MINIMUM_SCREEN_WIDTH 300
#define MINIMUM_SCREEN_HEIGHT 300

typedef struct GameState
{
    u8 hwIsRunning;
} GameState;

typedef struct Renderer
{
    //ScreenGraphics State
    u8 hwIsMinimized;

    //Win32 Screen Variables
    u32 dwScreenWidth = DEFAULT_SCREEN_WIDTH;
    u32 dwScreenHeight = DEFAULT_SCREEN_HEIGHT;
    HWND MainWindowHandle;
    const char *szWindowName = "FPS Camera Basic";
} Renderer;

Renderer sRENDERER;
GameState sGAMESTATE;

u32 max( u32 a, u32 b )
{
    return a > b ? a : b;
}

int logWindowsError(const char* msg)
{
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError();

    FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, dw, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPTSTR)&lpMsgBuf, 0, NULL );
    OutputDebugStringA( msg );
    OutputDebugStringA( (LPCTSTR)lpMsgBuf );

    LocalFree( lpMsgBuf );
    return -1;
}

inline
void CloseProgram()
{
    sGAMESTATE.hwIsRunning = 0;
}

LRESULT CALLBACK
Win32MainWindowCallback(
    HWND Window,  
    UINT Message,
    WPARAM WParam, 
    LPARAM LParam)
{
    LRESULT Result = 0;
    switch (Message)
    {

    case WM_SYSCHAR:
    break;

    case WM_SIZE:
    {
        u32 dwTempScreenWidth = LOWORD( LParam );
        u32 dwTempScreenHeight = HIWORD( LParam );
        if( WParam == SIZE_MINIMIZED || dwTempScreenWidth == 0 || dwTempScreenHeight == 0 )
        {
            sRENDERER.hwIsMinimized = 1;
        }
        else
        {
            sRENDERER.hwIsMinimized = 0;
        }
        sRENDERER.dwScreenWidth =  max( 1, dwTempScreenWidth );
        sRENDERER.dwScreenHeight = max( 1, dwTempScreenHeight );

    }break;


    case WM_GETMINMAXINFO:
    {
        LPMINMAXINFO lpMMI = (LPMINMAXINFO)LParam;
        lpMMI->ptMinTrackSize.x = MINIMUM_SCREEN_WIDTH;
        lpMMI->ptMinTrackSize.y = MINIMUM_SCREEN_HEIGHT;
    }break;

    case WM_CLOSE: //when user clicks on the X button on the window
    {
        CloseProgram();
    } break;

    case WM_ACTIVATE:
    {
        switch(WParam)
        {
            //WM_MOUSEACTIVATE 
            case WA_ACTIVE:
            case WA_CLICKACTIVE:
            case WA_INACTIVE:
            default:
            {
                break;
            }
        }
    } break;

    default:
        Result = DefWindowProc( Window, Message, WParam, LParam ); //call windows to handle default behavior of things we don't handle
    }

    return Result;
}


inline
void InitRawInput( HWND WindowHandle )
{ 
    RAWINPUTDEVICE Rid[2];
    Rid[0].usUsagePage = (USHORT) 0x01; 
    Rid[0].usUsage = (USHORT) 0x02; 
    Rid[0].dwFlags = RIDEV_INPUTSINK|RIDEV_DEVNOTIFY;   //RIDEV_INPUTSINK: If set, this enables the caller to receive the input even when the caller is not in the foreground
    Rid[0].hwndTarget = WindowHandle;

    Rid[1].usUsagePage = (USHORT) 0x01; 
    Rid[1].usUsage = (USHORT) 0x06; 
    Rid[1].dwFlags =  RIDEV_DEVNOTIFY | RIDEV_NOLEGACY;   //RIDEV_INPUTSINK: If set, this enables the caller to receive the input even when the caller is not in the foreground
    Rid[1].hwndTarget = 0;

    RegisterRawInputDevices( Rid, 2, sizeof( Rid[0] ) );

}

inline
void InitWin32Window()
{
    WNDCLASSEX WindowClass;
    WindowClass.cbSize = sizeof( WNDCLASSEX );
    WindowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; //https://devblogs.microsoft.com/oldnewthing/20060601-06/?p=31003
    WindowClass.lpfnWndProc = Win32MainWindowCallback;
    WindowClass.cbClsExtra = 0;
    WindowClass.cbWndExtra = 0;
    WindowClass.hInstance = GetModuleHandle( NULL );
    WindowClass.hIcon = LoadIcon( 0, IDI_APPLICATION ); //IDI_APPLICATION: Default application icon, 0 means use a default Icon
    WindowClass.hCursor = LoadCursor( 0, IDC_ARROW ); //IDC_ARROW: Standard arrow, 0 means used a predefined Cursor
    WindowClass.hbrBackground = NULL; 
    WindowClass.lpszMenuName = NULL;    // No menu 
    WindowClass.lpszClassName = "WindowTestClass"; //name our class
    WindowClass.hIconSm = NULL; //can also do default Icon here? will NULL be default automatically?

    if ( !RegisterClassEx( &WindowClass ) )
    {
        logWindowsError( "Failed to Register Window Class:\n" );
        sRENDERER.MainWindowHandle = 0;
        return;
    }

    HWND WindowHandle = CreateWindowEx( 0, WindowClass.lpszClassName, sRENDERER.szWindowName,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,  CW_USEDEFAULT, CW_USEDEFAULT, sRENDERER.dwScreenWidth, sRENDERER.dwScreenHeight, //if fullscreen get monitor width and height
        0, 0, WindowClass.hInstance, NULL );

    if ( !WindowHandle )
    {
        logWindowsError( "Failed to Instantiate Window Class:\n" );
        sRENDERER.MainWindowHandle = 0;
        return;   
    }

    sRENDERER.MainWindowHandle = WindowHandle;
    InitRawInput( sRENDERER.MainWindowHandle  );
}

inline
void InitStartingGameState()
{
    sGAMESTATE.hwIsRunning = 1;
}


//Use subsystem console when compiling
int main()
{
    InitStartingGameState();

    InitWin32Window();

    if( !sRENDERER.MainWindowHandle )
    {
        return -1;
    }

    while( sGAMESTATE.hwIsRunning )
    {
        MSG Message;
        while( PeekMessage( &Message, 0, 0, 0, PM_REMOVE ) )
        {
            switch( Message.message )
            {
                case WM_QUIT:
                {
                    CloseProgram();
                    break;
                }
                case WM_SYSKEYDOWN:
                case WM_SYSKEYUP:
                case WM_KEYDOWN:
                case WM_KEYUP:
                {
                    break;
                }
                case WM_INPUT_DEVICE_CHANGE:
                {
                    HANDLE hDevice = (HANDLE)Message.lParam;
                    RID_DEVICE_INFO deviceInfo;
                    deviceInfo.cbSize = sizeof(RID_DEVICE_INFO);
                    u32 dwSize = sizeof(RID_DEVICE_INFO);
                    GetRawInputDeviceInfo(hDevice,RIDI_DEVICEINFO,&deviceInfo,&dwSize);

                    u32 dwNameSize = 0;
                    u32 res = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, nullptr, &dwNameSize);

                    char *pName = new char[dwNameSize+1];
                    res = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, pName, &dwNameSize);
                    pName[dwNameSize] = 0;

                    switch(Message.wParam)
                    {
                        case GIDC_ARRIVAL:
                        {
                            switch(deviceInfo.dwType)
                            {
                                case RIM_TYPEKEYBOARD:
                                {
                                    //TODO HOW TO DETECT LOCK KEYS HERE
                                } break;
                                default:
                                {

                                } break;
                            }
                            printf("GIDC_ARRIVAL %p %d %s\n",hDevice,deviceInfo.dwType,pName);
                        } break;
                        case GIDC_REMOVAL:
                        {
                            switch(deviceInfo.dwType)
                            {
                                case RIM_TYPEKEYBOARD:
                                {

                                } break;
                                default:
                                {

                                } break;
                            }
                            printf("GIDC_REMOVAL %p %d %s\n",hDevice,deviceInfo.dwType,pName);
                        } break;
                        default:
                        {

                        } break;
                    }
                    delete [] pName;
                } break;
                case WM_INPUT:
                {
                    UINT dwSize = sizeof( RAWINPUT );
                    static BYTE lpb[sizeof( RAWINPUT )];

                    GetRawInputData( (HRAWINPUT)Message.lParam, RID_INPUT, lpb, &dwSize, sizeof( RAWINPUTHEADER ) );
                
                    RAWINPUT* raw = (RAWINPUT*)lpb;
                
                    if(raw->header.dwType == RIM_TYPEKEYBOARD )
                    {
                        bool bIsUp = (raw->data.keyboard.Flags & RI_KEY_BREAK) != 0;

                        u32 dwVirtualKey = raw->data.keyboard.VKey;
                        u32 dwScanCode = raw->data.keyboard.MakeCode;
                        u32 dwFlags = raw->data.keyboard.Flags;

                        //used for determining left or right ctrl/alt (left and right shift this does not apply to)
                        const bool isE0 = ((dwFlags & RI_KEY_E0) != 0);
                        const bool isE1 = ((dwFlags & RI_KEY_E1) != 0);

                        //TODO use the value of the locks here

                    }
                    else
                    {
                        TranslateMessage( &Message );
                        DispatchMessage( &Message );
                    }
                    break;
                }
                default:
                {
                    TranslateMessage( &Message );
                    DispatchMessage( &Message );
                    break;
                }
            }
        }       
    }

    return 0;
}
yosmo78
  • 489
  • 4
  • 13
  • 1
    Unfortunately, keyboard state is stored per-thread on Windows, you don't have access to key states from the Win32 API when dealing with the Raw Input API, since it is triggered before input reaches any particular UI thread. You would have to talk to the keyboard hardware directly to get that info. – Remy Lebeau Aug 21 '23 at 20:05

0 Answers0