WinAPI Raw Input confusion

Started by
8 comments, last by trill41 5 years, 4 months ago

I'm quite new to the windows api and raw input is for lack of better terms kicking my tail. I've initialized it in my program, called and defined in the window proc with WM_INPUT, and I'm definitely receiving inputs. However that pesky key repeat delay is still there... First question would be is this direction I should be going programming an input framework in windows? Or is there a more recommended method in modern windows coding when needing on the fly and latency free response?

Here is my window proc:


LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_CLOSE:
		PostQuitMessage(0);
		break;
	case WM_DESTROY:
		return 0;
	case WM_INPUT:
	{
		char keyBuffer[sizeof(RAWINPUT)] = {};
		UINT KBsize = sizeof(RAWINPUT);
		GetRawInputData(reinterpret_cast<HRAWINPUT>(lParam), RID_INPUT, keyBuffer, &KBsize, sizeof(RAWINPUTHEADER));
		RAWINPUT *raw = (RAWINPUT*)keyBuffer;
		if (raw->header.dwType == RIM_TYPEMOUSE)
		{
			// read the mouse data
		}
			if (raw->header.dwType == RIM_TYPEKEYBOARD)
			{
				// Get key value from the keyboard member (of type RAWKEYBOARD)
				USHORT keyCode = raw->data.keyboard.VKey;
				switch (keyCode) {
				case VK_ESCAPE:
					PostQuitMessage(0);
					break;
				case 0x41:
					theta += 1.0f; // Rotate left when A is pressed.
					break;
				case 0x44:
					theta -= 1.0f; // Rotate right when D is pressed.
					break;
				}
			}	
	}
	return 0;
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
	return 0;
}

I understand that I should be defining the buffer to a hard array size, but this isn't my issue in the current case. I simply want the application to react to the key state, and not delay key repeats at the OS setting. Kind of silly to claim low level and I'm being code blocked by the OS. I've tested in this application with dwFlags at 0 (virtual key assign) and no legacy, to no avail.

I'm sure this has probably been asked before, but I could not find an article or post here younger than 11 years on the subject. And what was found not what I was looking for.

I've tried to watch my language -- retired sailor -- if I missed a few let me know for edit.

Advertisement

