Solved

Hooking mouse movement

Posted on 2004-09-06
9
1,439 Views
Last Modified: 2010-05-18
This is a followup to this question:

http://www.experts-exchange.com/Programming/Programming_Languages/Delphi/Q_21116276.html

I have modified the library to also track mouse movement, so that (useless? ;)) numbers like mouse distance travelled, current mouse position etc. can be obtained.

The mouse hook procedure now looks like this:


// ======================================================
function MouseHookFunc(Code, wParam, MouseStrut: Integer): Integer; stdcall;
var
    ms:PMouseHookStruct;
    screen:HDC;
begin
    {this is the Mouse Hook function, I call the CallNextHookEx first because
    you do not need to block the Mouse events}
    Result := CallNextHookEx(0, Code, wParam, MouseStrut);

    if Code < 0 then Exit;

    if (Code = HC_ACTION) then begin
        {to make sure I get a Click and not a drag, I will get the Ticks in DTime}
        {if (wParam = WM_RBUTTONDOWN) or (wParam = WM_LBUTTONDOWN) or (wParam = WM_MBUTTONDOWN) then
            DTime := GetTickCount;}

        if (wParam = WM_LBUTTONUP) {and (GetTickCount - DTime < 400)} and
        not IsBadCodePtr(PHookRec1) then
            if PHookRec1.LeftBCount < MAXDWORD then  // do not go over range
                Inc(PHookRec1.LeftBCount);

        if (wParam = WM_RBUTTONUP) {and (GetTickCount - DTime < 400)} and
        not IsBadCodePtr(PHookRec1) then
            if PHookRec1.RightBCount < MAXDWORD then  // do not go over range
                Inc(PHookRec1.RightBCount);

        if (wParam = WM_MBUTTONUP) {and (GetTickCount - DTime < 400)} and
        not IsBadCodePtr(PHookRec1) then
            if PHookRec1.MiddleBCount < MAXDWORD then  // do not go over range
                Inc(PHookRec1.MiddleBCount);

        if (wParam = WM_MOUSEMOVE) and
        not IsBadCodePtr(PHookRec1) then begin

            ms := PMouseHookStruct(MouseStrut);

            // pixels
            PHookRec1.MouseDistance := PHookRec1.MouseDistance +
                sqrt( power(ms^.pt.X - PHookRec1.LastMouseX,2) + power(ms^.pt.Y - PHookRec1.LastMouseY,2)   );

            // millimeters
            screen := getdc(0);
            PHookRec1.MouseDistanceMM := PHookRec1.MouseDistanceMM +
                sqrt    (   power( (ms^.pt.X - PHookRec1.LastMouseX)*getdevicecaps(screen,HORZSIZE)/getdevicecaps(screen,HORZRES) ,2) +
                            power( (ms^.pt.Y - PHookRec1.LastMouseY)*getdevicecaps(screen,VERTSIZE)/getdevicecaps(screen,VERTRES) ,2)
                        );



            PHookRec1.LastMouseX := ms^.pt.X;
            PHookRec1.LastMouseY := ms^.pt.Y;

        end;


    end;
end;
// ======================================================

This is working great most of the time, except when I move my mouse very fast. eg. if I start my mouse in the upper-left corner of the screen and move it very quickly to the bottom-right corner, the reported Y-coordinate of the mouse is less than 1024 (my screen height).

Are there timing issues involved here? Is my MouseProc too complex and it is having trouble keeping up with the large number of mouse messages it has to process?
0
Comment
Question by:nem2k4
  • 4
  • 2
9 Comments
 
LVL 33

Accepted Solution

by:
Slick812 earned 50 total points
ID: 11993786
I can not do any testing to see if this is a problem or not, but I think the mouse movement messages are not suppose to really capture "All" of the mouse movements, just enough to show a resonable cursor tracking on your monitor, There is some kind of setup in the mouse driver, that will "Get" or "Capture" a mouse "Movement" at certain intervals, or maybe there's a pixel distance factor there also (you can also set your cursor movement speed to vary). So if you move your mouse very rapidly, either the mouse driver will miss some of the movements or some other system factor, will just skip the movement "Capture" to where the mouse singnals are for the last movememt sample. . .
You do alot of processing of distance, and this also may have some effect (I doubt it if your machine is faster than 2 Gig Hertz), , , So there may not be a cure for your problem, at least I do not know of one.
Also you are trying to get the millimeter movement, but your method WILL NOT WORK, , you are using the getdevicecaps(screen,HORZSIZE), this WILL NOT give you the correct size of the Monitor, the  getdevicecaps(screen,HORZSIZE)  is useful for printers, not for monitors, so you can leave out all of the code for the millimeter movement. . .


type
  PHookRec = ^THookRec;
  THookRec = packed Record
    KeyCount, LeftBCount, RightBCount, MiddleBCount,
    MouseDistance: Cardinal;
    LastMPt: TPoint;


function MouseHookFunc(Code, wParam: Integer; var MouseStrut: TMOUSEHOOKSTRUCT): Integer; stdcall;
var
dist1: Cardinal;
begin
Result := CallNextHookEx(0, Code, wParam, Integer(@MouseStrut));
if (Code < 0) or IsBadCodePtr(PHookRec1) then Exit;

