C++ Идентификация кнопок X и направления колес прокрутки

Недавно я в течение своего ограниченного свободного времени экспериментировал с небольшим проектом, чтобы попытаться получить больше опыта и понимания с C++, но я столкнулся с препятствиями в своей текущей программе:

Я пытаюсь создать глобальный низкоуровневый слушатель мыши с помощью хука окон, что в большинстве случаев кажется довольно простым. Однако определение, какая кнопка мыши X была нажата (MB4 или MB5) и в каком направлении вращалось колесо прокрутки, вызывает у меня сильную головную боль.

Согласно документам Microsoft, текущий способ, которым я пытаюсь определить нажатие соответствующей кнопки X и направление колеса прокрутки, является правильным, но моя реализация этого не работает.

Мне удалось найти одно рабочее решение проблемы кнопки X ( последнее сообщение о сегменте кода в этой ветке форума), но это выглядит как перепрыгивание ненужных обручей, когда сегмент кода 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;
}

1 ответ

Решение

Пожалуйста, прочитайте документацию:

Функция обратного вызова LowLevelMouseProc:

[...]

wParam [в]
Тип: WPARAM
Идентификатор сообщения мыши. Этот параметр может быть одним из следующих сообщений:
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_RBUTTONDOWN, или же WM_RBUTTONUP,

lParam [в]
Тип: LPARAM
Указатель на MSLLHOOKSTRUCT состав.

Так wParam может быть WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_RBUTTONDOWN, или же WM_RBUTTONUP, Нет никакого волшебного способа получить больше информации из этого. И если бы это было так, это было бы недокументировано и этого следует избегать.

lParam Однако указывает на MSLLHOOKSTRUCT:

Структура tagMSLLHOOKSTRUCT:

Содержит информацию о низкоуровневом событии ввода мышью.

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

[...]

mouseData
Тип: DWORD

Если сообщение WM_MOUSEWHEELстаршее слово этого члена - дельта колеса. Слово младшего разряда зарезервировано. Положительное значение указывает на то, что колесо было повернуто вперед от пользователя; отрицательное значение указывает, что колесо было повернуто назад, к пользователю. Один щелчок колеса определяется как WHEEL_DELTA, что составляет 120.

Если сообщение WM_XBUTTONDOWN, WM_XBUTTONUP, WM_XBUTTONDBLCLK, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP, или же WM_NCXBUTTONDBLCLKстаршее слово указывает, какая кнопка X была нажата или отпущена, а младшее слово зарезервировано. Это значение может быть одним или несколькими из следующих значений. Иначе, mouseData не используется

Значение Значение
XBUTTON1 0x0001 Первая кнопка X была нажата или отпущена.
XBUTTON2 0x0002 Вторая кнопка X была нажата или отпущена.

Таким образом, упрощенная версия вашего обратного вызова может выглядеть так:

#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():

Поскольку в вашем приложении нет окон, ему не будут отправляться сообщения и GetMessage() никогда не вернется. Это делает сообщение насосом бесполезным. Один звонок GetMessage() достаточно, чтобы дать Windows возможность вызвать установленный обратный вызов ловушки. Однако проблема в том, что этот код после вызова GetMessage() никогда не будет выполнен, потому что единственные способы завершить программу - закрыть окно или нажать 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);
}
Другие вопросы по тегам