Solved

Hooking mouse movement

Posted on 2004-09-06
9
1,464 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 34

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
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 34

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 34

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 34

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

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
In a recent question (https://www.experts-exchange.com/questions/29004105/Run-AutoHotkey-script-directly-from-Notepad.html) here at Experts Exchange, a member asked how to run an AutoHotkey script (.AHK) directly from Notepad++ (aka NPP). This video…

685 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