Microsoft suggests to use the default Windows messages, even for games (I read it somewhere but forgot where, but it makes sense). I would forget raw input and just use the default window messages. In your WndProc you could do something like this:


    switch (message)
    {
    case WM_MOUSEMOVE:
        OnMouseMove((DWORD)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        break;
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_XBUTTONDOWN:
        OnMouseDown((DWORD)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        break;
    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_MBUTTONUP:
    case WM_XBUTTONUP:
        OnMouseUp((DWORD)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        break;
    case WM_MOUSEWHEEL:
        OnMouseWheel((DWORD)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
            GET_WHEEL_DELTA_WPARAM(wParam));
        break;
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
        OnKeyDown((DWORD)MapLeftRightKeys(wParam, lParam));
        break;
    case WM_KEYUP:
    case WM_SYSKEYUP:
        OnKeyUp((DWORD)MapLeftRightKeys(wParam, lParam));
        break;
    // ...
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

 

Thanks for the reply trill41. As I'm finding over the last week of trying to puzzle this method together.

I will need raw input for talking to my applications with other tools besides standard peripherals -- such as an oscilloscope program with accompanying hardware. For standard HID mouse keyboard default messages are looking the way to stick. and a lot less headache.

Thanks again.

20 hours ago, trill41 said:

Microsoft suggests to use the default Windows messages

Even if Microsoft suggest this, it isn't the practise we really want to do. XInput is driving the same route these days catching on driver level while WM-Messages are quite looped through all the messages a window is receiving from the OS, including repaints and whatever so dont do this!

I was fighting with that some time too because I wanted to also grab HID Gamepad inputs from the OS without linking to the Media library. So HID was the only route to not have to worm with XInput.

You first need the Windows Driver SDK or define the bindings yourself and just lookup at initialization when needed. These are:


#ifndef INITGUID
#define INITGUID
#endif

#ifdef interface
#undef interface
#endif

#include <Windows.h>
#include <Dbt.h>

typedef struct _HIDD_ATTRIBUTES
{
	ULONG Size;
	USHORT VendorID;
	USHORT ProductID;
	USHORT VersionNumber;
} 
HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;

typedef USHORT USAGE;
typedef struct _HIDP_CAPS
{
    USAGE    Usage;
    USAGE    UsagePage;
    USHORT   InputReportByteLength;
    USHORT   OutputReportByteLength;
    USHORT   FeatureReportByteLength;
    USHORT   Reserved[17];

    USHORT   NumberLinkCollectionNodes;

    USHORT   NumberInputButtonCaps;
    USHORT   NumberInputValueCaps;
    USHORT   NumberInputDataIndices;

    USHORT   NumberOutputButtonCaps;
    USHORT   NumberOutputValueCaps;
    USHORT   NumberOutputDataIndices;

    USHORT   NumberFeatureButtonCaps;
    USHORT   NumberFeatureValueCaps;
    USHORT   NumberFeatureDataIndices;
} 
HIDP_CAPS, *PHIDP_CAPS;
typedef enum _HIDP_REPORT_TYPE
{
    HidP_Input,
    HidP_Output,
    HidP_Feature
} 
HIDP_REPORT_TYPE;
typedef struct _USAGE_AND_PAGE
{
    USAGE Usage;
    USAGE UsagePage;
} 
USAGE_AND_PAGE, *PUSAGE_AND_PAGE;
typedef struct _HIDP_BUTTON_CAPS
{
    USAGE    UsagePage;
    UCHAR    ReportID;
    BOOLEAN  IsAlias;

    USHORT   BitField;
    USHORT   LinkCollection;   // A unique internal index pointer

    USAGE    LinkUsage;
    USAGE    LinkUsagePage;

    BOOLEAN  IsRange;
    BOOLEAN  IsStringRange;
    BOOLEAN  IsDesignatorRange;
    BOOLEAN  IsAbsolute;

    ULONG    Reserved[10];
    union {
        struct {
            USAGE    UsageMin,         UsageMax;
            USHORT   StringMin,        StringMax;
            USHORT   DesignatorMin,    DesignatorMax;
            USHORT   DataIndexMin,     DataIndexMax;
        } Range;
        struct  {
            USAGE    Usage,            Reserved1;
            USHORT   StringIndex,      Reserved2;
            USHORT   DesignatorIndex,  Reserved3;
            USHORT   DataIndex,        Reserved4;
        } NotRange;
    };

} 
HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS;

typedef struct _HIDP_VALUE_CAPS
{
    USAGE    UsagePage;
    UCHAR    ReportID;
    BOOLEAN  IsAlias;

    USHORT   BitField;
    USHORT   LinkCollection;

    USAGE    LinkUsage;
    USAGE    LinkUsagePage;

    BOOLEAN  IsRange;
    BOOLEAN  IsStringRange;
    BOOLEAN  IsDesignatorRange;
    BOOLEAN  IsAbsolute;

    BOOLEAN  HasNull;
    UCHAR    Reserved;
    USHORT   BitSize;

    USHORT   ReportCount;
    USHORT   Reserved2[5];

    ULONG    UnitsExp;
    ULONG    Units;

    LONG     LogicalMin,       LogicalMax;
    LONG     PhysicalMin,      PhysicalMax;

    union {
        struct {
            USAGE    UsageMin,         UsageMax;
            USHORT   StringMin,        StringMax;
            USHORT   DesignatorMin,    DesignatorMax;
            USHORT   DataIndexMin,     DataIndexMax;
        } Range;

        struct {
            USAGE    Usage,            Reserved1;
            USHORT   StringIndex,      Reserved2;
            USHORT   DesignatorIndex,  Reserved3;
            USHORT   DataIndex,        Reserved4;
        } NotRange;
    };
} 
HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS;
typedef struct _HIDP_DATA
{
    USHORT  DataIndex;
    USHORT  Reserved;
    union {
        ULONG   RawValue;
        BOOLEAN On;
    };
} 
HIDP_DATA, *PHIDP_DATA;

typedef void* PHIDP_PREPARSED_DATA;
#define HIDP_STATUS_SUCCESS 0x110000

BOOLEAN (__stdcall *HidD_GetAttributes)(HANDLE device, PHIDD_ATTRIBUTES attrib) = 0;
BOOLEAN (__stdcall *HidD_GetSerialNumberString)(HANDLE device, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_GetManufacturerString)(HANDLE handle, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_GetProductString)(HANDLE handle, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_SetFeature)(HANDLE handle, PVOID data, ULONG length) = 0;
BOOLEAN (__stdcall *HidD_GetFeature)(HANDLE handle, PVOID data, ULONG length) = 0;
BOOLEAN (__stdcall *HidD_GetIndexedString)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_GetPreparsedData)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data) = 0;
BOOLEAN (__stdcall *HidD_FreePreparsedData)(PHIDP_PREPARSED_DATA preparsed_data) = 0;
BOOLEAN (__stdcall *HidD_SetNumInputBuffers)(HANDLE handle, ULONG number_buffers) = 0;
NTSTATUS (__stdcall *HidP_GetCaps)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps) = 0;
NTSTATUS (__stdcall *HidP_GetValueCaps) (HIDP_REPORT_TYPE ReportType, PHIDP_VALUE_CAPS ValueCaps, PUSHORT ValueCapsLength, PHIDP_PREPARSED_DATA PreparsedData) = 0;
NTSTATUS (__stdcall *HidP_GetButtonCaps) (HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData) = 0;
NTSTATUS (__stdcall *HidP_GetData) (HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength) = 0;
ULONG (__stdcall *HidP_MaxDataListLength) (HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData) = 0;
  
#include <string>
#undef INITGUID
#ifdef UNICODE
typedef struct _DEV_BROADCAST_HID
{
	DWORD			dbcc_size;
	DWORD			dbcc_devicetype;
	DWORD			dbcc_reserved;
	GUID			dbcc_classguid;
	wchar_t			dbcc_name[200];
}DEV_BROADCAST_HID;
#else
typedef struct _DEV_BROADCAST_HID
{
	DWORD			dbcc_size;
	DWORD			dbcc_devicetype;
	DWORD			dbcc_reserved;
	GUID			dbcc_classguid;
	char			dbcc_name[200];
}DEV_BROADCAST_HID;
#endif

I encapsulated this into conditional defined files to keep the whole thing multiplatform (Windows, Linux, Android). Defining those bindings by myself was neccessary because of the Windows Driver SDK just for working with that I and everyone else in my team needed to download and install this complicated to get piece of memory just to grab some header files.

You then need to first enumerate your devices using


uint32 count = 0; 
		GetRawInputDeviceList(0, (PUINT)&count, sizeof(RAWINPUTDEVICELIST));
		if(count <= 0) return false;

		Array<RAWINPUTDEVICELIST> devices(count, allocator);
		if((count = GetRawInputDeviceList((PRAWINPUTDEVICELIST)&devices[0], (PUINT)&count, sizeof(RAWINPUTDEVICELIST))) <= 0) 
			return false;

To know the targets for


RID_DEVICE_INFO _deviceInfo;
		_deviceInfo.cbSize = sizeof( RID_DEVICE_INFO );
		uint32 val_size = _deviceInfo.cbSize;

		GetRawInputDeviceInfo(device.handle, RIDI_DEVICEINFO, &_deviceInfo, (PUINT)&val_size));

