Monitor *raw* mouse buttons and keypresses in Windows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* hooktest2 | |
Monitor *raw* mouse buttons, keypresses, everything but MOUSEMOVE via low level | |
mouse and keyboard hooks. I wrote this to help identify some bugs in Chrome and | |
software running on Dell laptops. | |
The program will exit when the caps lock key is pressed. You can change that in | |
the LowLevelKeyboardProc function. | |
Windows will auto detach the hooks if there's a delay processing them. This is | |
especially apparent with the mouse hook, so if you attempt to select and copy | |
the console output from this program that may cause the mouse hook to be | |
detached. | |
g++ -Wall -o hooktest2 hooktest2.cpp && hooktest2 | |
Copyright (C) 2020 Jay Satiro <raysatiro@yahoo.com> | |
All rights reserved. License GPLv3+: GNU GPL version 3 or later | |
<http://www.gnu.org/licenses/gpl.html>. | |
This is free software: you are free to change and redistribute it. | |
There is NO WARRANTY, to the extent permitted by law. | |
https://gist.github.com/jay/2c2cd67534595615bc81ec0c8440f95f | |
*/ | |
#define _WIN32_WINNT 0x0501 | |
#include <windows.h> | |
#include <stdio.h> | |
#include <time.h> | |
#include <iomanip> | |
#include <iostream> | |
#include <sstream> | |
#include <string> | |
#ifndef GET_X_LPARAM | |
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) | |
#endif | |
#ifndef GET_Y_LPARAM | |
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) | |
#endif | |
#ifndef WM_MOUSEHWHEEL | |
#define WM_MOUSEHWHEEL 0x020E | |
#endif | |
#ifndef LLKHF_INJECTED | |
#define LLKHF_INJECTED 0x00000010 | |
#endif | |
#ifndef LLKHF_LOWER_IL_INJECTED | |
#define LLKHF_LOWER_IL_INJECTED 0x00000002 | |
#endif | |
#ifndef LLMHF_INJECTED | |
#define LLMHF_INJECTED 0x00000001 | |
#endif | |
#ifndef LLMHF_LOWER_IL_INJECTED | |
#define LLMHF_LOWER_IL_INJECTED 0x00000002 | |
#endif | |
using namespace std; | |
/* system time in format: Tue May 16 03:24:31.123 PM */ | |
string SystemTimeStr(const SYSTEMTIME *t) | |
{ | |
const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; | |
const char *mon[] = { NULL, "Jan", "Feb", "Mar", "Apr", "May", "Jun", | |
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; | |
stringstream ss; | |
unsigned t_12hr = (t->wHour > 12 ? t->wHour - 12 : t->wHour ? t->wHour : 12); | |
const char *t_ampm = (t->wHour < 12 ? "AM" : "PM"); | |
ss.fill('0'); | |
ss << dow[t->wDayOfWeek] << " " | |
<< mon[t->wMonth] << " " | |
<< setw(2) << t->wDay << " " | |
<< setw(2) << t_12hr << ":" | |
<< setw(2) << t->wMinute << ":" | |
<< setw(2) << t->wSecond << "." | |
<< setw(3) << t->wMilliseconds << " " | |
<< t_ampm; | |
return ss.str(); | |
} | |
string now() | |
{ | |
SYSTEMTIME st; | |
GetLocalTime(&st); | |
return SystemTimeStr(&st); | |
} | |
/* The timestamp style in default mode: [Sun May 28 07:00:27.999 PM]: text */ | |
#define TIMESTAMP \ | |
"[" << now() << "]: " | |
std::string get_wm_name(WPARAM wParam) | |
{ | |
switch(wParam) { | |
case WM_KEYDOWN: return "WM_KEYDOWN"; | |
case WM_SYSKEYDOWN: return "WM_SYSKEYDOWN"; | |
case WM_KEYUP: return "WM_KEYUP"; | |
case WM_SYSKEYUP: return "WM_SYSKEYUP"; | |
case WM_MOUSEMOVE: return "WM_MOUSEMOVE"; | |
case WM_MOUSEWHEEL: return "WM_MOUSEWHEEL"; | |
case WM_MOUSEHWHEEL: return "WM_MOUSEHWHEEL"; | |
case WM_LBUTTONDOWN: return "WM_LBUTTONDOWN"; | |
case WM_LBUTTONUP: return "WM_LBUTTONUP"; | |
case WM_MBUTTONDOWN: return "WM_MBUTTONDOWN"; | |
case WM_MBUTTONUP: return "WM_MBUTTONUP"; | |
case WM_RBUTTONDOWN: return "WM_RBUTTONDOWN"; | |
case WM_RBUTTONUP: return "WM_RBUTTONUP"; | |
case WM_XBUTTONDOWN: return "WM_XBUTTONDOWN"; | |
case WM_XBUTTONUP: return "WM_XBUTTONUP"; | |
} | |
stringstream ss; | |
ss << "<Unknown wParam: 0x" << std::hex << wParam << std::dec << ">"; | |
return ss.str(); | |
} | |
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) | |
{ | |
DWORD flags = ((MSLLHOOKSTRUCT *) lParam)->flags; | |
bool injected = flags & (LLMHF_INJECTED | LLMHF_LOWER_IL_INJECTED); | |
POINT pt = ((MSLLHOOKSTRUCT *) lParam)->pt; | |
if(nCode == HC_ACTION) { | |
switch(wParam) { | |
case WM_MOUSEMOVE: | |
break; | |
default: | |
case WM_MOUSEWHEEL: | |
case WM_MOUSEHWHEEL: | |
case WM_LBUTTONDOWN: | |
case WM_LBUTTONUP: | |
case WM_MBUTTONDOWN: | |
case WM_MBUTTONUP: | |
case WM_RBUTTONDOWN: | |
case WM_RBUTTONUP: | |
case WM_XBUTTONDOWN: | |
case WM_XBUTTONUP: | |
stringstream ss; | |
ss << "\n" << TIMESTAMP | |
<< "{M" << (injected ? ", injected" : "") << "} " | |
<< get_wm_name(wParam); | |
size_t fillcount; | |
const int col = 55; | |
long pos = ss.tellp(); | |
if(0 < pos && pos < col) | |
fillcount = col - pos; | |
else | |
fillcount = 1; | |
ss << string(fillcount, ' ') | |
<< pt.x << "," << pt.y << "\n"; | |
cerr << ss.rdbuf(); | |
break; | |
} | |
} | |
return CallNextHookEx(NULL, nCode, wParam, lParam ); | |
} | |
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) | |
{ | |
KBDLLHOOKSTRUCT *pkh = (KBDLLHOOKSTRUCT *)lParam; | |
DWORD flags = ((KBDLLHOOKSTRUCT *) lParam)->flags; | |
bool injected = flags & (LLKHF_INJECTED | LLKHF_LOWER_IL_INJECTED); | |
if(nCode == HC_ACTION) { | |
switch(wParam) { | |
case WM_KEYDOWN: | |
case WM_SYSKEYDOWN: | |
if(pkh->scanCode == 0x3a) { // EXIT ON CAPS LOCK | |
Beep(750, 300); | |
exit(1); | |
} | |
default: | |
case WM_KEYUP: | |
case WM_SYSKEYUP: | |
DWORD key = ((pkh->scanCode << 16) & 0xFFFF0000); | |
// Right Shift: Ignore extended flag so GetKeyNameText will recognize it. | |
// https://stackoverflow.com/a/18901844 | |
if((pkh->flags & LLKHF_EXTENDED) && (pkh->vkCode != VK_RSHIFT)) | |
key |= (1 << 24); | |
char keyname[256] = { 0 }; | |
GetKeyNameTextA(key, keyname, sizeof(keyname)); | |
stringstream ss; | |
ss << "\n" << TIMESTAMP | |
<< "{K" << (injected ? ", injected" : "") << "} " | |
<< get_wm_name(wParam) << " '" << keyname << "'"; | |
size_t fillcount; | |
const int col = 72; | |
long pos = ss.tellp(); | |
if(0 < pos && pos < col) | |
fillcount = col - pos; | |
else | |
fillcount = 1; | |
ss << string(fillcount, ' ') | |
<< std::hex << "0x" << pkh->scanCode << std::dec << "\n"; | |
cerr << ss.rdbuf(); | |
break; | |
} | |
} | |
return CallNextHookEx(NULL, nCode, wParam, lParam); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
MSG msg; | |
HINSTANCE hinstExe = GetModuleHandle(NULL); | |
// Force creation of message queue | |
PeekMessageA(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); | |
for(int i = 0; !SetThreadPriority(GetCurrentThread(), 15) && (i < 100); ++i) | |
Sleep(1); | |
SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hinstExe, 0); | |
SetWindowsHookExW(WH_MOUSE_LL, LowLevelMouseProc, hinstExe, 0 ); | |
cerr << "To exit the program hit caps lock key at any time." << endl; | |
while(GetMessageA(&msg, NULL, 0, 0)) | |
; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment