DJm00n
DJm00n

Reputation: 1441

WM_INPUT_DEVICE_CHANGE messages get lost when reading RAWINPUT via GetRawInputBuffer

I trying to write program that is working properly with GetRawInputBuffer API and consuming all input with little overhead on separate thread.

But I found that WM_INPUT_DEVICE_CHANGE messages get lost when reading RAWINPUT via GetRawInputBuffer instead of usual WM_INPUT approach.

My code (I removed some error checking):

void RawInputDeviceManager::RawInputManagerImpl::ThreadRun()
{
    // if I set it to true then WM_INPUT_DEVICE_CHANGE does not come
    constexpr bool buffered = false;

    // prepare buffer for up to 32 raw input messages
    m_InputDataBuffer.resize(std::max({ sizeof(RAWKEYBOARD), sizeof(RAWMOUSE), sizeof(RAWHID) }) * 32);

    m_WakeUpEvent = ::CreateEventExW(nullptr, nullptr, 0, EVENT_ALL_ACCESS);

    WNDCLASSEXW wc{};
    wc.cbSize = sizeof(wc);
    wc.lpszClassName = L"Message";
    wc.lpfnWndProc = [](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -> LRESULT
    {
        RawInputManagerImpl* manager = reinterpret_cast<RawInputManagerImpl*>(::GetWindowLongPtrW(hWnd, 0));

        if (manager)
        {
            switch (message)
            {
            case WM_INPUT_DEVICE_CHANGE:  // <== get lost after ::GetRawInputBuffer(..) call
            {
                manager->OnInputDeviceChange();
                return 0;
            }
            case WM_INPUT:
            {
                manager->OnInput(reinterpret_cast<RAWINPUT*>(lParam));
                return 0;
            }
            }
        }

        return ::DefWindowProcW(hWnd, message, wParam, lParam);
    };
    wc.cbWndExtra = sizeof(RawInputManagerImpl*); // add some space for this pointer
    wc.hInstance = ::GetModuleHandleW(nullptr);

    ATOM classAtom = ::RegisterClassExW(&wc);

    HWND hWnd = ::CreateWindowExW(0, reinterpret_cast<LPCWSTR>(classAtom), nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, wc.hInstance, 0);

    ::SetWindowLongPtrW(hWnd, 0, reinterpret_cast<LONG_PTR>(this));

    Register(hWnd);

    // enumerate devices before start
    OnInputDeviceChange();

    // main message loop
    while (m_Running)
    {
        MSG msg;

        if (buffered)
            OnInputBuffered();

        while (true)
        {
            bool haveMessage = false;

            if (buffered)
            {
                // retrieve any message but WM_INPUT
                haveMessage = ::PeekMessageW(&msg, 0, 0, WM_INPUT - 1, PM_REMOVE) ||
                    ::PeekMessageW(&msg, 0, WM_INPUT + 1, 0, PM_REMOVE);
            }
            else
            {
                // retrieve any message
                haveMessage = ::PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);
            }

            if (!haveMessage)
                break;

            // not needed since we are not interested in WM_CHAR or WM_DEADCHAR
            //::TranslateMessage(&msg);

            // dispatch message to WndProc
            ::DispatchMessageW(&msg);
        }

        // wait for new messages
        ::MsgWaitForMultipleObjectsEx(1, &m_WakeUpEvent, INFINITE, QS_ALLEVENTS, MWMO_INPUTAVAILABLE);
    }

    Unregister();
    ::DestroyWindow(hWnd);
    ::UnregisterClassW(reinterpret_cast<LPCWSTR>(classAtom), wc.hInstance);
}

bool RawInputDeviceManager::RawInputManagerImpl::Register(HWND hWnd)
{
    RAWINPUTDEVICE rid[] =
    {
        // register for all HID device generic types (keyboard/mouse/joystick etc)
        {
            HID_USAGE_PAGE_GENERIC,
            0,
            RIDEV_DEVNOTIFY | RIDEV_INPUTSINK | RIDEV_PAGEONLY,
            hWnd
        }
    };

    return ::RegisterRawInputDevices(rid, ARRAYSIZE(rid), sizeof(RAWINPUTDEVICE));
}

void RawInputDeviceManager::RawInputManagerImpl::OnInputBuffered()
{
    // Processing all pending WM_INPUT messages in message queue
    while (true)
    {
        UINT size = static_cast<UINT>(m_InputDataBuffer.size());
        RAWINPUT* input = reinterpret_cast<RAWINPUT*>(m_InputDataBuffer.data());

        UINT result = ::GetRawInputBuffer(input, &size, sizeof(RAWINPUTHEADER));

        if (result == 0 || result == static_cast<UINT>(-1))
            return;

        // hack for a undefined QWORD used in NEXTRAWINPUTBLOCK macro
        using QWORD = __int64;

        for (; result; result--, input = NEXTRAWINPUTBLOCK(input))
        {
            OnInput(input);
        }
    }
}

Is this bug in Windows?

PS: This WM_INPUT_DEVICE_CHANGE event was added in Windows Vista and have proven that it has some bugs in its implementation.

PPS: As a workaround I can subscribe to WM_DEVICECHANGE message via RegisterDeviceNotification but I not sure if I doing something wrong in this case.

Upvotes: 1

Views: 354

Answers (0)

Related Questions