and


RAWINPUTDEVICE rdv = RAWINPUTDEVICE();
		rdv.hwndTarget = (HWND)handle;
		rdv.usUsage = static_cast<USHORT>(target);
		rdv.usUsagePage = static_cast<USHORT>(extra);
		rdv.dwFlags = 0;

		return boolean_cast(RegisterRawInputDevices(&rdv, 1, sizeof(RAWINPUTDEVICE)));

To enable your window to get input events for the specified device(s).

Before you could handle what arrives in WM_INPUT, you need to find the protocol definition of your device. HID is a general tehcnic that routes information from the device to your program and vice versa so you need to know hot to communicate with your device. This a kind of protocol which defines the meaning of each single byte in your HID-Input-Report.

An XBox Gamepad has different alignment than one from Logitech so the protocol bytes handle that properly.


int32 val_size = 0; GetRawInputDeviceInfoA((HANDLE)*device, RIDI_DEVICENAME, 0, (PUINT)&val_size );
		if(val_size)
		{
			string path(device.GetAllocator());
			path.Resize(val_size);

			GetRawInputDeviceInfoA((HANDLE)*device, RIDI_DEVICENAME, path.Begin(), (PUINT)&val_size );
			*(path.Begin() + 1) = '\\';

			return CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
		}

				if(!HidD_GetPreparsedData(ntHandle, (PHIDP_PREPARSED_DATA*)&ind.protocol) && ind.Type == InputDevice::HID)
				{
					CloseHandle(ntHandle);
					continue;
				}

				CloseHandle(ntHandle);

First captures the size of the device path in driver cache and then grabs that path to open the HID-Protocl file. Those paths usually are in the form of HID\VID_046D&PID_C046\6&78d1573&0&0000 (my USB Mouse for example)

