Недавно я в течение своего ограниченного свободного времени экспериментировал с небольшим проектом, чтобы попытаться получить больше опыта и понимания с C ++, но я столкнулся с препятствиями в своей текущей программе:
Я пытаюсь создать глобальный низкоуровневый слушатель мыши с помощью хука окон, что в большинстве случаев кажется довольно простым. Тем не мение, определить, какая кнопка мыши была нажата (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;
}
Пожалуйста, прочитайте документацию:
Функция обратного вызова 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
:
Содержит информацию о низкоуровневом событии ввода мышью.
[…]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 Первый Х
Кнопка была нажата или отпущена.
XBUTTON2
0x0002 Второй Х
Кнопка была нажата или отпущена.
Таким образом, упрощенная версия вашего обратного вызова может выглядеть так:
#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 + С.
Чтобы убедиться 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);
}
Других решений пока нет …