Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Mouse Hook Tutorial, with Delphi codes

delphi

  • Please log in to reply
2 replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 12 November 2010 - 07:52 AM

This tutorial is the first of a series, or at least that is what I plan to :), of tutorials about Windows Hooking. Windows hook is a term defining the process intalling application defined hook procedure into a hook chain. Main purpose of hooking is to monitor the system for certain types of events. Example of events you can monitor: mouse movements/events, keyboard events, and shell events.

Why do we need hooking? There is OnMouseMove to get the position or OnKeyDown from my Form to get what key the user presses. Okay, the most interesting point of hooking is that you get to monitor events that initially not belong or intended for your application. For example, event OnMouseMove and OnKeyDown won't fired if you application is not the one has the focus. Now what if you need to know the mouse movements, or mouse clicks, or keyboard pressing even when your application is not having focus?

Please note that although the technique explain here can be used for keylogger application, but this tutorial does not approve or promote writing any application with intentions against law and moral/social norms. This is purely for knowledge sharing and educational purpose.


The Project

The application that we will develop is asked to answer the following requirement.

Whenever mouse pointer is over any of its form, even if the form is behind window from other application (ie. when the application is not having focus), it must make beep sound.


If you check this thread that need to be able to click through windows from other application, perhaps this technique can be one work around.


The Basics

To install your hook, you need to use Windows API SetWindowsHookEx. Go find its full explanation in Windows SDK or in MSDN. In brief we have to tell it what kind of event we want to monitor, and provide callback routine matches the event we want to monitor. When we finish with the hook (i.e. stop monitoring) we have to release the hook by calling Windows API UnhookWindowsHookEx along with hook handle we got previously from SetWindowsHookEx.

The Implementation

From the Basic section above, it sounds like we have three routines ready to write. So here we go.

type
  PData = ^TData;
  TData = packed record
    MouseHook: HHOOK;
    AppHandle: HWnd;
  end;

var
  Data: PData;

function MouseHookHandler(ACode: Integer; WParam: WParam;
                          LParam: LParam): LResult; stdcall;
var
  vMouseInfo: PMouseHookStruct;
begin
  if ACode < 0 then
    Result := CallNextHookEx(Data^.MouseHook, ACode, WParam, LParam)
  else begin
    vMouseInfo := PMouseHookStruct(Pointer(LParam));
    PostMessage(Data^.AppHandle, WM_CUSTOM_MOUSE, vMouseInfo^.pt.X, vMouseInfo^.pt.Y);
      
    // we dont want to block the mouse messages
    Result := 0;
  end;
end;

procedure HookMouse(const AppHandle: HWnd); stdcall;
begin
  Data^.MouseHook := SetWindowsHookEx(
	               WH_MOUSE             // hook to mouse events
		       , MouseHookHandler   // the callback routine
		       , HINSTANCE          // library (dll) identifier
		       , 0
	          );
  Data^.AppHandle := AppHandle;
end;

procedure UnhookMouse;
begin
  UnhookWindowsHookEx(Data^.MouseHook);
  Data^.MouseHook := 0;
end;

MouseHookHandler

This is the callback function that gets called each time the system generates mouse events. Note that if you get ACode with value of 0, you have to IMMEDIATELY pass the processing to the next hook and do no processing. When we get our share of events (inside the else begin end block) we just parse the parameter to get mouse location and pass that information to our application main window (stored in Data^.AppHandle). Since we don't need to wait for the result we can safely use PostMessage here.

HookMouse
This procedure is the one that installs the mouse hook. It also store the passed application window handle into a variable for easy reference later.

UnhookMouse
This procedure is the one that uninstalls our mouse hook.


Dynamic-Link Library
These routines must be placed inside a dynamic-link library (dll) file. Otherwise we only get events belong to our application, not including events for other applications. Placing these routines inside a dll introduces one important issue: we want the Data variable to be global, i.e. shared among all applications loading the dll.

