近日要实现远程控制,需要捕获本地鼠标信息,传输到远程计算机。
鼠标事件,无非是WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE(就说这基本的三个命令吧),开始以为很容易获取这些事件,但在实现过程中,并不是想象中的那么简单:
上面的确实现了三个基本命令,但存在这样一个问题:即鼠标的当前位置离开这个应用程序时(对话框界面),这些鼠标数据就无法捕获。
为什么会有这种应用呢?比如在应用程序中点击某个按钮,打开了一个CMD命令行控制台窗口(验证与对方能否Ping通),弹出的CMD窗口上就无法捕获这些鼠标数据;
上面说的为什么在“在【CWinApp】中的PreTranslateMessage更好一些”?是说如果要编写的应用程序假若有多个Dialog,岂不是要给每一个窗口写一个PreTranslateMessage!
② 怎么在“整个系统”中获取鼠标?想到了DriectX中的DriectInput,和鼠标钩子。先说DriectInput,下面是在定时器或者线程中获取鼠标数据的一段:(初始化部分就不粘贴了)
用DirectInput有几个局限,一是如上程序看到,鼠标移动中给出的是“相对位置”,而不是“绝对位置”(两者如何在DirectInput中转换,我没有试出来),不得不还是使用Win32中的::GetCursorPos;二是只能判断鼠标“点击按下”,无法识别鼠标按键“抬起”,如上程序,自己模拟了在点击后延时2ms后发生“抬起”事件,但这样并不是用户在操作中的真正行为;
③ 继续说鼠标低级钩子:用的是WH_MOUSE_LL,对应的挂接函数为LowLevelMouseProc,好处是不用单独写一个DLL库,直接在应用程序中使用即可;
鼠标低级钩子是一个全局的,只要安装钩子成功,在整个系统中都是有效的,基本解决了这个问题。
综上,决定使用鼠标低级钩子。
鼠标事件,无非是WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE(就说这基本的三个命令吧),开始以为很容易获取这些事件,但在实现过程中,并不是想象中的那么简单:
① 在基于MFC中的对话框应用程序中,可以在 PreTranslateMessage 中获取(【主对话框】的或者是【CWinApp】的,应该说放在【CWinApp】中的PreTranslateMessage更好一些),如同下面:
BOOL CTestApp::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_LBUTTONDOWN)//左键按下
{
if(m_bSend)
{
CPoint pt;
GetCursorPos(&pt);
memset(szMsg,'\0',sizeof(szMsg));
sprintf(szMsg,"WM_LBD;%d;%d;%d;0;\0",pt.x, pt.y, keyFlags);
LanSend(szMsg, strlen(szMsg));
}
}
else if (pMsg->message == WM_LBUTTONUP)//左键抬起
{
if(m_bSend)
{
CPoint pt;
GetCursorPos(&pt);
memset(szMsg,'\0',sizeof(szMsg));
sprintf(szMsg,"WM_LBU;%d;%d;%d;0;\0",pt.x, pt.y, keyFlags);
LanSend(szMsg, strlen(szMsg));
}
}
else if (pMsg->message == WM_MOUSEMOVE) //光标移动
{
if(m_bSend)
{
POINT pt;
::GetCursorPos(&pt);
memset(szMsg,'\0',sizeof(szMsg));
//sprintf(szMsg,"WM_MM;%d;%d;%d;0;\0", GET_X_LPARAM(pMsg->lParam), GET_Y_LPARAM(pMsg->lParam), 0);
sprintf(szMsg,"WM_MM;%d;%d;%d;0;\0", pt.x, pt.y, 0);
LanSend(szMsg, strlen(szMsg));
}
}
else if (pMsg->message == WM_KEYDOWN) //键盘按下
{
if(m_bSend)
{
memset(szMsg,'\0',sizeof(szMsg));
sprintf(szMsg,"WM_KD;%d;%d;%d;%d;\0",pMsg->wParam, 0, 0, 0);
LanSend(szMsg, strlen(szMsg));
}
}
else if (pMsg->message == WM_KEYUP) //键盘抬起
{
if(m_bSend)
{
memset(szMsg,'\0',sizeof(szMsg));
sprintf(szMsg,"WM_KU;%d;%d;%d;%d;\0",pMsg->wParam, 0, 0, 0);
LanSend(szMsg, strlen(szMsg));
}
}
return CDialog::PreTranslateMessage(pMsg);
}
上面的确实现了三个基本命令,但存在这样一个问题:即鼠标的当前位置离开这个应用程序时(对话框界面),这些鼠标数据就无法捕获。
为什么会有这种应用呢?比如在应用程序中点击某个按钮,打开了一个CMD命令行控制台窗口(验证与对方能否Ping通),弹出的CMD窗口上就无法捕获这些鼠标数据;
上面说的为什么在“在【CWinApp】中的PreTranslateMessage更好一些”?是说如果要编写的应用程序假若有多个Dialog,岂不是要给每一个窗口写一个PreTranslateMessage!
② 怎么在“整个系统”中获取鼠标?想到了DriectX中的DriectInput,和鼠标钩子。先说DriectInput,下面是在定时器或者线程中获取鼠标数据的一段:(初始化部分就不粘贴了)
HRESULT ReadImmediateData( HWND hDlg )
{
HRESULT hr;
TCHAR strNewText[128] = TEXT(""); // Output string
DIMOUSESTATE2 dims2; // DirectInput mouse state structure
if( NULL == g_pMouse )
return S_OK;
// Get the input's device state, and put the state in dims
ZeroMemory( &dims2, sizeof(dims2) );
hr = g_pMouse->GetDeviceState( sizeof(DIMOUSESTATE2), &dims2 );
if( FAILED(hr) )
{
// DirectInput may be telling us that the input stream has been
// interrupted. We aren't tracking any state between polls, so
// we don't have any special reset that needs to be done.
// We just re-acquire and try again.
// If input is lost then acquire and keep trying
hr = g_pMouse->Acquire();
while( hr == DIERR_INPUTLOST )
hr = g_pMouse->Acquire();
// Update the dialog text
if( hr == DIERR_OTHERAPPHASPRIO ||
hr == DIERR_NOTACQUIRED )
SetDlgItemText( hDlg, IDC_DATA, TEXT("Unacquired") );
// hr may be DIERR_OTHERAPPHASPRIO or other errors. This
// may occur when the app is minimized or in the process of
// switching, so just try again later
return S_OK;
}
// The dims structure now has the state of the mouse, so
// display mouse coordinates (x, y, z) and buttons.
StringCchPrintf( strNewText, 128, TEXT("(X=% 3.3d, Y=% 3.3d, Z=% 3.3d) B0=%c B1=%c B2=%c B3=%c B4=%c B5=%c B6=%c B7=%c"),
dims2.lX, dims2.lY, dims2.lZ,
(dims2.rgbButtons[0] & 0x80) ? '1' : '0',
(dims2.rgbButtons[1] & 0x80) ? '1' : '0',
(dims2.rgbButtons[2] & 0x80) ? '1' : '0',
(dims2.rgbButtons[3] & 0x80) ? '1' : '0',
(dims2.rgbButtons[4] & 0x80) ? '1' : '0',
(dims2.rgbButtons[5] & 0x80) ? '1' : '0',
(dims2.rgbButtons[6] & 0x80) ? '1' : '0',
(dims2.rgbButtons[7] & 0x80) ? '1' : '0');
// Get the old text in the text box
TCHAR strOldText[128];
GetDlgItemText( hDlg, IDC_DATA, strOldText, 127 );
// If nothing changed then don't repaint - avoid flicker
if( 0 != lstrcmp( strOldText, strNewText ) )
SetDlgItemText( hDlg, IDC_DATA, strNewText );
//
// 仍然使用::GetCursorPos 发送鼠标的绝对位置
//
char szMsg[100] = {0};
POINT pt;
::GetCursorPos(&pt);
sprintf(szMsg,"WM_MM;%d;%d;%d;0;\0", pt.x, pt.y, 0);
LanSend(szMsg, strlen(szMsg));
//
// 发送鼠标的【按下】和【抬起】事件
// 因为driectInput里无法识别鼠标抬起事件,这里只能模拟抬起
//
if (dims2.rgbButtons[0] & 0x80)
{
GetCursorPos(&pt);
sprintf(szMsg,"WM_LBD;%d;%d;%d;0;\0",pt.x, pt.y, 0);
LanSend(szMsg, strlen(szMsg));
Sleep(2);
sprintf(szMsg,"WM_LBU;%d;%d;%d;0;\0",pt.x, pt.y, 0);
LanSend(szMsg, strlen(szMsg));
}
return S_OK;
}
用DirectInput有几个局限,一是如上程序看到,鼠标移动中给出的是“相对位置”,而不是“绝对位置”(两者如何在DirectInput中转换,我没有试出来),不得不还是使用Win32中的::GetCursorPos;二是只能判断鼠标“点击按下”,无法识别鼠标按键“抬起”,如上程序,自己模拟了在点击后延时2ms后发生“抬起”事件,但这样并不是用户在操作中的真正行为;
③ 继续说鼠标低级钩子:用的是WH_MOUSE_LL,对应的挂接函数为LowLevelMouseProc,好处是不用单独写一个DLL库,直接在应用程序中使用即可;
//
// 全局变量和全局函数定义
//
HHOOK hhookMs = NULL;
LRESULT CALLBACK LowLevelMouseProc (INT nCode, WPARAM wParam, LPARAM lParam);
BOOL UninstallKbHook();
BOOL InstallKbHook();
//
// 安装鼠标Hook
//
void CTestMFCDlg::OnButton1()
{
InstallKbHook();
}
//
// 卸掉键盘Hook
//
void CTestMFCDlg::OnButton2()
{
UninstallKbHook();
}
LRESULT CALLBACK LowLevelMouseProc (INT nCode, WPARAM wParam, LPARAM lParam)
{
MSLLHOOKSTRUCT *pkbhs = (MSLLHOOKSTRUCT *)lParam;
char strMsg[100] = {0};
switch (nCode)
{
case HC_ACTION:
{
//鼠标移动
if (wParam == WM_MOUSEMOVE)
{
sprintf(strMsg, "WM_MOUSEMOVE: x= %d, y= %d\n", pkbhs->pt.x, pkbhs->pt.y);
OutputDebugString(strMsg);
}
//鼠标左击
if(wParam == WM_LBUTTONDOWN)
{
sprintf(strMsg, "WM_LBUTTONDOWN: x= %d, y= %d\n", pkbhs->pt.x, pkbhs->pt.y);
OutputDebugString(strMsg);
}
// //滚轮事件
// if (wParam == WM_MOUSEWHEEL)
// {
// sprintf(strMsg, "WM_MOUSEWHEEL: %d\n", HIWORD(pkbhs->mouseData));
// OutputDebugString(strMsg);
// }
}
default:
break;
}
return CallNextHookEx (NULL, nCode, wParam, lParam);
}
BOOL InstallKbHook( )
{
if (hhookMs )
UninstallKbHook();
hhookMs = SetWindowsHookEx(WH_MOUSE_LL,
(HOOKPROC)LowLevelMouseProc, AfxGetApp()->m_hInstance, NULL);
return(hhookMs != NULL);
}
BOOL UninstallKbHook()
{
BOOL fOk = FALSE;
if (hhookMs ) {
fOk = UnhookWindowsHookEx(hhookMs );
hhookMs = NULL;
}
return(fOk);
}
鼠标低级钩子是一个全局的,只要安装钩子成功,在整个系统中都是有效的,基本解决了这个问题。
综上,决定使用鼠标低级钩子。