2004. október 15., péntek

How to trap mouse clicks on the Desktop when using a system wide mouse hook


Problem/Question/Abstract:

Does anyone know how to tell (in Delphi code) if I have clicked on the Desktop (not an icon). I have written a system wide mouse hook program but the window handle and the icon handle are the same. How can I tell the difference?

Answer:

I did this by creating a DLL (you can only hook into the desktop via a DLL). The DLL then posts messages to the main application. You need to load the DLL, call Initialize supplying the applications handle (note: StdCall). You then need to assign a custom message handler (application.OnMessage) to listen for the messages posted from the DLL.

Here is the application message handler:

const
  WM_DESKTOPMOUSEMESSAGE = WM_USER + 1;

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  case Msg of
    WM_DESKTOPMOUSEMESSAGE:
      case Msg.WParam of
        WM_LBUTTONUP: ShowMessage('You clicked on the desktop');
      end;
  end;

procedure TForm1.OnCreate(Sender: TObject);
begin
  Application.OnMessage := AppMessage;
end;


Here is the hook code:


Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL - even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters.

library test;

uses
  SysUtils, Messages, Windows;

{$R *.RES}

const
  WM_DESKTOPMOUSEMESSAGE = WM_USER + 1;

var
  HookHandle: HHook;
  DesktopHandle: HWnd;
  AppHandle: HWnd;

procedure log(logstr: string);
var
  F1: Textfile;
begin
  AssignFile(F1, 'c:\temp.log');
  if FileExists('c:\temp.log') then
    Append(F1)
  else
    Rewrite(F1);
  writeln(F1, logstr);
  CloseFile(F1);
end;

function MouseHook(code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT stdcall;
var
  WinDir: array[0..MAX_PATH] of Char;
  f: file of HWnd;
begin
  {This only happens once, we use the file just to get the variable across to the systems memory}
  if AppHandle = 0 then
  begin
    GetWindowsDirectory(Windir, MAX_PATH);
    AssignFile(f, WinDir + '\ah.dat');
    Reset(f);
    Read(f, AppHandle);
    CloseFile(f);
  end;
  PostMessage(AppHandle, WM_DESKTOPMOUSEMESSAGE, wParam, lParam);
  Result := CallNextHookEx(HookHandle, Code, WParam, LParam);
end;

procedure Initialize(ApplicationHandle: HWnd); stdcall;
var
  res, pid: DWORD;
  f: file of HWnd;
  WinDir: array[0..MAX_PATH] of Char;
begin
  {Write the application handle to a file so that it can be read first time round
        by the hook (the hook has its own memory space)}
  Fillchar(windir, sizeOf(WinDir), 0);
  GetWindowsDirectory(Windir, MAX_PATH);
  AssignFile(f, WinDir + '\ah.dat');
  Rewrite(f);
  Write(f, ApplicationHandle);
  CloseFile(f);
  DesktopHandle := FindWindow(nil, 'Program Manager');
  if DesktopHandle = 0 then
    HookHandle := 0
  else
  begin
    AppHandle := ApplicationHandle;
    res := GetWindowThreadProcessID(DesktopHandle, @pid);
    HookHandle := SetWindowsHookEx(WH_MOUSE, @MouseHook, hInstance, res);
  end;
end;

procedure DeInitialize; stdcall;
begin
  if HookHandle <> 0 then
    UnHookWindowsHookEx(HookHandle);
end;

exports
  Initialize,
  DeInitialize;

begin
end.

Nincsenek megjegyzések:

Megjegyzés küldése