最近工作用的Thinkpad左ALT键可能使用太多损坏,表现为是不是粘死,在工作中用快捷达不到目标时才发现这一状况,且该状态是不可见的,发生时总要排除其它原因后才通过重复敲击ALT键来释放这一BT的状态,尤其在处理关键任务时发生令人心烦意乱,于是捡起丢了很多年的VC写个键盘钩子监视这一情况,以便及时发行并整改。
解决的原理为采用低级键盘钩子检测ALT键的状态,若发现ALT键长期被按着不释放,说明已经发生了故障,通过任务栏的气球提示故障信息,可及时敲打ALT键进行恢复,虽然办法比较笨,但在更换键盘前可以凑合着使用(Thinkpad的监盘更换要400大洋,不知道这个价位是不是真的合理)!
要使用全局钩子,因此要把钩子函数放在动态库中,首先编写动态库,下面是动态库的主要代码内容:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
__declspec(dllexport) HMODULE glhModule;
__declspec(dllexport) HHOOK glhHook;
__declspec(dllexport) DWORD dwALTPressTime;
__declspec(dllexport)
LRESULT CALLBACK LowLevelKeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
){
if (code < 0)
{
return CallNextHookEx(glhHook, code, wParam, lParam);
}
LPKBDLLHOOKSTRUCT lpKBDLLHook;
lpKBDLLHook = (LPKBDLLHOOKSTRUCT)lParam;
if (wParam == WM_SYSKEYDOWN && (lpKBDLLHook->vkCode == VK_LMENU || lpKBDLLHook->vkCode == VK_RMENU))
{
if (dwALTPressTime == 0)
{
dwALTPressTime = lpKBDLLHook->time;
}
}
if (wParam == WM_KEYUP && (lpKBDLLHook->vkCode == VK_LMENU || lpKBDLLHook->vkCode == VK_RMENU))
{
dwALTPressTime = 0;
}
return CallNextHookEx(glhHook, code, wParam, lParam);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
glhModule = hModule;
dwALTPressTime = 0;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
然后创建一个MFC对话框应用来安装和监视钩子的运行结果,将对话框配置什么的最简化处理,对话框类的头文件内容如下:
// keymonitorDlg.h : header file
//
#pragma once
// CkeymonitorDlg dialog
class CkeymonitorDlg : public CDialogEx
{
// Construction
public:
CkeymonitorDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_KEYMONITOR_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
afx_msg LRESULT OnShellNotifyIconCallBackMessage(WPARAM wParam, LPARAM lParam);
afx_msg void OnPopupmenuExit();
public:
afx_msg void OnClose();
afx_msg void OnTimer(UINT_PTR nIDEvent);
private:
DWORD m_dv;
DWORD m_qv;
DWORD m_tv;
public:
afx_msg void OnBnClickedApply();
afx_msg void OnPopupmenuShow();
};
对话框的实现代码内容如下:
// keymonitorDlg.cpp : implementation file
//
#include "stdafx.h"
#include "keymonitor.h"
#include "keymonitorDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CkeymonitorDlg dialog
#pragma comment(lib,"E:\\Source\\keymon\\Release\\keymon.lib")
__declspec(dllimport) HMODULE glhModule;
__declspec(dllimport) HHOOK glhHook;
__declspec(dllimport) DWORD dwALTPressTime;
__declspec(dllimport)
LRESULT CALLBACK LowLevelKeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
);
#define nTimerID1 1
#define nTimerID2 2
UINT const WMAPP_NOTIFYCALLBACK = WM_APP + 1;
class __declspec(uuid("705C8E3B-86E6-48B6-8DD0-A6E1C1F84880")) KeyMonitorIcon;
void ShowErrorMessage(DWORD dwErrorCode)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error Message"), MB_OK);
LocalFree(lpMsgBuf);
}
CkeymonitorDlg::CkeymonitorDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CkeymonitorDlg::IDD, pParent)
, m_dv(0)
, m_qv(0)
, m_tv(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CkeymonitorDlg::DoDataExchange(CDataExchange* pDX)
{
DDX_Text(pDX,IDC_EDIT1,m_dv);
DDX_Text(pDX,IDC_EDIT2,m_qv);
DDX_Text(pDX,IDC_EDIT3,m_tv);
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CkeymonitorDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(WMAPP_NOTIFYCALLBACK,OnShellNotifyIconCallBackMessage)
ON_COMMAND(ID_POPUPMENU_EXIT, &CkeymonitorDlg::OnPopupmenuExit)
ON_WM_CLOSE()
ON_WM_TIMER()
ON_BN_CLICKED(IDC_BUTTON1, &CkeymonitorDlg::OnBnClickedApply)
ON_COMMAND(ID_POPUPMENU_SHOW, &CkeymonitorDlg::OnPopupmenuShow)
END_MESSAGE_MAP()
// CkeymonitorDlg message handlers
BOOL CkeymonitorDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
NOTIFYICONDATA nid = {sizeof(nid)};
nid.hWnd = this->m_hWnd;
// add the icon, setting the icon, tooltip, and callback message.
// the icon will be identified with the GUID
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP | NIF_GUID;
nid.guidItem = __uuidof(KeyMonitorIcon);
nid.uCallbackMessage = WMAPP_NOTIFYCALLBACK;
LoadIconMetric(theApp.m_hInstance, MAKEINTRESOURCE(IDR_MAINFRAME), LIM_SMALL, &nid.hIcon);
wcsncpy_s(nid.szTip,L"ALT Key Monitor",sizeof(nid.szTip));
Shell_NotifyIcon(NIM_ADD, &nid);
// NOTIFYICON_VERSION_4 is prefered
nid.uVersion = NOTIFYICON_VERSION_4;
Shell_NotifyIcon(NIM_SETVERSION, &nid);
glhHook = ::SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC)LowLevelKeyboardProc,glhModule,NULL);
if (glhHook == NULL)
{
::ShowErrorMessage(::GetLastError());
}
m_dv = 30000;
m_qv = 1000;
m_tv = 15000;
UpdateData(FALSE);
SetTimer(nTimerID1,3000,NULL);
return TRUE; // return TRUE unless you set the focus to a control
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CkeymonitorDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CkeymonitorDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
LRESULT CkeymonitorDlg::OnShellNotifyIconCallBackMessage(WPARAM wParam, LPARAM lParam)
{
if (lParam==WM_RBUTTONDOWN)
{
CMenu menu;
menu.LoadMenu(IDR_RIGHT_MENU);
CMenu* pMenu=menu.GetSubMenu(0);
CPoint pos;
GetCursorPos(&pos);
pMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,pos.x,pos.y,AfxGetMainWnd());
}
return LRESULT();
}
void CkeymonitorDlg::OnPopupmenuExit()
{
// TODO: Add your command handler code here
NOTIFYICONDATA nid = {sizeof(nid)};
nid.uFlags = NIF_GUID;
nid.guidItem = __uuidof(KeyMonitorIcon);
Shell_NotifyIcon(NIM_DELETE, &nid);
KillTimer(nTimerID1);
KillTimer(nTimerID2);
::UnhookWindowsHookEx(glhHook);
OnOK();
}
void CkeymonitorDlg::OnClose()
{
ShowWindow(SW_HIDE);
}
void CkeymonitorDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: Add your message handler code here and/or call default
switch(nIDEvent){
case nTimerID1:
if (dwALTPressTime != 0)
{
DWORD dwTime = GetMessageTime();
if (dwTime - dwALTPressTime > m_dv)
{
// Display a low ink balloon message. This is a warning, so show the appropriate system icon.
NOTIFYICONDATA nid = {sizeof(nid)};
nid.uFlags = NIF_INFO | NIF_GUID;
nid.guidItem = __uuidof(KeyMonitorIcon);
// respect quiet time since this balloon did not come from a direct user action.
nid.dwInfoFlags = NIIF_WARNING | NIIF_RESPECT_QUIET_TIME;
wcsncpy_s(nid.szInfoTitle,L"Oops!!!",sizeof(nid.szInfoTitle));
wcsncpy_s(nid.szInfo,L"Please check your ALT key,this is a fucking problem!",sizeof(nid.szInfo));
Shell_NotifyIcon(NIM_MODIFY, &nid);
KillTimer(nTimerID1);
SetTimer(nTimerID2,m_tv,NULL);
}
}
break;
case nTimerID2:
KillTimer(nTimerID2);
SetTimer(nTimerID1,m_qv,NULL);
break;
default:break;
}
CDialogEx::OnTimer(nIDEvent);
}
void CkeymonitorDlg::OnBnClickedApply()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
KillTimer(nTimerID1);
KillTimer(nTimerID2);
SetTimer(nTimerID1,m_qv,NULL);
}
void CkeymonitorDlg::OnPopupmenuShow()
{
// TODO: Add your command handler code here
ShowWindow(SW_NORMAL);
}