You will then obtain the protocol file using the GetPreparsedData call and anything is fine. Last step is getting input from the WM_INPUT message.


uint32 val_size = sizeof(RAWINPUT);
		RAWINPUT report; Drough::memset(&report, 0, sizeof(report));
		
		if(GetRawInputData((HRAWINPUT)data, RID_INPUT, &report, (PUINT)&val_size, sizeof(RAWINPUTHEADER)) < signed_cast(-1))
		{
			switch(report.header.dwType)
			{
                ...
                  case HID:
                {
                uint16 size = static_cast<uint16>(report.data.hid.dwSizeHid * report.data.hid.dwCount);
						
						if(raw.Size() != size) raw.Resize(size);
						Drough::memcpy(raw.Begin(), report.data.hid.bRawData, size);
                }
            }
        }
                                                                                                                           

This fills the fixed size data buffer (it is dynamic array in my case so don't wonder about raw.Resize()) from the msg.lParam part of WM_INPUT message.

Finally get the real HID input from the preparsed data and the protocol


HIDP_DATA* dta = (HIDP_DATA*)(raw.Begin() + raw.Offset());
		ULONG length = (ULONG)raw.Offset() / sizeof(HIDP_DATA);

if(HidP_GetData((HIDP_REPORT_TYPE)0, dta, &length, (PHIDP_PREPARSED_DATA)device.Protocol(), (PCHAR)raw.Begin(), (ULONG)raw.Size()) != HIDP_STATUS_SUCCESS || length == 0)
			return false;

And if anything was successfull, you'll end up with an array of HIDP_DATA objects that each contain information about the kind of input like Button/Axis and it's state [On/Off]/Delta.

Depending on how often you resize data, you'll end up with input latency of a few ms.

Hope this brief overview helps

3 hours ago, Shaarigan said:

Even if Microsoft suggest this, it isn't the practise we really want to do.

I agree on that. And I also don't see any direction Microsoft is heading to, there was DirectInput then XInput (not running on Win7 without hacks), Window messages etc. All is more or less deprecated. It is the same mess as for UI development for Windows, Win32 API, MFC, WinForms, WPF etc. Again all more or less deprecated, and Microsoft doesn't give any direction.

This is really an unfortunate situation.

Indeed. Being new to windows programming -- my world is in electronics, gnu/linux is usually the go-to. It's extremely overwhelming to sift through 20 years of API differences for the most up-to-date and compatible practices.

Many many thanks Shaarigan. That's a lot for me to go through but the best explanation I've gotten. I followed the MSDN method to no-avail as noted in the original post.

11 hours ago, trill41 said:

And I also don't see any direction Microsoft is heading to, there was DirectInput then XInput (not running on Win7 without hacks)

What doesn't work on Win7 without hacks?

XInput is still the standard way to communicate with XBox controllers on windows.
DirectInput is still the standard way to communicate with force feedback joysticks.

53 minutes ago, Hodgman said:

What doesn't work on Win7 without hacks?

XInput is still the standard way to communicate with XBox controllers on windows.
DirectInput is still the standard way to communicate with force feedback joysticks.

About Raw Input

  • 05/30/2018
  • 4 minutes to read

There are many user-input devices beside the traditional keyboard and mouse. For example, user input can come from a joystick, a touch screen, a microphone, or other devices that allow great flexibility in user input. These devices are collectively known as Human Interface Devices (HIDs). The raw input API provides a stable and robust way for applications to accept raw input from any HID, including the keyboard and mouse. https://docs.microsoft.com/en-us/windows/desktop/inputdev/about-raw-input

 

It's what microsoft is recommending and promoting.
The functions of xinput and directinput have been re-written and added to the windows header. Also for communicating with other user devices besides joysticks keyboards and mice. Which is my purposes.
I brought this into question here with game developers as it would be the "modern" way to do things and figured to find it more commonly used. Also necessary for writing drivers for new input devices communicating through the usb bus, such as a keyboard controller or similar ascii coming into the decoder. How does one receive and translate messages received by the decoder? Take that message, // do stuff with it. (Figurative question, though I'm sure there's a great answer)
 

1 hour ago, Hodgman said:

DirectInput is still the standard way to communicate with force feedback joysticks.

DirectInput was also for Keyboard and Mouse.

Forget what I wrote about XInput, XInput1_3 works on Win7, I must have been confused, sorry.

This topic is closed to new replies.

Advertisement