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_LBUTTONDOWN
、WM_LBUTTONUP
、WM_MOUSEMOVE
、WM_MOUSEWHEEL
、WM_MOUSEHWHEEL
、WM_RBUTTONDOWN
或WM_RBUTTONUP
。没有神奇的方法可以从中获取更多信息。如果有的话,它将是无证的,应该避免。
lParam
但是指向 MSLLHOOKSTRUCT
:
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);
}
我最近在有限的空闲时间里尝试了一个小项目,试图获得更多的 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
, orWM_RBUTTONUP
.
lParam
[in]
Type:LPARAM
A pointer to anMSLLHOOKSTRUCT
structure.
因此 wParam
可以是 WM_LBUTTONDOWN
、WM_LBUTTONUP
、WM_MOUSEMOVE
、WM_MOUSEWHEEL
、WM_MOUSEHWHEEL
、WM_RBUTTONDOWN
或WM_RBUTTONUP
。没有神奇的方法可以从中获取更多信息。如果有的话,它将是无证的,应该避免。
lParam
但是指向 MSLLHOOKSTRUCT
:
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 asWHEEL_DELTA
, which is 120.If the message is
WM_XBUTTONDOWN
,WM_XBUTTONUP
,WM_XBUTTONDBLCLK
,WM_NCXBUTTONDOWN
,WM_NCXBUTTONUP
, orWM_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);
}