if (Code = HC_ACTION)then
  begin
  if wParam = WM_LBUTTONUP then
    if PHookRec1.LeftBCount < MAXDWORD then  // do not go over range
        Inc(PHookRec1.LeftBCount);

  if wParam = WM_RBUTTONUP then
    if PHookRec1.RightBCount < MAXDWORD then  // do not go over range
        Inc(PHookRec1.RightBCount);

  if wParam = WM_MBUTTONUP then
    if PHookRec1.MiddleBCount < MAXDWORD then  // do not go over range
        Inc(PHookRec1.MiddleBCount);

  if wParam = WM_MOUSEMOVE then
    begin
    dist1 := Round(Sqrt(SumOfSquares([abs(MouseStrut.pt.y-PHookRec1.LastMPt.y),
      abs(MouseStrut.pt.x-PHookRec1.LastMPt.x)])));
    if PHookRec1.MouseDistance < MAXDWORD-dist1 then  // do not go over range
        PHookRec1.MouseDistance := PHookRec1.MouseDistance+ dist1;
    PHookRec1.LastMPt := MouseStrut.pt;
    end;
  end;
end;

 - - - - - - - - - - - - -  - - - - - -  - - - - - - - - - - - - - - - - - - - - -

and if you want to get the distance in Millimeters or inches, then you will need to Measure your monitor and do the math for the distance

Label2.Caption := IntTostr(Round(PHookRec1.MouseDistance/3.555))+' Millimeters';
 {I measured my monitor screen and it was 324 millimeters wide
 resolution was 1152x864 so 1152 / 324 is 3.555}
0
 

Author Comment

by:nem2k4
ID: 11993861
What about using a LowLevelMouseProc? Would that be more likely to be able to catch all mouse movement? I suppose I could always use GetCursorPos to get the mouse position but that might make things even slower or not help at all...

Are you sure getdevicecaps(screen,HORZSIZE) does not give correct (or "near-enough" - it doesn't have to be perfect) numbers for monitors? It gave the same result on 2 different 19" monitors, and slightly lower for my laptop screen (not sure how big it is, maybe 15" LCD?), I don't have a ruler here at work to measure the screens but I'll bring one in tomorrow to measure with.
0
 
LVL 13

Expert Comment

by:BlackTigerX
ID: 11999259
I think Slick812  is right about his comments, you can also change the mouse sensitivity in the control panel | mouse, make it go slower and you'll be able to capture all the movements... but you probably don't want that
0
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 33

Expert Comment

by:Slick812
ID: 11999507
???
I have never used (or did any information research) about  "LowLevelMouseProc"  (whatever that may mean), I guess you want to some how, get a mouse driver event (mouse move) at a more responsive (lower lever) system function, but I have never wanted, ,  or tried to do that, so I can not say anything about that, I feel lucky if I can get a system cursor movement Hook at all. . . .
You could write your own mouse driver I guess?, but I do not know enough about that sort of thing, to say if you can get what you want or not from doing your own mouse driver (It is well reported that Delphi can not do hardware drivers). . ,

as to the  getdevicecaps(screen,HORZSIZE), , I remember trying that years ago, and it did not work at all, but with the newer win system ( Win 2000 or Win XP), it may work now? if you try it and it works for you, then I was wrong, sorry

as far as the   MouseHookFunc   above, as far as I know, if you get the mouse move message, you get it, , , and if you do not, then you do not get it, I know of NOTHING to try to do to change it. . . . .
0
 
LVL 33

Expert Comment

by:Slick812
ID: 11999577
Oh, I do not think that you could call  GetCursorPos( ), when would you call that? and How? in a Timer event?
if you call it in the  WM_MOUSEMOVE then it should just give your the same position, I do not beleive it would give you any "Missed" mouse messages.
0
 

Author Comment

by:nem2k4
ID: 12000686
LowLevelMouseProc: http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/lowlevelmouseproc.asp?frame=true

According to MSDN, MouseProc:
"The MouseProc hook procedure is an application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function whenever an application calls the GetMessage or PeekMessage function and there is a mouse message to be processed."

And LowLevelMouseProc:
"The LowLevelMouseProc hook procedure is an application-defined or library-defined callback function used with the SetWindowsHookEx function. The system call this function every time a new mouse input event is about to be posted into a thread input queue. The mouse input can come from the local mouse driver or from calls to the mouse_event function. If the input comes from a call to mouse_event, the input was "injected". However, the WH_MOUSE_LL hook is not injected into another process. Instead, the context switches back to the process that installed the hook and it is called in its original context. Then the context switches back to the application that generated the event. "

Perhaps the problem is that the application calls GetMessage but there's no mouse input to process when you move the mouse very fast (GetMessage might be called on a timer basis?)

You're probably right about the GetCursorPos thing :)
0
 
LVL 33

Expert Comment

by:Slick812
ID: 12002273
I really have no Idea whatsoever about -
" there's no mouse input to process when you move the mouse very fast"

I have not used the  WH_MOUSE_LL   hook type, so I can not comment on it, I did read some of the MSDN web page that you gave the addy for, I could not see much in their description that would help you for this. . . it seemed like the WH_MOUSE_LL hook type would also get the  mouse_event  along with the normal mouse events, but I could not get anything as to MORE events or faster events? but thats just my first look, who knows, it may do just what you need?

also, , , I do not see how you are having problems, now that I have thought about it more. . .  even if it would "Skip" a move message the code has

PHookRec1.LastMPt := MouseStrut.pt;

which will remember the last point that it did get, and process the distance for that last point, reguardless of any "Skipped" messages. . .

are you sure about your processing distance code?. . . but this is mostly just head analisis, so it may not have any value

have you tried my code without the MouseDistanceMM  calculations (which may be a time comsumer and block some message)

sorry I do not have a soulution, but I have no ideas for a fix
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

757 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now