December 01, 2024, 11:36:50 AM

News:

IWBasic runs in Windows 11!


Mouse wheel hook example

Started by sapero, September 22, 2008, 09:32:32 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

sapero

September 22, 2008, 09:32:32 AM Last Edit: September 24, 2008, 06:30:25 AM by sapero
When you open favorites band in internet explorer, it will let you scroll it by mouse wheel. After you focus browser window, the favorites band will not scroll until you activate it. Same with browser window - you must activate it (click) before wheeling.

This example hook automatically activates the window under cursor when mouse wheel begins. It uses also other techniques:
1. critical section for putting a string into console window. Two threads are printing to console.
2. Event objects to synchronize tasks between two threads
3. Pipe server and client - communication between hooked processes and this one that installed the hook.
4. The pipe server on systems above win98 uses asynchronous/nonblocking pipe.

Make sure you compile all files in one directory.

Shared include file pipe.inc
#define PIPENAME "\\\\.\\pipe\\msghookpipe"

Save this as hookdll.src and then compile it as DLL
#include "windows.inc"
#include "stdio.inc"
#include "pipe.inc"

export InstallHook;
export UninstallHook;

const GENERIC_READ_WRITE = GENERIC_READ | GENERIC_WRITE;

HHOOK g_hook;

sub UpdateHookHandle(),BOOL
{
dstring text[MAX_PATH];
// exe path
int cch = GetModuleFileName(0, text, MAX_PATH);

while (!g_hook)
{
// connect to pipe server, send exe path to it, and request HHOOK handle
HANDLE hPipe = CreateFile(PIPENAME, GENERIC_READ_WRITE, 0,0, OPEN_EXISTING,0,0);

if (hPipe != INVALID_HANDLE_VALUE)
{
DWORD PipeType = PIPE_READMODE_MESSAGE;
SetNamedPipeHandleState(hPipe, &PipeType,0,0);

// send exe path to the server
WriteFile(hPipe, text, cch, &cch, 0);
// read HHOOK from server
ReadFile(hPipe, &g_hook, 4, &cch, 0);
CloseHandle(hPipe);
break;
}
if (GetLastError() != ERROR_PIPE_BUSY)
{
break;
}
if (!WaitNamedPipe(PIPENAME, 20000))
{
break;
}
}
return g_hook;
}

// hook main function
sub GetMsgProc(int code, WPARAM wParam, MSG *msg),LRESULT
{
POINT pt;
HWND hwnd;
LRESULT result = 0;

if (code == HC_ACTION)
{
switch (msg->message)
{
case WM_MOUSEWHEEL:
GetCursorPos(&pt);
hwnd = WindowFromPoint(pt);

if (GetFocus() != hwnd)
{
SetFocus(hwnd);
msg->message = WM_NULL;
PostMessage(hwnd, WM_MOUSEWHEEL, msg->wParam, msg->lParam);
}
}
}
if (!g_hook)
{
// Beep(1000, 100);
UpdateHookHandle();
}
if (g_hook)
{
result = CallNextHookEx(g_hook, code, wParam, msg);
}

return result;
}


sub InstallHook(),HHOOK
{
if (!g_hook)
{
g_hook = SetWindowsHookEx(WH_GETMESSAGE, &GetMsgProc, _hinstance, 0);
}
return g_hook;
}


sub UninstallHook()
{
if (g_hook)
{
UnhookWindowsHookEx(g_hook);
g_hook = 0;
}
}


Then save this program as hookexe.src and compile as Console EXE:
#include "windows.inc"
#include "stdio.inc"
#include "conio.inc"
#include "pipe.inc"

struct MYCONTEXT
{
HHOOK      hHook;
OVERLAPPED ovp;
HANDLE     hExitEvent;  // must be after OVERLAPPED
HANDLE     hPipe;       // server pipe handle
HANDLE     hThread;     // pipe server thread
HANDLE     hReadyEvent; // thread launched
HANDLE     hHookEvent;  // received message from hooked app
CRITICAL_SECTION ConsoleSection; // exclusive access to console window
}
// MYCONTEXT.ovp.hEvent \
// MYCONTEXT.hExitEvent / used as HANDLE array in WaitForMultipleObjects()

