Reputation: 483
I am trying to modify an existing application by adding an input gathering thread outside of its main thread.
The original application already processes mouse input pretty decently inside its main loop, but it does so at a rate that's very slow for my project to properly work.
So I want to process mouse input outside the main thread on a very high rate, without interfering with the original application's input handling process at all? How can I do that? Can I register a mouse device and get corresponding WM_INPUT without preventing the original app from doing its own processing untouched?
Upvotes: 0
Views: 370
Reputation: 1441
You can register for Raw Input in a separate thread. But first you need to create invisible window in that thread. Also to receive input in that thread you need to provide RIDEV_INPUTSINK
to your RegisterRawInputDevices()
call.
Here is my code that doing that:
void RawInputDeviceManager::RawInputManagerImpl::ThreadRun()
{
m_WakeUpEvent = ::CreateEventExW(nullptr, nullptr, 0, EVENT_ALL_ACCESS);
CHECK(IsValidHandle(m_WakeUpEvent));
HINSTANCE hInstance = ::GetModuleHandleW(nullptr);
m_hWnd = ::CreateWindowExW(0, L"Static", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, hInstance, 0);
CHECK(IsValidHandle(m_hWnd));
SUBCLASSPROC subClassProc = [](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR /*uIdSubclass*/, DWORD_PTR dwRefData) -> LRESULT
{
auto manager = reinterpret_cast<RawInputManagerImpl*>(dwRefData);
CHECK(manager);
switch (uMsg)
{
case WM_CHAR:
{
wchar_t ch = LOWORD(wParam);
DBGPRINT("WM_CHAR: `%s` (U+%04X %s)\n", GetUnicodeCharacterForPrint(ch).c_str(), ch, GetUnicodeCharacterName(ch).c_str());
return 0;
}
case WM_INPUT_DEVICE_CHANGE:
{
CHECK(wParam == GIDC_ARRIVAL || wParam == GIDC_REMOVAL);
HANDLE deviceHandle = reinterpret_cast<HANDLE>(lParam);
bool isConnected = (wParam == GIDC_ARRIVAL);
manager->OnInputDeviceConnected(deviceHandle, isConnected);
return 0;
}
case WM_INPUT:
{
HRAWINPUT dataHandle = reinterpret_cast<HRAWINPUT>(lParam);
manager->OnInputMessage(dataHandle);
return 0;
}
}
return ::DefSubclassProc(hWnd, uMsg, wParam, lParam);
};
CHECK(::SetWindowSubclass(m_hWnd, subClassProc, 0, reinterpret_cast<DWORD_PTR>(this)));
CHECK(Register());
// enumerate devices before start
EnumerateDevices();
// main message loop
while (m_Running)
{
// wait for new messages
::MsgWaitForMultipleObjectsEx(1, &m_WakeUpEvent, INFINITE, QS_ALLEVENTS, MWMO_INPUTAVAILABLE);
MSG msg;
while (::PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
m_Running = false;
break;
}
::TranslateMessage(&msg);
::DispatchMessageW(&msg);
}
}
CHECK(Unregister());
CHECK(::RemoveWindowSubclass(m_hWnd, subClassProc, 0));
CHECK(::DestroyWindow(m_hWnd));
m_hWnd = nullptr;
}
bool RawInputDeviceManager::RawInputManagerImpl::Register()
{
RAWINPUTDEVICE rid[] =
{
{
HID_USAGE_PAGE_GENERIC,
0,
RIDEV_DEVNOTIFY | RIDEV_INPUTSINK | RIDEV_PAGEONLY,
m_hWnd
}
};
return ::RegisterRawInputDevices(rid, static_cast<UINT>(std::size(rid)), sizeof(RAWINPUTDEVICE));
}
You even can make WM_CHAR to work in your thread by posting WM_KEYDOWN
from WM_INPUT
keyboard messages:
void RawInputDeviceManager::RawInputManagerImpl::OnKeyboardEvent(const RAWKEYBOARD& keyboard) const
{
if (keyboard.VKey >= 0xff/*VK__none_*/)
return;
// Sync keyboard layout with parent thread
HKL keyboardLayout = ::GetKeyboardLayout(m_ParentThreadId);
if (keyboardLayout != m_KeyboardLayout)
{
m_KeyboardLayout = keyboardLayout;
// This will post WM_INPUTLANGCHANGE
::ActivateKeyboardLayout(m_KeyboardLayout, 0);
}
// To be able to receive WM_CHAR in our thread we need WM_KEYDOWN/WM_KEYUP messages.
// But we wouldn't have them in invisible unfocused window that we have there.
// Just emulate them from RawInput message manually.
uint16_t keyFlags = LOBYTE(keyboard.MakeCode);
if (keyboard.Flags & RI_KEY_E0)
keyFlags |= KF_EXTENDED;
if (keyboard.Message == WM_SYSKEYDOWN || keyboard.Message == WM_SYSKEYUP)
keyFlags |= KF_ALTDOWN;
if (keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP)
keyFlags |= KF_REPEAT;
if (keyboard.Flags & RI_KEY_BREAK)
keyFlags |= KF_UP;
::PostMessageW(m_hWnd, keyboard.Message, keyboard.VKey, MAKELONG(1/*repeatCount*/, keyFlags));
}
Another more common approach is to push WM_INPUT
events in queue (possibly lockless) and process them in some input worker thread that could emit input events etc to other parts of your program.
Upvotes: 0