This issue can be solved by creating a map to the data variable. All applications must use the same map. With the same map, it will be guaranteed that the Data variable is shared (there is only a single instance of Data variable). And of course after we are done, we need to erase the map. Sounds like two new routines ready.

const
  MMF_KEY = '{D2B9306D-EB08-41F9-AC43-9D0DD84CBD4C}';

var
  MMF: THandle;

procedure CreateGlobalData;
begin
  Randomize;
  MMF := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
    SizeOf(TGlobalData), MMF_KEY);

  if MMF = 0 then
    exit;

  Data := MapViewOfFile(MMF, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TData));
  if Data = nil then
    CloseHandle(MMF);
end;

procedure DeleteGlobalData;
begin
  if Data <> nil then
    UnmapViewOfFile(Data);

  if MMF <> INVALID_HANDLE_VALUE then
    CloseHandle(MMF);
end;

constant MMF_KEY is required to guarantee that our map is the same across all applications that loads the dll. Because they all will execute CreateFileMapping API with the same key which giving the same map.

Now we have our library of map functions ready. Time to define when to execute them. The following routine will be executed every time a process/a thread use the dll, so it will be our best bet to manage the map.

procedure DLLEntry(dwReason: DWORD);
begin
  case dwReason of
    // run when a process starts using our dll
    DLL_PROCESS_ATTACH: CreateGlobalData;
    // run when a process stop unload (stop using) our dll
    DLL_PROCESS_DETACH: DeleteGlobalData;
  end;
end;

Now for the complete dll code:

library MouseHook;

type
  PData = ^TData;
  TData = packed record
    MouseHook: HHOOK;
    AppHandle: HWnd;
  end;

var
  Data: PData;

const
  MMF_KEY = '{D2B9306D-EB08-41F9-AC43-9D0DD84CBD4C}';
  WM_CUSTOM_MOUSE = WM_USER + 111;

var
  MMF: THandle;

function MouseHookHandler(ACode: Integer; WParam: WParam;
                          LParam: LParam): LResult; stdcall;
var
  vMouseInfo: PMouseHookStruct;
begin
  if ACode < 0 then
    Result := CallNextHookEx(Data^.MouseHook, ACode, WParam, LParam)
  else begin
    vMouseInfo := PMouseHookStruct(Pointer(LParam));
    PostMessage(Data^.AppHandle, WM_CUSTOM_MOUSE, vMouseInfo^.pt.X, vMouseInfo^.pt.Y);
      
    // we dont want to block the mouse messages
    Result := 0;
  end;
end;

procedure HookMouse(const AppHandle: HWnd); stdcall;
begin
  Data^.MouseHook := SetWindowsHookEx(
	                WH_MOUSE             // hook to mouse events
		        , MouseHookHandler   // the callback routine
		        , HINSTANCE          // library (dll) identifier
		        , 0
	               );
  Data^.AppHandle := AppHandle;
end;

procedure UnhookMouse;
begin
  UnhookWindowsHookEx(Data^.MouseHook);
  Data^.MouseHook := 0;
end;

procedure CreateGlobalData;
begin
  Randomize;
  MMF := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
    SizeOf(TGlobalData), MMF_KEY);

  if MMF = 0 then
    exit;

  Data := MapViewOfFile(MMF, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TData));
  if Data = nil then
    CloseHandle(MMF);
end;

procedure DeleteGlobalData;
begin
  if Data <> nil then
    UnmapViewOfFile(Data);

  if MMF <> INVALID_HANDLE_VALUE then
    CloseHandle(MMF);
end;

procedure DLLEntry(dwReason: DWORD);
begin
  case dwReason of
    // run when a process starts using our dll
    DLL_PROCESS_ATTACH: CreateGlobalData;
    // run when a process stop unload (stop using) our dll
    DLL_PROCESS_DETACH: DeleteGlobalData;
  end;
end;

exports
  HookMouse
  , UnhookMouse
  ;

begin
  DLLProc := @DLLEntry;
  DLLEntry(DLL_PROCESS_ATTACH);
end.


Application Part

