Close

Looking at RAWINPUT for more detail

A project log for Cheap Windows Jog/keyboard controller for CNCs

Cheap <$5 usb numpad to use for key macros and CNC job controllers. Primarily I'm using it to extend a remote for jogging around my G0704

charliexcharliex 04/23/2015 at 15:560 Comments

So in the previous example, it is a system wide low level keyboard hook that can change or remove key events from everything that generates output ( though some apps can bypass it) but what it doesn't tell us is where it came from. We need RAWINPUT for this. This will listen to nearly all the RAWINPUT's system wide.

Make a normal WIN32 project , make these changes.

i'll put these as complete examples on GitHub

Add crtdbg.h to stdafx.h for the debug macros.

Make InitInstance

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
//        PostQuitMessage wont get the exit code out, since no pump, use debug RPT since no window yet.
//
BOOL InitInstance ( HINSTANCE hInstance, int nCmdShow )
{
    HWND hWnd;

    hInst = hInstance; // Store instance handle in our global variable

    hWnd = CreateWindow ( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL );

    if ( !hWnd ) {
        return FALSE;
    }

    // raw device count
    UINT nDevices = 0;

    // used for result code
    INT nResult;

    // get number of raw devices, including fake ones for RDP etc, this allows  us
    // to allocate the right amount of memory, since we're only asking for how many are attached.
    nResult = GetRawInputDeviceList ( NULL, &nDevices, sizeof ( RAWINPUTDEVICELIST ) );

    // this would be bad. possibly permissions.
    if ( nResult < 0 ) {

        _RPT1 ( _CRT_WARN, "failed to get raw input device list  %d\n", GetLastError() );
        PostQuitMessage ( 4 );
        return FALSE;
    }

    _RPT1 ( _CRT_WARN,   "number of raw devices = %d\n", nDevices );

    // uhhh.
    if ( nDevices == 0 ) { PostQuitMessage ( 1 ); return FALSE; }

    // allocate enough memory for all the RAW devices reported

    pRawInputDeviceList = new RAWINPUTDEVICELIST[sizeof ( RAWINPUTDEVICELIST ) * nDevices];

    // failed ?
    if ( pRawInputDeviceList == NULL ) {

        _RPT1 ( _CRT_WARN, "failed to allocate memory for raw device list %d\n", GetLastError() );
        PostQuitMessage ( 2 );
        return FALSE;
    }

    // zero it
    memset ( pRawInputDeviceList, 0, sizeof ( RAWINPUTDEVICELIST ) * nDevices );

    // now get the actual list of raw devices
    GetRawInputDeviceList ( pRawInputDeviceList, &nDevices, sizeof ( RAWINPUTDEVICELIST ) );

    // failed ?
    if ( nDevices < 0 ) {

        // Clean Up
        delete[] pRawInputDeviceList;

        _RPT1 ( _CRT_WARN, "failed to get list of raw devices %d\n", GetLastError() );
        PostQuitMessage ( 3 );
        return FALSE;
    }


    for ( UINT i = 0; i < nDevices; i++ ) {

        // how much data do we need to allocate for the device name ?
        UINT nDeviceBufferLength = 0;

        nResult = GetRawInputDeviceInfo ( pRawInputDeviceList[i].hDevice,
                                          RIDI_DEVICENAME,
                                          NULL,
                                          &nDeviceBufferLength );

        if ( nResult < 0 ) {

            _RPT1 ( _CRT_WARN, "failed to get raw input device name length %d\n", GetLastError() );

            continue;
        }

        // allocate memory for device name (wide char)
        WCHAR* wcDeviceName = new WCHAR[nDeviceBufferLength + 1];

        // failed?
        if ( wcDeviceName == NULL ) {

            delete[] pRawInputDeviceList;
            _RPT1 ( _CRT_WARN, "failed to allocate memory for raw device names %d\n", GetLastError() );
            PostQuitMessage ( 5 );
            return FALSE;

        }

        // get the RAW device name
        nResult = GetRawInputDeviceInfo ( pRawInputDeviceList[i].hDevice,
                                          RIDI_DEVICENAME,
                                          wcDeviceName,
                                          &nDeviceBufferLength );

        // failed?
        if ( nResult < 0 ) {

            _RPT1 ( _CRT_WARN, "failed to get actual raw device name %d\n", GetLastError() );


            // free the memory for the name
            delete[] wcDeviceName;


            // skip this one
            continue;
        }

        // setup buffer to receive device info
        RID_DEVICE_INFO ridDeviceInfo;
        ridDeviceInfo.cbSize = sizeof ( RID_DEVICE_INFO );

        // this tells the api how big the structure is
        nDeviceBufferLength = ridDeviceInfo.cbSize;

        // fetch the device info for this raw device
        nResult = GetRawInputDeviceInfo ( pRawInputDeviceList[i].hDevice,
                                          RIDI_DEVICEINFO,
                                          &ridDeviceInfo,
                                          &nDeviceBufferLength );

        // failed?
        if ( nResult < 0 ) {

            _RPT1 ( _CRT_WARN, "didn't get raw input device info %d\n", GetLastError() );

            // skip to next device
            continue;

        } else

            // is it a keyboard?
            if ( ridDeviceInfo.dwType == RIM_TYPEKEYBOARD ) {

                _RPT2 ( _CRT_WARN, "device handle 0x%x (%d)\ndevice name ", pRawInputDeviceList[i].hDevice, i + 1 );

                // wchar type
                OutputDebugStringW (  wcDeviceName );

                _RPT1 ( _CRT_WARN, "dwKeyboardMode %d\n" , ridDeviceInfo.keyboard.dwKeyboardMode );
                _RPT1 ( _CRT_WARN, "dwNumberOfFunctionKeys %d\n" , ridDeviceInfo.keyboard.dwNumberOfFunctionKeys );
                _RPT1 ( _CRT_WARN, "dwNumberOfIndicators %d\n" , ridDeviceInfo.keyboard.dwNumberOfIndicators );
                _RPT1 ( _CRT_WARN, "dwNumberOfKeysTotal %d\n" , ridDeviceInfo.keyboard.dwNumberOfKeysTotal );
                _RPT1 ( _CRT_WARN, "dwType %d\n" , ridDeviceInfo.keyboard.dwType );
                _RPT1 ( _CRT_WARN, "dwSubType %d\n" , ridDeviceInfo.keyboard.dwSubType );
            }

        // don't need this anymore
        delete[] wcDeviceName;

        wcDeviceName = NULL ;

    }

    // setup the global RAWINPUT device

    ZeroMemory ( &rawInputDevice, sizeof ( rawInputDevice ) );

    /// ignore legacy messages, and
    // RIDEV_INPUTSINK = If set, this enables the caller to receive the input even when
    // the caller is not in the foreground. Note that hwndTarget must be specified

    rawInputDevice.dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK;

    //  https://msdn.microsoft.com/en-us/library/ff543477.aspx
    rawInputDevice.usUsagePage = HID_USAGE_PAGE_GENERIC;

    rawInputDevice.usUsage = HID_USAGE_GENERIC_KEYBOARD;

    // needs to know where to send the message, when using RIDEV_INPUTSINK 
    rawInputDevice.hwndTarget = hWnd;

    // we want WM_INPUT's
    nResult = RegisterRawInputDevices ( &rawInputDevice, 1, sizeof ( rawInputDevice ) );

    // failed?
    if ( nResult == FALSE ) {
        _RPT1 ( _CRT_WARN, "register raw input devices failed %d\n", GetLastError() );
    }

    ShowWindow ( hWnd, nCmdShow );
    UpdateWindow ( hWnd );

    return TRUE;
}

