xref: /reactos/win32ss/user/ntuser/mouse.c (revision 682f85ad)
1 /*
2  * COPYRIGHT:        See COPYING in the top level directory
3  * PROJECT:          ReactOS kernel
4  * PURPOSE:          Mouse functions
5  * FILE:             win32ss/user/ntuser/mouse.c
6  * PROGRAMERS:       Casper S. Hornstrup (chorns@users.sourceforge.net)
7  *                   Rafal Harabien (rafalh@reactos.org)
8  */
9 
10 #include <win32k.h>
11 DBG_DEFAULT_CHANNEL(UserInput);
12 
13 MOUSEMOVEPOINT gMouseHistoryOfMoves[64];
14 INT gcMouseHistoryOfMoves = 0;
15 
16 /*
17  * UserGetMouseButtonsState
18  *
19  * Returns bitfield of MK_* flags used in mouse messages
20  */
21 WORD FASTCALL
22 UserGetMouseButtonsState(VOID)
23 {
24     WORD wRet = 0;
25 
26     wRet = IntGetSysCursorInfo()->ButtonsDown;
27 
28     if (IS_KEY_DOWN(gafAsyncKeyState, VK_SHIFT)) wRet |= MK_SHIFT;
29     if (IS_KEY_DOWN(gafAsyncKeyState, VK_CONTROL)) wRet |= MK_CONTROL;
30 
31     return wRet;
32 }
33 
34 /*
35  * UserProcessMouseInput
36  *
37  * Process raw mouse input data
38  */
39 VOID NTAPI
40 UserProcessMouseInput(PMOUSE_INPUT_DATA mid)
41 {
42     MOUSEINPUT mi;
43 
44     /* Convert MOUSE_INPUT_DATA to MOUSEINPUT. First init all fields. */
45     mi.dx = mid->LastX;
46     mi.dy = mid->LastY;
47     mi.mouseData = 0;
48     mi.dwFlags = 0;
49     mi.time = 0;
50     mi.dwExtraInfo = mid->ExtraInformation;
51 
52     /* Mouse position */
53     if (mi.dx != 0 || mi.dy != 0)
54         mi.dwFlags |= MOUSEEVENTF_MOVE;
55 
56     /* Flags for absolute move */
57     if (mid->Flags & MOUSE_MOVE_ABSOLUTE)
58         mi.dwFlags |= MOUSEEVENTF_ABSOLUTE;
59     if (mid->Flags & MOUSE_VIRTUAL_DESKTOP)
60         mi.dwFlags |= MOUSEEVENTF_VIRTUALDESK;
61 
62     /* Left button */
63     if (mid->ButtonFlags & MOUSE_LEFT_BUTTON_DOWN)
64         mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
65     if (mid->ButtonFlags & MOUSE_LEFT_BUTTON_UP)
66         mi.dwFlags |= MOUSEEVENTF_LEFTUP;
67 
68     /* Middle button */
69     if (mid->ButtonFlags & MOUSE_MIDDLE_BUTTON_DOWN)
70         mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
71     if (mid->ButtonFlags & MOUSE_MIDDLE_BUTTON_UP)
72         mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;
73 
74     /* Right button */
75     if (mid->ButtonFlags & MOUSE_RIGHT_BUTTON_DOWN)
76         mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;
77     if (mid->ButtonFlags & MOUSE_RIGHT_BUTTON_UP)
78         mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
79 
80     /* Note: Next buttons use mouseData field so they cannot be sent in one call */
81 
82     /* Button 4 */
83     if (mid->ButtonFlags & MOUSE_BUTTON_4_DOWN)
84     {
85         mi.dwFlags |= MOUSEEVENTF_XDOWN;
86         mi.mouseData |= XBUTTON1;
87     }
88     if (mid->ButtonFlags & MOUSE_BUTTON_4_UP)
89     {
90         mi.dwFlags |= MOUSEEVENTF_XUP;
91         mi.mouseData |= XBUTTON1;
92     }
93 
94     /* If mouseData is used by button 4, send input and clear mi */
95     if (mi.dwFlags & (MOUSE_BUTTON_4_DOWN | MOUSE_BUTTON_4_UP))
96     {
97         UserSendMouseInput(&mi, FALSE);
98         RtlZeroMemory(&mi, sizeof(mi));
99     }
100 
101     /* Button 5 */
102     if (mid->ButtonFlags & MOUSE_BUTTON_5_DOWN)
103     {
104         mi.mouseData |= XBUTTON2;
105         mi.dwFlags |= MOUSEEVENTF_XDOWN;
106     }
107     if (mid->ButtonFlags & MOUSE_BUTTON_5_UP)
108     {
109         mi.mouseData |= XBUTTON2;
110         mi.dwFlags |= MOUSEEVENTF_XUP;
111     }
112 
113     /* If mouseData is used by button 5, send input and clear mi */
114     if (mi.dwFlags & (MOUSE_BUTTON_5_DOWN | MOUSE_BUTTON_5_UP))
115     {
116         UserSendMouseInput(&mi, FALSE);
117         RtlZeroMemory(&mi, sizeof(mi));
118     }
119 
120     /* Mouse wheel */
121     if (mid->ButtonFlags & MOUSE_WHEEL)
122     {
123         mi.mouseData = mid->ButtonData;
124         mi.dwFlags |= MOUSEEVENTF_WHEEL;
125     }
126 
127     /* If something has changed, send input to user */
128     if (mi.dwFlags)
129         UserSendMouseInput(&mi, FALSE);
130 }
131 
132 /*
133  * IntFixMouseInputButtons
134  *
135  * Helper function for supporting mouse button swap function
136  */
137 DWORD
138 IntFixMouseInputButtons(DWORD dwFlags)
139 {
140     DWORD dwNewFlags;
141 
142     if (!gspv.bMouseBtnSwap)
143         return dwFlags;
144 
145     /* Buttons other than left and right are not affected */
146     dwNewFlags = dwFlags & ~(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP |
147                              MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP);
148 
149     /* Swap buttons */
150     if (dwFlags & MOUSEEVENTF_LEFTDOWN)
151         dwNewFlags |= MOUSEEVENTF_RIGHTDOWN;
152     if (dwFlags & MOUSEEVENTF_LEFTUP)
153         dwNewFlags |= MOUSEEVENTF_RIGHTUP;
154     if (dwFlags & MOUSEEVENTF_RIGHTDOWN)
155         dwNewFlags |= MOUSEEVENTF_LEFTDOWN;
156     if (dwFlags & MOUSEEVENTF_RIGHTUP)
157         dwNewFlags |= MOUSEEVENTF_LEFTUP;
158 
159     return dwNewFlags;
160 }
161 
162 /*
163  * UserSendMouseInput
164  *
165  * Process mouse input from input devices and SendInput API
166  */
167 BOOL NTAPI
168 UserSendMouseInput(MOUSEINPUT *pmi, BOOL bInjected)
169 {
170     POINT ptCursor;
171     PSYSTEM_CURSORINFO pCurInfo;
172     MSG Msg;
173     DWORD dwFlags;
174 
175     ASSERT(pmi);
176 
177     pCurInfo = IntGetSysCursorInfo();
178     ptCursor = gpsi->ptCursor;
179     dwFlags = IntFixMouseInputButtons(pmi->dwFlags);
180 
181     gppiInputProvider = ((PTHREADINFO)PsGetCurrentThreadWin32Thread())->ppi;
182 
183     if (pmi->dwFlags & MOUSEEVENTF_MOVE)
184     {
185         /* Mouse has changes position */
186         if (!(pmi->dwFlags & MOUSEEVENTF_ABSOLUTE))
187         {
188             /* Relative move */
189             ptCursor.x += pmi->dx;
190             ptCursor.y += pmi->dy;
191         }
192         else if (pmi->dwFlags & MOUSEEVENTF_VIRTUALDESK)
193         {
194             /* Absolute move in virtual screen units */
195             ptCursor.x = pmi->dx * UserGetSystemMetrics(SM_CXVIRTUALSCREEN) >> 16;
196             ptCursor.y = pmi->dy * UserGetSystemMetrics(SM_CYVIRTUALSCREEN) >> 16;
197         }
198         else
199         {
200             /* Absolute move in primary monitor units */
201             ptCursor.x = pmi->dx * UserGetSystemMetrics(SM_CXSCREEN) >> 16;
202             ptCursor.y = pmi->dy * UserGetSystemMetrics(SM_CYSCREEN) >> 16;
203         }
204     }
205 
206     /* Init message fields */
207     Msg.wParam = UserGetMouseButtonsState();
208     Msg.lParam = MAKELPARAM(ptCursor.x, ptCursor.y);
209     Msg.pt = ptCursor;
210     Msg.time = pmi->time;
211     if (!Msg.time)
212     {
213         Msg.time = EngGetTickCount32();
214     }
215 
216     /* Do GetMouseMovePointsEx FIFO. */
217     gMouseHistoryOfMoves[gcMouseHistoryOfMoves].x = ptCursor.x;
218     gMouseHistoryOfMoves[gcMouseHistoryOfMoves].y = ptCursor.y;
219     gMouseHistoryOfMoves[gcMouseHistoryOfMoves].time = Msg.time;
220     gMouseHistoryOfMoves[gcMouseHistoryOfMoves].dwExtraInfo = pmi->dwExtraInfo;
221     if (++gcMouseHistoryOfMoves == ARRAYSIZE(gMouseHistoryOfMoves))
222        gcMouseHistoryOfMoves = 0; // 0 - 63 is 64, FIFO forwards.
223 
224     /* Update cursor position */
225     if (dwFlags & MOUSEEVENTF_MOVE)
226     {
227         UserSetCursorPos(ptCursor.x, ptCursor.y, bInjected, pmi->dwExtraInfo, TRUE);
228     }
229 
230     if (IS_KEY_DOWN(gafAsyncKeyState, VK_SHIFT))
231         pCurInfo->ButtonsDown |= MK_SHIFT;
232     else
233         pCurInfo->ButtonsDown &= ~MK_SHIFT;
234 
235     if (IS_KEY_DOWN(gafAsyncKeyState, VK_CONTROL))
236         pCurInfo->ButtonsDown |= MK_CONTROL;
237     else
238         pCurInfo->ButtonsDown &= ~MK_CONTROL;
239 
240     /* Left button */
241     if (dwFlags & MOUSEEVENTF_LEFTDOWN)
242     {
243         SET_KEY_DOWN(gafAsyncKeyState, VK_LBUTTON, TRUE);
244         Msg.message = WM_LBUTTONDOWN;
245         pCurInfo->ButtonsDown |= MK_LBUTTON;
246         Msg.wParam |= MK_LBUTTON;
247         co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
248     }
249     else if (dwFlags & MOUSEEVENTF_LEFTUP)
250     {
251         SET_KEY_DOWN(gafAsyncKeyState, VK_LBUTTON, FALSE);
252         Msg.message = WM_LBUTTONUP;
253         pCurInfo->ButtonsDown &= ~MK_LBUTTON;
254         Msg.wParam &= ~MK_LBUTTON;
255         co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
256     }
257 
258     /* Middle button */
259     if (dwFlags & MOUSEEVENTF_MIDDLEDOWN)
260     {
261         SET_KEY_DOWN(gafAsyncKeyState, VK_MBUTTON, TRUE);
262         Msg.message = WM_MBUTTONDOWN;
263         pCurInfo->ButtonsDown |= MK_MBUTTON;
264         Msg.wParam |= MK_MBUTTON;
265         co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
266     }
267     else if (dwFlags & MOUSEEVENTF_MIDDLEUP)
268     {
269         SET_KEY_DOWN(gafAsyncKeyState, VK_MBUTTON, FALSE);
270         Msg.message = WM_MBUTTONUP;
271         pCurInfo->ButtonsDown &= ~MK_MBUTTON;
272         Msg.wParam &= ~MK_MBUTTON;
273         co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
274     }
275 
276     /* Right button */
277     if (dwFlags & MOUSEEVENTF_RIGHTDOWN)
278     {
279         SET_KEY_DOWN(gafAsyncKeyState, VK_RBUTTON, TRUE);
280         Msg.message = WM_RBUTTONDOWN;
281         pCurInfo->ButtonsDown |= MK_RBUTTON;
282         Msg.wParam |= MK_RBUTTON;
283         co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
284     }
285     else if (dwFlags & MOUSEEVENTF_RIGHTUP)
286     {
287         SET_KEY_DOWN(gafAsyncKeyState, VK_RBUTTON, FALSE);
288         Msg.message = WM_RBUTTONUP;
289         pCurInfo->ButtonsDown &= ~MK_RBUTTON;
290         Msg.wParam &= ~MK_RBUTTON;
291         co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
292     }
293 
294     if((dwFlags & (MOUSEEVENTF_XDOWN | MOUSEEVENTF_XUP)) &&
295        (dwFlags & MOUSEEVENTF_WHEEL))
296     {
297         /* Fail because both types of events use the mouseData field */
298         WARN("Invalid flags!\n");
299         return FALSE;
300     }
301 
302     /* X-Button (4 or 5) */
303     if (dwFlags & MOUSEEVENTF_XDOWN)
304     {
305         Msg.message = WM_XBUTTONDOWN;
306         if (pmi->mouseData & XBUTTON1)
307         {
308             SET_KEY_DOWN(gafAsyncKeyState, VK_XBUTTON1, TRUE);
309             pCurInfo->ButtonsDown |= MK_XBUTTON1;
310             Msg.wParam |= MAKEWPARAM(MK_XBUTTON1, XBUTTON1);
311             co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
312         }
313         if (pmi->mouseData & XBUTTON2)
314         {
315             SET_KEY_DOWN(gafAsyncKeyState, VK_XBUTTON2, TRUE);
316             pCurInfo->ButtonsDown |= MK_XBUTTON2;
317             Msg.wParam |= MAKEWPARAM(MK_XBUTTON2, XBUTTON2);
318             co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
319         }
320     }
321     else if (dwFlags & MOUSEEVENTF_XUP)
322     {
323         Msg.message = WM_XBUTTONUP;
324         if(pmi->mouseData & XBUTTON1)
325         {
326             SET_KEY_DOWN(gafAsyncKeyState, VK_XBUTTON1, FALSE);
327             pCurInfo->ButtonsDown &= ~MK_XBUTTON1;
328             Msg.wParam &= ~MK_XBUTTON1;
329             Msg.wParam |= MAKEWPARAM(0, XBUTTON2);
330             co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
331         }
332         if (pmi->mouseData & XBUTTON2)
333         {
334             SET_KEY_DOWN(gafAsyncKeyState, VK_XBUTTON2, FALSE);
335             pCurInfo->ButtonsDown &= ~MK_XBUTTON2;
336             Msg.wParam &= ~MK_XBUTTON2;
337             Msg.wParam |= MAKEWPARAM(0, XBUTTON2);
338             co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
339         }
340     }
341 
342     /* Mouse wheel */
343     if (dwFlags & MOUSEEVENTF_WHEEL)
344     {
345         Msg.message = WM_MOUSEWHEEL;
346         Msg.wParam = MAKEWPARAM(pCurInfo->ButtonsDown, pmi->mouseData);
347         co_MsqInsertMouseMessage(&Msg, bInjected, pmi->dwExtraInfo, TRUE);
348     }
349 
350     return TRUE;
351 }
352 
353 VOID
354 FASTCALL
355 IntRemoveTrackMouseEvent(
356     PDESKTOP pDesk)
357 {
358     /* Generate a leave message */
359     if (pDesk->dwDTFlags & DF_TME_LEAVE)
360     {
361         UINT uMsg = (pDesk->htEx != HTCLIENT) ? WM_NCMOUSELEAVE : WM_MOUSELEAVE;
362         UserPostMessage(UserHMGetHandle(pDesk->spwndTrack), uMsg, 0, 0);
363     }
364     /* Kill the timer */
365     if (pDesk->dwDTFlags & DF_TME_HOVER)
366         IntKillTimer(pDesk->spwndTrack, ID_EVENT_SYSTIMER_MOUSEHOVER, TRUE);
367 
368     /* Reset state */
369     pDesk->dwDTFlags &= ~(DF_TME_LEAVE|DF_TME_HOVER);
370     pDesk->spwndTrack = NULL;
371 }
372 
373 BOOL
374 FASTCALL
375 IntQueryTrackMouseEvent(
376     LPTRACKMOUSEEVENT lpEventTrack)
377 {
378     PDESKTOP pDesk;
379     PTHREADINFO pti;
380 
381     pti = PsGetCurrentThreadWin32Thread();
382     pDesk = pti->rpdesk;
383 
384     /* Always cleared with size set and return true. */
385     RtlZeroMemory(lpEventTrack , sizeof(TRACKMOUSEEVENT));
386     lpEventTrack->cbSize = sizeof(TRACKMOUSEEVENT);
387 
388     if (pDesk->dwDTFlags & (DF_TME_LEAVE | DF_TME_HOVER) &&
389         pDesk->spwndTrack &&
390         pti->MessageQueue == pDesk->spwndTrack->head.pti->MessageQueue)
391     {
392         if (pDesk->htEx != HTCLIENT)
393             lpEventTrack->dwFlags |= TME_NONCLIENT;
394 
395         if (pDesk->dwDTFlags & DF_TME_LEAVE)
396             lpEventTrack->dwFlags |= TME_LEAVE;
397 
398         if (pDesk->dwDTFlags & DF_TME_HOVER)
399         {
400             lpEventTrack->dwFlags |= TME_HOVER;
401             lpEventTrack->dwHoverTime = pDesk->dwMouseHoverTime;
402         }
403         lpEventTrack->hwndTrack = UserHMGetHandle(pDesk->spwndTrack);
404     }
405     return TRUE;
406 }
407 
408 BOOL
409 FASTCALL
410 IntTrackMouseEvent(
411     LPTRACKMOUSEEVENT lpEventTrack)
412 {
413     PDESKTOP pDesk;
414     PTHREADINFO pti;
415     PWND pWnd;
416     POINT point;
417 
418     pti = PsGetCurrentThreadWin32Thread();
419     pDesk = pti->rpdesk;
420 
421     if (!(pWnd = UserGetWindowObject(lpEventTrack->hwndTrack)))
422         return FALSE;
423 
424     if ( pDesk->spwndTrack != pWnd ||
425             (pDesk->htEx != HTCLIENT) ^ !!(lpEventTrack->dwFlags & TME_NONCLIENT) )
426     {
427         if ( lpEventTrack->dwFlags & TME_LEAVE && !(lpEventTrack->dwFlags & TME_CANCEL) )
428         {
429             UserPostMessage( lpEventTrack->hwndTrack,
430                              lpEventTrack->dwFlags & TME_NONCLIENT ? WM_NCMOUSELEAVE : WM_MOUSELEAVE,
431                              0, 0);
432         }
433         TRACE("IntTrackMouseEvent spwndTrack %p pwnd %p\n", pDesk->spwndTrack, pWnd);
434         return TRUE;
435     }
436 
437     /* Tracking spwndTrack same as pWnd */
438     if (lpEventTrack->dwFlags & TME_CANCEL) // Canceled mode.
439     {
440         if (lpEventTrack->dwFlags & TME_LEAVE)
441             pDesk->dwDTFlags &= ~DF_TME_LEAVE;
442 
443         if (lpEventTrack->dwFlags & TME_HOVER)
444         {
445             if (pDesk->dwDTFlags & DF_TME_HOVER)
446             {   // Kill hover timer.
447                 IntKillTimer(pWnd, ID_EVENT_SYSTIMER_MOUSEHOVER, TRUE);
448                 pDesk->dwDTFlags &= ~DF_TME_HOVER;
449             }
450         }
451     }
452     else // Not Canceled.
453     {
454         if (lpEventTrack->dwFlags & TME_LEAVE)
455             pDesk->dwDTFlags |= DF_TME_LEAVE;
456 
457         if (lpEventTrack->dwFlags & TME_HOVER)
458         {
459             pDesk->dwDTFlags |= DF_TME_HOVER;
460 
461             if (!lpEventTrack->dwHoverTime || lpEventTrack->dwHoverTime == HOVER_DEFAULT)
462                 pDesk->dwMouseHoverTime = gspv.iMouseHoverTime; // use the system default hover time-out.
463             else
464                 pDesk->dwMouseHoverTime = lpEventTrack->dwHoverTime;
465             // Start timer for the hover period.
466             IntSetTimer(pWnd, ID_EVENT_SYSTIMER_MOUSEHOVER, pDesk->dwMouseHoverTime, SystemTimerProc, TMRF_SYSTEM);
467             // Get windows thread message points.
468             point = pWnd->head.pti->ptLast;
469             // Set desktop mouse hover from the system default hover rectangle.
470             RECTL_vSetRect(&pDesk->rcMouseHover,
471                            point.x - gspv.iMouseHoverWidth  / 2,
472                            point.y - gspv.iMouseHoverHeight / 2,
473                            point.x + gspv.iMouseHoverWidth  / 2,
474                            point.y + gspv.iMouseHoverHeight / 2);
475         }
476     }
477     return TRUE;
478 }
479 
480 BOOL
481 APIENTRY
482 NtUserTrackMouseEvent(
483     LPTRACKMOUSEEVENT lpEventTrack)
484 {
485     TRACKMOUSEEVENT SafeTME;
486     BOOL bRet = FALSE;
487 
488     TRACE("Enter NtUserTrackMouseEvent\n");
489 
490     _SEH2_TRY
491     {
492         ProbeForRead(lpEventTrack, sizeof(TRACKMOUSEEVENT), 1);
493         RtlCopyMemory(&SafeTME, lpEventTrack, sizeof(TRACKMOUSEEVENT));
494     }
495     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
496     {
497         SetLastNtError(_SEH2_GetExceptionCode());
498         _SEH2_YIELD(return FALSE);
499     }
500     _SEH2_END;
501 
502     if (SafeTME.cbSize != sizeof(TRACKMOUSEEVENT))
503     {
504         EngSetLastError(ERROR_INVALID_PARAMETER);
505         return FALSE;
506     }
507 
508     if (SafeTME.dwFlags & ~(TME_CANCEL | TME_QUERY | TME_NONCLIENT | TME_LEAVE | TME_HOVER) )
509     {
510         EngSetLastError(ERROR_INVALID_FLAGS);
511         return FALSE;
512     }
513 
514     UserEnterExclusive();
515 
516     if (SafeTME.dwFlags & TME_QUERY)
517     {
518         bRet = IntQueryTrackMouseEvent(&SafeTME);
519         _SEH2_TRY
520         {
521             ProbeForWrite(lpEventTrack, sizeof(TRACKMOUSEEVENT), 1);
522             RtlCopyMemory(lpEventTrack, &SafeTME, sizeof(TRACKMOUSEEVENT));
523         }
524         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
525         {
526             SetLastNtError(_SEH2_GetExceptionCode());
527             bRet = FALSE;
528         }
529         _SEH2_END;
530     }
531     else
532     {
533         bRet = IntTrackMouseEvent(&SafeTME);
534     }
535 
536     TRACE("Leave NtUserTrackMouseEvent, ret=%i\n", bRet);
537     UserLeave();
538     return bRet;
539 }
540 
541 DWORD
542 APIENTRY
543 NtUserGetMouseMovePointsEx(
544     UINT cbSize,
545     LPMOUSEMOVEPOINT lpptIn,
546     LPMOUSEMOVEPOINT lpptOut,
547     int nBufPoints,
548     DWORD resolution)
549 {
550     MOUSEMOVEPOINT Safeppt;
551     //BOOL Hit;
552     INT iRet = -1;
553 
554     TRACE("Enter NtUserGetMouseMovePointsEx\n");
555 
556     if ((cbSize != sizeof(MOUSEMOVEPOINT)) || (nBufPoints < 0) || (nBufPoints > 64))
557     {
558         EngSetLastError(ERROR_INVALID_PARAMETER);
559         return (DWORD)-1;
560     }
561 
562     if (!lpptIn || (!lpptOut && nBufPoints))
563     {
564         EngSetLastError(ERROR_NOACCESS);
565         return (DWORD)-1;
566     }
567 
568     _SEH2_TRY
569     {
570         ProbeForRead(lpptIn, cbSize, 1);
571         RtlCopyMemory(&Safeppt, lpptIn, cbSize);
572     }
573     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
574     {
575         SetLastNtError(_SEH2_GetExceptionCode());
576         _SEH2_YIELD(return (DWORD)-1);
577     }
578     _SEH2_END;
579 
580     UserEnterShared();
581 
582     // http://msdn.microsoft.com/en-us/library/ms646259(v=vs.85).aspx
583     // This explains the math issues in transforming points.
584     iRet = gcMouseHistoryOfMoves; // FIFO is forward so retrieve backward.
585     //Hit = FALSE;
586     do
587     {
588         if (Safeppt.x == 0 && Safeppt.y == 0)
589             break; // No test.
590         // Finds the point, it returns the last nBufPoints prior to and including the supplied point.
591         if (gMouseHistoryOfMoves[iRet].x == Safeppt.x && gMouseHistoryOfMoves[iRet].y == Safeppt.y)
592         {
593             if (Safeppt.time) // Now test time and it seems to be absolute.
594             {
595                 if (Safeppt.time == gMouseHistoryOfMoves[iRet].time)
596                 {
597                     //Hit = TRUE;
598                     break;
599                 }
600                 else
601                 {
602                     if (--iRet < 0) iRet = 63;
603                     continue;
604                 }
605             }
606             //Hit = TRUE;
607             break;
608         }
609         if (--iRet < 0) iRet = 63;
610     }
611     while (iRet != gcMouseHistoryOfMoves);
612 
613     switch(resolution)
614     {
615         case GMMP_USE_DISPLAY_POINTS:
616             if (nBufPoints)
617             {
618                 _SEH2_TRY
619                 {
620                     ProbeForWrite(lpptOut, cbSize, 1);
621                 }
622                 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
623                 {
624                     SetLastNtError(_SEH2_GetExceptionCode());
625                     iRet = -1;
626                     _SEH2_YIELD(goto cleanup);
627                 }
628                 _SEH2_END;
629             }
630             iRet = nBufPoints;
631             break;
632         case GMMP_USE_HIGH_RESOLUTION_POINTS:
633             break;
634         default:
635             EngSetLastError(ERROR_POINT_NOT_FOUND);
636             iRet = -1;
637     }
638 
639 cleanup:
640     TRACE("Leave NtUserGetMouseMovePointsEx, ret=%i\n", iRet);
641     UserLeave();
642     return (DWORD)iRet;
643 }
644