In this part we only need to deal with notification message from our dll and compare with the areas of our form. When the mouse is over one of our form, even if that form is under other application's window, we have to play beep sound.

You can start by creating a new Delphi project, and add several forms to it. Make sure all the forms have "Visible" property set to true. Add two buttons in the main form, give a button a title of Install Mouse Hook and Uninstall Mouse Hook for the other. In the form declaration add a few lines like the following.

const
  WM_CUSTOM_MOUSE = WM_USER + 111;
  
type
  TForm1 = class(TForm)
    ...
    ...
  private
    // flag to see if we already have mouse over our form previously
    FMouseOver: Boolean;
    procedure HandleCustomMouseMsg(var AMsg: TMessage); message WM_CUSTOM_MOUSE;
    procedure Beeps;
  end;

  procedure HookMouse(const AppHandle: HWnd); stdcall external 'MouseHook.dll';
  procedure UnhookMouse; stdcall external 'MouseHook.dll';


Note that we now declare a method named HandleCustomMouseMsg which will be fired whenever the main form receives windows message with ID WM_CUSTOM_MOUSE. Implement that method goes like:

procedure TForm1.HandleCustomMouseMsg(var AMsg: TMessage);

  function IsMouseWithinOurRange: Boolean;
  var
    i: Integer;
  begin
    Result := False;
    for i := 0 to Screen.FormCount-1 do
      with Screen.Forms[i] do
        if Visible then
        begin
          Result := (AMsg.WParam >= Left)
                    and (AMsg.WParam <= Left + Width)
                    and (AMsg.LParam >= Top)
                    and (AMsg.LParam <= Top + Height);

          if Result then Break;
        end;
  end;

begin
  if IsMouseWithinOurRange then
  begin
    // if we had handled this, just exit
    if FMouseOver then Exit;
    FMouseOver := True;
    Beeps;
  end
  else
    // mark that mouse is not over any of our forms
    FMouseOver := False;
end;

Whenever we receive WM_CUSTOM_MOUSE message from our dll, it will contain mouse cursor coordinate. The X part passed in WParam part of the message, and the Y part passed in LParam part of the message. So now we can check (using local function IsMouseWithinOurRange) if the coordinate is above one of our forms or not. If it is, then we play beeps.

The Beeps method is simply playing a series of Beep. :)

procedure TForm1.Beeps;
begin
  Beep;
  Beep;
  Beep;
end;

For the OnClick event handler of "Install Mouse Hook" put the following codes:

procedure TForm1.Button1Click(Sender: TObject);
begin
  HookMouse(Self.Handle);
end;

It tells our dll to install the mouse hook and to send the windows message notifications to our window handle.

And lastly the button of "Uninstall Mouse Hook" we give the following code for its OnClick event.

procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnhookMouse;
end;

Testing

After compiling both projects (the dll and exe) make sure that they both in the same folder before running the exe. When the application is running, click the "Install Mouse Hook", then activate other application. Make sure our application completely covered by window from other application. Then move around the mouse. You will notice that there will be beeps sound whenever the mouse is over one of your forms, even if it's completely covered.

The zipped source codes for the demo project is attached. Would love to hear your feedback, guys!

Cheers!

Attached Files


Edited by LuthfiHakim, 12 November 2010 - 10:59 PM.

  • 0

#2 Saeed7007

Saeed7007

    CC Regular

  • Member
  • PipPipPip
  • 43 posts

Posted 29 March 2012 - 03:24 AM

does you app hook whether the mouse is clicked or not?
  • 0

#3 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 29 March 2012 - 12:00 PM

does you app hook whether the mouse is clicked or not?


I don't quite understand what you are asking.

If you are asking whether the hook is "activated" by mouse clicking, then not necessarily. In the sample application, the hook is activated by clicking the Install Mouse Hook button. So yes, it looks like the hooks is activated by mouse click. But only if the mouse click is done on Install Mouse Hook. Furthermore, you can just tabbed to button Install Mouse Hook then press keyboard button Space to get the same effect.
  • 0





Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download