sub main()
{
MYCONTEXT ctx;
OSVERSIONINFO ver;

DWORD dwPipeFlags = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED;
LPTHREAD_START_ROUTINE PipeThread = &PipeThreadXP;

GetVersionEx(&ver);

if (ver.dwPlatformId < VER_PLATFORM_WIN32_NT)
{
//return MessageBox(0, "Sorry, your system does not support asynchronous pipes");
// Windows Me/98/95: overlapped pipes not supported
dwPipeFlags = PIPE_ACCESS_DUPLEX;
PipeThread  = &PipeThread98;
}

declare *InstallHook(),HHOOK;
declare *UninstallHook();

HINSTANCE dll = LoadLibrary("hookdll.dll");
InstallHook   = GetProcAddress(dll, "InstallHook");
UninstallHook = GetProcAddress(dll, "UninstallHook");

if (InstallHook && UninstallHook)
{
ZeroMemory(&ctx, sizeof(ctx));
// event for asynchronous calls
ctx.ovp.hEvent  = CreateEvent(NULL, true, true, NULL);
// event for forcing server thread to exit
ctx.hExitEvent  = CreateEvent(NULL, false, false, NULL);
// event for signalling ready state of server thread
ctx.hReadyEvent = CreateEvent(NULL, false, false, NULL);
// event for signalling received pipe message
ctx.hHookEvent  = CreateEvent(NULL, false, false, NULL);

// two threads writing to one console should be synchronized
InitializeCriticalSection(&ctx.ConsoleSection);

// create asynchronous pipe, so we can easily break blocking calls
ctx.hPipe = CreateNamedPipe(PIPENAME,
dwPipeFlags,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
MAX_PATH,
MAX_PATH,
NMPWAIT_USE_DEFAULT_WAIT, NULL);

ctx.hThread = CreateThread(0,0, PipeThread, &ctx,0,0);

if (ctx.hThread)
{
// wait for a signal from thread
WaitForSingleObject(ctx.hReadyEvent, INFINITE);
CloseHandle(ctx.hReadyEvent);

// this is all you need to install a hook
ctx.hHook = InstallHook();
// but we start a pipe server

if (ctx.hHook)
{
// wait until hook is fully installed
while (!WaitForSingleObject(ctx.hHookEvent, 1000))
{
}

EnterCriticalSection(&ctx.ConsoleSection);
// any console writing here
puts("\nPress any key to exit\n");
LeaveCriticalSection(&ctx.ConsoleSection);

getch();
UninstallHook();
}
// force pipe thread to exit
SetEvent(ctx.hExitEvent);

if (WaitForSingleObject(ctx.hThread, 2000))
{
// terminate pipe thread because CloseHandle(ctx.hPipe) will hung
// and CancelIO() will not work here.
TerminateThread(ctx.hThread, 0);
}

CloseHandle(ctx.hThread);
}
CloseHandle(ctx.hHookEvent);
CloseHandle(ctx.hPipe);
CloseHandle(ctx.hExitEvent);
CloseHandle(ctx.ovp.hEvent);
DeleteCriticalSection(&ctx.ConsoleSection);
}
return 0;
}


sub PipeThreadXP(MYCONTEXT *ctx)
{
dstring text[MAX_PATH];
DWORD cch;
DWORD err;
BOOL fSuccess;
DWORD result;

// tell to main() that pipe thread is now running - WaitForSingleObject(ctx.hReadyEvent, INFINITE);
SetEvent(ctx->hReadyEvent);

while (true)
{
// initialize waiting for a client
fSuccess = ConnectNamedPipe(ctx->hPipe, &ctx->ovp);

if (fSuccess)
{
// we are now connected
}
else
{
err = GetLastError();

switch (err)
{
case ERROR_IO_PENDING:
// wait for connection or server termination
result = WaitForMultipleObjects(2, &ctx->ovp.hEvent, false, INFINITE);

switch (result)
{
case 0: // pipe
fSuccess = GetOverlappedResult(ctx->hPipe, &ctx->ovp, &cch, true);

default:// hExitEvent
break;
}

case ERROR_PIPE_CONNECTED:
SetEvent(ctx->ovp.hEvent);

default:
break;
}
}
if (fSuccess)
{
// connected, initiate ReadFile [read exe path from client]
fSuccess = ReadFile(ctx->hPipe, text, MAX_PATH, &cch, &ctx->ovp);

if (fSuccess)
{
// ReadFile completed
}
else
{
err = GetLastError();

switch (err)
{
case ERROR_IO_PENDING:
result = WaitForMultipleObjects(2, &ctx->ovp.hEvent, false, INFINITE);

switch (result)
{
case 0:// pipe
fSuccess = GetOverlappedResult(ctx->hPipe, &ctx->ovp, &cch, true);

default:// hExitEvent
break;
}
default:
break;
}
}
}
if (!fSuccess || !cch)
{
DisconnectNamedPipe(ctx->hPipe);
break;
}
text[cch] = 0;

EnterCriticalSection(&ctx->ConsoleSection);
// any console writing here
puts(text);
LeaveCriticalSection(&ctx->ConsoleSection);

// send hook handle to client
WriteFile(ctx->hPipe, &ctx->hHook, 4, &cch, 0);
// notify other threads that we received a message
SetEvent(ctx->hHookEvent);

FlushFileBuffers(ctx->hPipe);
DisconnectNamedPipe(ctx->hPipe);
}
ExitThread(0);
}


