简体   繁体   中英

Can I get raw mouse input as a WM_INPUT twice?

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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM