Skip to content

Instantly share code, notes, and snippets.

@jay
Last active November 24, 2023 21:43
Show Gist options
  • Save jay/2c2cd67534595615bc81ec0c8440f95f to your computer and use it in GitHub Desktop.
Save jay/2c2cd67534595615bc81ec0c8440f95f to your computer and use it in GitHub Desktop.
Monitor *raw* mouse buttons and keypresses in Windows
/* 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.
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();
}
/* print to the console from a separate thread that takes only apc calls */
static HHOOK hhkKeyboard, hhkMouse;
static HANDLE hApcThread;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for(;;) SleepEx(INFINITE, TRUE);
}
void Papcfunc(ULONG_PTR Parameter)
{
static bool working;
if(working)
exit(99); // this shouldn't happen
working = true;
stringstream &ss = *(stringstream *)Parameter;
fputs(ss.str().c_str(), stderr);
delete &ss;
working = false;
}
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
MSLLHOOKSTRUCT *hs = (MSLLHOOKSTRUCT *)lParam;
POINT pt = hs->pt;
DWORD flags = hs->flags;
bool injected = flags & (LLMHF_INJECTED | LLMHF_LOWER_IL_INJECTED);
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 = *(new stringstream);
ss << "\n" << TIMESTAMP
<< "{M" << (injected ? ", injected" : "") << "} "
<< get_wm_name(wParam);
#define FILL_TO_COLUMN(col) \
do { \
long pos = ss.tellp(); \
size_t fillcount = (0 < pos && pos < col) ? col - pos : 1; \
ss << string(fillcount, ' '); \
} while(0)
if(wParam == WM_MOUSEWHEEL || wParam == WM_MOUSEHWHEEL) {
int delta = GET_WHEEL_DELTA_WPARAM(hs->mouseData);
FILL_TO_COLUMN(52);
ss << "[" << delta << "]";
FILL_TO_COLUMN(62);
}
else if(wParam == WM_XBUTTONDOWN || wParam == WM_XBUTTONUP) {
int button = GET_XBUTTON_WPARAM(hs->mouseData);
FILL_TO_COLUMN(52);
ss << "[" << button << "]";
FILL_TO_COLUMN(62);
}
else {
FILL_TO_COLUMN(62);
}
ss << pt.x << "," << pt.y << "\n";
/* this function must always return immediately so print to stderr from
a different thread. if it fails to queue even one time then unhook
but continue to process this hook call so no input is lost. */
__sync_synchronize();
if(!QueueUserAPC(Papcfunc, hApcThread, (ULONG_PTR)&ss))
UnhookWindowsHookEx(hhkMouse);
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 = *(new stringstream);
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";
/* this function must always return immediately so print to stderr from
a different thread. if it fails to queue even one time then unhook
but continue to process this hook call so no input is lost. */
__sync_synchronize();
if(!QueueUserAPC(Papcfunc, hApcThread, (ULONG_PTR)&ss))
UnhookWindowsHookEx(hhkKeyboard);
break;
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
int main(int argc, char *argv[])
{
MSG msg;
HINSTANCE hinstExe = GetModuleHandle(NULL);
hApcThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if(!hApcThread) {
cerr << "Failed to start the apc thread" << endl;
exit(1);
}
// Force creation of message queue
PeekMessageA(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
hhkKeyboard = SetWindowsHookEx(
WH_KEYBOARD_LL, LowLevelKeyboardProc, hinstExe, 0);
hhkMouse = 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