when run it'll scan through all the RAWINPUT devices and list out, via the debugger, the id/guid for them , as well as any details on the device itself, we're only looking at keyboards and you'll also see any virtual keyboards listed. It then registers for RAWINPUT messages with RegisterRawInputDevices. This requires a change to WndProc

Add this to the message switch

        case WM_INPUT:

            // wParam is either RIM_INPUT (this app foreground) or RIM_INPUTSINK (this app background)
            // lParam is the RAWINPUT handle

            // determine size of buffer
            if ( GetRawInputData ( ( HRAWINPUT ) lParam,
                                   RID_INPUT, NULL, &dwSize, sizeof ( RAWINPUTHEADER ) ) == -1 ) {
                break;
            }

            LPBYTE lpb ;

            // allocate it
            lpb = new BYTE[dwSize];

            if ( lpb == NULL ) {
                break;
            }

            ZeroMemory ( lpb, dwSize );

            // get actual data
            if ( GetRawInputData ( ( HRAWINPUT ) lParam,
                                   RID_INPUT, lpb, &dwSize, sizeof ( RAWINPUTHEADER ) ) != dwSize ) {
                delete[] lpb;
                break;
            }

            // process it
            {

                PRAWINPUT pRaw = ( PRAWINPUT ) lpb;
                WCHAR wcTextBuffer[512];
                UINT keyChar = MapVirtualKey ( pRaw->data.keyboard.VKey, MAPVK_VK_TO_CHAR );

                wsprintf ( wcTextBuffer,
                           TEXT (
                               "Type=%d\nDevice=0x%x\nMakeCode=0x%x\nFlags=0x%x\nReserved=0x%x\nExtraInformation=0x%x\nMessage=0x%x\nVKey=0x%x\nEvent=0x%x\nkeyChar=0x%x\n\n"
                           ),

                           /// device header
                           pRaw->header.dwType,

                           // device handle, pass this to GetRawInputDeviceInfo
                           pRaw->header.hDevice,

                           pRaw->data.keyboard.MakeCode,
                           pRaw->data.keyboard.Flags,
                           pRaw->data.keyboard.Reserved,
                           pRaw->data.keyboard.ExtraInformation,
                           pRaw->data.keyboard.Message,
                           pRaw->data.keyboard.VKey,
                           keyChar );

                OutputDebugStringW ( wcTextBuffer );
            }

            // not needed
            delete[] lpb;

            break;

This grabs all the RAWINPUT events we registered before and dumps them to the debugger window.

So this shows the underlying RAWINPUT and gives us much more information, we can see which device the input came from, but unfortunately we can't change it easily here. pRaw->data.hDevice contains the handle to the device the message came from, so we can filter by device. .Useful for looking at individual RAW device output

MSDN is an amazing resource for Windows programming

Using Raw Input

Discussions