sub PipeThread98(MYCONTEXT *ctx)
{
dstring text[MAX_PATH];
DWORD cch;
BOOL fSuccess;

// tell to main() that pipe thread is now running - WaitForSingleObject(ctx.hReadyEvent, INFINITE);
SetEvent(ctx->hReadyEvent);

while (true)
{
// initialize waiting for a client
fSuccess = ConnectNamedPipe(ctx->hPipe, NULL);

if (!fSuccess)
{
break;
}

// connected, read exe path from client
fSuccess = ReadFile(ctx->hPipe, text, MAX_PATH, &cch, NULL);

if (!fSuccess || !cch)
{
DisconnectNamedPipe(ctx->hPipe);
break;
}
text[cch] = 0;

EnterCriticalSection(&ctx->ConsoleSection);
// any console writing here; this thread has exclusive access to console
puts(text);
LeaveCriticalSection(&ctx->ConsoleSection);

// send hook handle to client
WriteFile(ctx->hPipe, &ctx->hHook, 4, &cch, 0);
// notify other threads that we received a message
SetEvent(ctx->hHookEvent);

FlushFileBuffers(ctx->hPipe);
DisconnectNamedPipe(ctx->hPipe);
}
ExitThread(0);
}


After you run it, this program will load the already compiled hookdll.dll, and query it for two functions - InstallHook and UninstallHook. It initializes then all events, starts pipe server and installs the system-wide WH_GETMESSAGE hook, and waits for a keypress.
Dll with hook code will be loaded into any process that has message loop, and at first time it will send process path and name to our pipe server. It will also read back the hook handle, required for CallNextHookEx.

There is a small loop that waits for the initial hook distribution to all running and active/busy processes:
while (!WaitForSingleObject(ctx.hHookEvent, 1000))Every time the dll is attached to a process, a pipe message received from it forces the loop to wait additional second until the hook is loaded into another process. If there is no such process, the WaitForSingleObject will time-out and our program will display 'press any key to quit'.

pistol350

Thank you sapero for this great example.
It works great!

However, I have a "problem" to report.
I tried to use the hook with Mozilla Firefox's bookmarks  but it does not work as expected.
When i click on Mozilla Firefox' bookmark, the drop down list appears,
then whenever i try to use my mouse wheel, the list freezes. and i can't click on any item in the list until i move the mouse pointer out of the list area.

Any suggestions ?

By the way, i can't tell if the problem appears only with Mozilla.

Regards,

Peter B.

sapero

September 24, 2008, 06:19:12 AM #2 Last Edit: September 24, 2008, 06:25:45 AM by sapero
I noticed same bug with combo box (check the dropped listbox in Aurora IDE->Subs), and another one in internet explorer, when <select> has focus.
Need to filter out filter the ComboLBox window class, and some thinking how to fix it in IE.
Also tooltips should be not focused.

