C++ 识别 X 按钮和滚轮方向

C++ Identifying X Buttons & Scroll Wheel Directions

我最近在有限的空闲时间里尝试了一个小项目,试图获得更多的 C++ 经验和理解,但我在当前的程序中遇到了障碍:

我正在尝试使用 windows 挂钩创建一个全局低级鼠标侦听器,大多数事情看起来相当简单。但是,确定单击了哪个 X 鼠标按钮(MB4 或 MB5)和滚动滚轮的方向让我很头疼.

根据 Microsoft 文档,我目前尝试确定合适 X button clicked and scroll wheel direction 的方法是正确的,但我的实施方式不起作用。

我已经找到了解决 X 按钮问题的有效解决方案 (the last code segment post in this forum thread),但当 Microsoft 代码段更清晰并且 时,这似乎有点像跳过不必要的圈套应该 工作。

虽然C++不是我最熟悉的语言,但我想继续学习它并经常使用它。我希望我只是犯了一个简单的错误,因为这是我第一次使用 Windows 钩子。提前感谢您提供任何建议或帮助!

#include <iostream>
#include <windows.h>

static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) 
{
    if(nCode >= 0)
    {
        switch(wParam)
        {
            case WM_LBUTTONDOWN:
                system("CLS");
                std::cout << "left mouse button down\n";
                break;
            case WM_LBUTTONUP:
                std::cout << "left mouse button up\n";
                break;
            case WM_RBUTTONDOWN:
                system("CLS");
                std::cout << "right mouse button down\n";
                break;
            case WM_RBUTTONUP:
                std::cout << "right mouse button up\n";
                break;
            case WM_MBUTTONDOWN:
                system("CLS");
                std::cout << "middle mouse button down\n";
                break;
            case WM_MBUTTONUP:
                std::cout << "middle mouse button up\n";
                break;
            case WM_MOUSEWHEEL:
                if(GET_WHEEL_DELTA_WPARAM(wParam) > 0)
                    std::cout << "mouse wheel scrolled up\n";
                else if(GET_WHEEL_DELTA_WPARAM(wParam) < 0)
                    std::cout << "mouse wheel scrolled down\n";
                else //always goes here
                    std::cout << "unknown mouse wheel scroll direction\n";
                break;
            case WM_XBUTTONDOWN:
                system("CLS");
                if(GET_XBUTTON_WPARAM(wParam) == XBUTTON1)
                    std::cout << "X1 mouse button down\n";
                else if(GET_XBUTTON_WPARAM(wParam) == XBUTTON2)
                    std::cout << "X2 mouse button down\n";
                else //always goes here
                    std::cout << "unknown X mouse button down\n";
                break;
            case WM_XBUTTONUP:
                if(GET_XBUTTON_WPARAM(wParam) == XBUTTON1)
                    std::cout << "X1 mouse button up\n";
                else if(GET_XBUTTON_WPARAM(wParam) == XBUTTON2)
                    std::cout << "X2 mouse button up\n";
                else //always goes here
                    std::cout << "unknown X mouse button up\n";
                break;
        }
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main()
{
    HHOOK mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, NULL, 0);
    MSG msg;

    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    UnhookWindowsHookEx(mouseHook);
    return 0;
}

请阅读文档:

LowLevelMouseProc callback function:

[...]

wParam [in]
Type: WPARAM
The identifier of the mouse message. This parameter can be one of the following messages:
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_RBUTTONDOWN, or WM_RBUTTONUP.

lParam [in]
Type: LPARAM
A pointer to an MSLLHOOKSTRUCT structure.

因此 wParam 可以是 WM_LBUTTONDOWNWM_LBUTTONUPWM_MOUSEMOVEWM_MOUSEWHEELWM_MOUSEHWHEELWM_RBUTTONDOWNWM_RBUTTONUP。没有神奇的方法可以从中获取更多信息。如果有的话,它将是无证的,应该避免。

lParam 但是指向 MSLLHOOKSTRUCT:

tagMSLLHOOKSTRUCT structure:

Contains information about a low-level mouse input event.

typedef struct tagMSLLHOOKSTRUCT {
  POINT     pt;
  DWORD     mouseData;
  DWORD     flags;
  DWORD     time;
  ULONG_PTR dwExtraInfo;
} MSLLHOOKSTRUCT, *LPMSLLHOOKSTRUCT, *PMSLLHOOKSTRUCT;

[...]

mouseData
Type: DWORD

If the message is WM_MOUSEWHEEL, the high-order word of this member is the wheel delta. The low-order word is reserved. A positive value indicates that the wheel was rotated forward, away from the user; a negative value indicates that the wheel was rotated backward, toward the user. One wheel click is defined as WHEEL_DELTA, which is 120.

If the message is WM_XBUTTONDOWN, WM_XBUTTONUP, WM_XBUTTONDBLCLK, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP, or WM_NCXBUTTONDBLCLK, the high-order word specifies which X button was pressed or released, and the low-order word is reserved. This value can be one or more of the following values. Otherwise, mouseData is not used.

Value Meaning
XBUTTON1 0x0001 The first X button was pressed or released.
XBUTTON2 0x0002 The second X button was pressed or released.

所以您的回调的简化版本可能如下所示:

#include <iostream>
#include <type_traits> // std::make_signed_t<>

#include <windows.h>

LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode != HC_ACTION)  // Nothing to do :(
        return CallNextHookEx(NULL, nCode, wParam, lParam);


    MSLLHOOKSTRUCT *info = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);

    char const *button_name[] = { "Left", "Right", "Middle", "X" };
    enum { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_XBUTTON, BTN_NONE } button = BTN_NONE;

    char const *up_down[] = { "up", "down" };
    bool down = false;


    switch (wParam)
    {

    case WM_LBUTTONDOWN: down = true;
    case WM_LBUTTONUP: button = BTN_LEFT;
        break;
    case WM_RBUTTONDOWN: down = true;
    case WM_RBUTTONUP: button = BTN_RIGHT;
        break;
    case WM_MBUTTONDOWN: down = true;
    case WM_MBUTTONUP: button = BTN_MIDDLE;
        break;
    case WM_XBUTTONDOWN: down = true;
    case WM_XBUTTONUP: button = BTN_XBUTTON;
        break;

    case WM_MOUSEWHEEL:
        // the hi order word might be negative, but WORD is unsigned, so
        // we need some signed type of an appropriate size:
        down = static_cast<std::make_signed_t<WORD>>(HIWORD(info->mouseData)) < 0;
        std::cout << "Mouse wheel scrolled " << up_down[down] << '\n';
        break;
    }

    if (button != BTN_NONE) {
        std::cout << button_name[button];
        if (button == BTN_XBUTTON)
            std::cout << HIWORD(info->mouseData);
        std::cout << " mouse button " << up_down[down] << '\n';
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

关于你的main()

由于您的应用程序没有 windows,因此不会向其发送任何消息,并且 GetMessage() 永远不会 return。这使消息泵变得无用。对 GetMessage() 的一次调用足以让 Windows 有机会调用已安装的挂钩回调。但问题是,调用 GetMessage() 之后的代码将永远不会执行,因为结束程序的唯一方法是关闭 window 或按 Ctrl + C.

为了确保 UnhookWindowsHookEx() 被调用,我建议设置一个 ConsoleCtrlHandler:

HHOOK hook = NULL;

BOOL WINAPI ctrl_handler(DWORD dwCtrlType)
{
    if (hook) {
        std::cout << "Unhooking " << hook << '\n';
        UnhookWindowsHookEx(hook);
        hook = NULL;  // ctrl_handler might be called multiple times
        std::cout << "Bye :(";
        std::cin.get();  // gives the user 5 seconds to read our last output
    }

    return TRUE;
}

int main()
{
    SetConsoleCtrlHandler(ctrl_handler, TRUE);
    hook = SetWindowsHookExW(WH_MOUSE_LL, MouseHookProc, nullptr, 0);

    if (!hook) {
        std::cerr << "SetWindowsHookExW() failed. Bye :(\n\n";
        return EXIT_FAILURE;
    }

    std::cout << "Hook set: " << hook << '\n';
    GetMessageW(nullptr, nullptr, 0, 0);
}