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;
}