Bugfix for combobox:sub GetMsgProc(int code, WPARAM wParam, MSG *msg),LRESULT
{
POINT pt;
HWND hwnd;
LRESULT result = 0;
dstring cname[64];

if (code == HC_ACTION)
{
switch (msg->message)
{
case WM_MOUSEWHEEL:
GetCursorPos(&pt);
hwnd = WindowFromPoint(pt);

if (GetFocus() != hwnd)
{
//bugfix start
GetClassName(hwnd, cname, 64);
if ((cname != "ComboLBox") && (cname != "tooltips_class32"))
{
SetFocus(hwnd);
msg->message = WM_NULL;
PostMessage(hwnd, WM_MOUSEWHEEL, msg->wParam, msg->lParam);
}
}
}
}
if (!g_hook)
{
UpdateHookHandle();
}
if (g_hook)
{
result = CallNextHookEx(g_hook, code, wParam, msg);
}

return result;
}

pistol350

I've updated the code but the problem remains.
I've tried with many different applications but for some reasons, the problem only shows up with Mozilla F.
By the way, i should update what i said previously, actually the problem appears with any dropped down list of any menu item in Mozilla F ,
while it woks great with the web pages.  ???

I should also mention that when the list freezes the borders of mozilla window flip strangely as if the window could not be redrawn properly.
Regards,

Peter B.

sapero

Peter, try to exclude MozillaDropShadowWindowClass, or replace your dll source with this one. It will print in console current class name (only for firefox).

Edit: unable to upload 1KB zip file.
#include "windows.inc"
#include "stdio.inc"
#include "pipe.inc"

export InstallHook;
export UninstallHook;

const GENERIC_READ_WRITE = GENERIC_READ | GENERIC_WRITE;

HHOOK g_hook;
dstring g_exe[MAX_PATH];


sub UpdateHookHandle(),BOOL
{
// exe path
int cch = GetModuleFileName(0, g_exe, MAX_PATH);

while (!g_hook)
{
// connect to pipe server, send exe path to it, and request HHOOK handle
HANDLE hPipe = CreateFile(PIPENAME, GENERIC_READ_WRITE, 0,0, OPEN_EXISTING,0,0);

if (hPipe != INVALID_HANDLE_VALUE)
{
DWORD PipeType = PIPE_READMODE_MESSAGE;
SetNamedPipeHandleState(hPipe, &PipeType,0,0);

// send exe path to the server
WriteFile(hPipe, g_exe, cch, &cch, 0);
// read HHOOK from server
ReadFile(hPipe, &g_hook, 4, &cch, 0);
CloseHandle(hPipe);
break;
}
if (GetLastError() != ERROR_PIPE_BUSY)
{
break;
}
if (!WaitNamedPipe(PIPENAME, 20000))
{
break;
}
}
return g_hook;
}


sub GetMsgProc(int code, WPARAM wParam, MSG *msg),LRESULT
{
POINT pt;
HWND hwnd;
LRESULT result = 0;
dstring cname[64];

if (!g_hook)
{
UpdateHookHandle();
CharLower(g_exe);
}

if (code == HC_ACTION)
{
switch (msg->message)
{
case WM_MOUSEWHEEL:
GetCursorPos(&pt);
hwnd = WindowFromPoint(pt);

if (GetFocus() != hwnd)
{
GetClassName(hwnd, cname, 64);
if (strstr(g_exe, "firefox"))
{
// print class name in the console window
openconsole(); print(cname);
}
if ((cname != "ComboLBox") && (cname != "tooltips_class32"))
{
SetFocus(hwnd);
msg->message = WM_NULL;
PostMessage(hwnd, WM_MOUSEWHEEL, msg->wParam, msg->lParam);
}
}
}
}
if (g_hook)
{
result = CallNextHookEx(g_hook, code, wParam, msg);
}

return result;
}


sub InstallHook(),HHOOK
{
if (!g_hook)
{
g_hook = SetWindowsHookEx(WH_GETMESSAGE, &GetMsgProc, _hinstance, 0);
}
return g_hook;
}


sub UninstallHook()
{
if (g_hook)
{
UnhookWindowsHookEx(g_hook);
g_hook = 0;
}
}

pistol350

"MozillaDropShadowWindowClass" is indeed printed in the console.
You suggested me to exclude that class, any idea how i can do this ?
Regards,

Peter B.

sapero

if ((cname != "ComboLBox") && (cname != "tooltips_class32") && (cname != "MozillaDropShadowWindowClass"))

pistol350

That was a brilliant idea!
After applying that line, everything worked as expected.
Thank you!
Regards,

Peter B.