Reputation: 1441
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