我可以将原始鼠标输入作为 WM_INPUT 两次吗?

发布于 2025-01-20 09:54:54 字数 186 浏览 4 评论 0原文

我正在尝试通过在其主线程之外添加输入收集线程来修改现有应用程序。

原始应用程序已经在其主循环内处理了鼠标输入,但它以我项目正确工作的速度非常慢。

因此,我想以很高的速率处理主线程外部的鼠标输入,而不会干扰原始应用程序的输入处理过程?我该怎么做?我可以注册鼠标设备并获取相应的WM_INPUT,而无需阻止原始应用程序执行自己的处理?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

只为守护你 2025-01-27 09:54:54

您可以在单独的线程中注册原始输入。但首先您需要在该线程中创建不可见窗口。另外,要在该线程中接收输入,您需要向 RegisterRawInputDevices() 调用提供 RIDEV_INPUTSINK

这是我的代码:

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));
}

你甚至可以使WM_CHAR 通过从 WM_INPUT 键盘消息发布 WM_KEYDOWN 来在您的线程中工作:

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));
}

另一种更常见的方法是推送队列中的 WM_INPUT 事件(可能是无锁的)并在某些输入工作线程中处理它们,该线程可以向程序的其他部分发出输入事件等。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文