[PATCH] user32: Reimplement TrackMouseEvent.

Rafał Harabień rafalh1992 at o2.pl
Sat Nov 18 19:10:16 CST 2017


Improvements:
- Don't use timer for TME_LEAVE and react immediately on mouse motion.
It improves user experience for controls depending on this API like toolbar.
- Fail when called with invalid flags
- Hold tracking info for each win32 thread independently
- Merge tracking requests if possible (leave+hover)
- Return hover time 0 when hover tracking is disabled

Signed-off-by: Rafał Harabień <rafalh1992 at o2.pl>
---
 dlls/user32/input.c        | 228 ++++++++++++++++++++++++---------------------
 dlls/user32/message.c      |   5 +
 dlls/user32/tests/msg.c    | 165 ++++++++++++++++++++++++++++++++
 dlls/user32/user_private.h |  11 +++
 dlls/winex11.drv/event.c   |   2 +-
 dlls/winex11.drv/mouse.c   |  29 ++++++
 dlls/winex11.drv/window.c  |   2 +-
 dlls/winex11.drv/x11drv.h  |   1 +
 8 files changed, 335 insertions(+), 108 deletions(-)

diff --git a/dlls/user32/input.c b/dlls/user32/input.c
index c475b19..82c1d4f 100644
--- a/dlls/user32/input.c
+++ b/dlls/user32/input.c
@@ -1243,48 +1243,59 @@ BOOL WINAPI UnloadKeyboardLayout(HKL hkl)
     return USER_Driver->pUnloadKeyboardLayout(hkl);
 }
 
-typedef struct __TRACKINGLIST {
-    TRACKMOUSEEVENT tme;
-    POINT pos; /* center of hover rectangle */
-} _TRACKINGLIST;
 
-/* FIXME: move tracking stuff into a per thread data */
-static _TRACKINGLIST tracking_info;
-static UINT_PTR timer;
+void maybe_clean_tracking_info(struct tracking_info *tracking_info)
+{
+    if (!(tracking_info->tme.dwFlags & (TME_HOVER | TME_LEAVE)))
+    {
+        tracking_info->tme.hwndTrack = 0;
+        tracking_info->tme.dwFlags = 0;
+    }
+    if (!(tracking_info->tme.dwFlags & TME_HOVER))
+        tracking_info->tme.dwHoverTime = 0;
+}
 
-static void check_mouse_leave(HWND hwnd, int hittest)
+void check_mouse_leave(HWND hwnd, int hittest)
 {
-    if (tracking_info.tme.hwndTrack != hwnd)
+    struct tracking_info *tracking_info = &get_user_thread_info()->tracking_info;
+
+    TRACE("hwnd %p hittest %d\n", hwnd, hittest);
+
+    if (!(tracking_info->tme.dwFlags & TME_LEAVE))
+        return;
+
+    if (tracking_info->tme.hwndTrack != hwnd)
     {
-        if (tracking_info.tme.dwFlags & TME_NONCLIENT)
-            PostMessageW(tracking_info.tme.hwndTrack, WM_NCMOUSELEAVE, 0, 0);
+        if (tracking_info->tme.dwFlags & TME_NONCLIENT)
+            PostMessageW(tracking_info->tme.hwndTrack, WM_NCMOUSELEAVE, 0, 0);
         else
-            PostMessageW(tracking_info.tme.hwndTrack, WM_MOUSELEAVE, 0, 0);
+            PostMessageW(tracking_info->tme.hwndTrack, WM_MOUSELEAVE, 0, 0);
 
         /* remove the TME_LEAVE flag */
-        tracking_info.tme.dwFlags &= ~TME_LEAVE;
+        tracking_info->tme.dwFlags &= ~TME_LEAVE;
     }
     else
     {
         if (hittest == HTCLIENT)
         {
-            if (tracking_info.tme.dwFlags & TME_NONCLIENT)
+            if (tracking_info->tme.dwFlags & TME_NONCLIENT)
             {
-                PostMessageW(tracking_info.tme.hwndTrack, WM_NCMOUSELEAVE, 0, 0);
+                PostMessageW(tracking_info->tme.hwndTrack, WM_NCMOUSELEAVE, 0, 0);
                 /* remove the TME_LEAVE flag */
-                tracking_info.tme.dwFlags &= ~TME_LEAVE;
+                tracking_info->tme.dwFlags &= ~TME_LEAVE;
             }
         }
         else
         {
-            if (!(tracking_info.tme.dwFlags & TME_NONCLIENT))
+            if (!(tracking_info->tme.dwFlags & TME_NONCLIENT))
             {
-                PostMessageW(tracking_info.tme.hwndTrack, WM_MOUSELEAVE, 0, 0);
+                PostMessageW(tracking_info->tme.hwndTrack, WM_MOUSELEAVE, 0, 0);
                 /* remove the TME_LEAVE flag */
-                tracking_info.tme.dwFlags &= ~TME_LEAVE;
+                tracking_info->tme.dwFlags &= ~TME_LEAVE;
             }
         }
     }
+    maybe_clean_tracking_info(tracking_info);
 }
 
 static void CALLBACK TrackMouseEventProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent,
@@ -1292,6 +1303,7 @@ static void CALLBACK TrackMouseEventProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent,
 {
     POINT pos;
     INT hoverwidth = 0, hoverheight = 0, hittest;
+    struct tracking_info *tracking_info = &get_user_thread_info()->tracking_info;
 
     TRACE("hwnd %p, msg %04x, id %04lx, time %u\n", hwnd, uMsg, idEvent, dwTime);
 
@@ -1304,31 +1316,24 @@ static void CALLBACK TrackMouseEventProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent,
     SystemParametersInfoW(SPI_GETMOUSEHOVERHEIGHT, 0, &hoverheight, 0);
 
     TRACE("tracked pos %s, current pos %s, hover width %d, hover height %d\n",
-           wine_dbgstr_point(&tracking_info.pos), wine_dbgstr_point(&pos),
+           wine_dbgstr_point(&tracking_info->pos), wine_dbgstr_point(&pos),
            hoverwidth, hoverheight);
 
-    /* see if this tracking event is looking for TME_LEAVE and that the */
-    /* mouse has left the window */
-    if (tracking_info.tme.dwFlags & TME_LEAVE)
-    {
-        check_mouse_leave(hwnd, hittest);
-    }
-
-    if (tracking_info.tme.hwndTrack != hwnd)
+    if (tracking_info->tme.hwndTrack != hwnd)
     {
         /* mouse is gone, stop tracking mouse hover */
-        tracking_info.tme.dwFlags &= ~TME_HOVER;
+        tracking_info->tme.dwFlags &= ~TME_HOVER;
     }
 
     /* see if we are tracking hovering for this hwnd */
-    if (tracking_info.tme.dwFlags & TME_HOVER)
+    if (tracking_info->tme.dwFlags & TME_HOVER)
     {
         /* has the cursor moved outside the rectangle centered around pos? */
-        if ((abs(pos.x - tracking_info.pos.x) > (hoverwidth / 2)) ||
-            (abs(pos.y - tracking_info.pos.y) > (hoverheight / 2)))
+        if ((abs(pos.x - tracking_info->pos.x) > (hoverwidth / 2)) ||
+            (abs(pos.y - tracking_info->pos.y) > (hoverheight / 2)))
         {
             /* record this new position as the current position */
-            tracking_info.pos = pos;
+            tracking_info->pos = pos;
         }
         else
         {
@@ -1337,29 +1342,88 @@ static void CALLBACK TrackMouseEventProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent,
                 ScreenToClient(hwnd, &pos);
                 TRACE("client cursor pos %s\n", wine_dbgstr_point(&pos));
 
-                PostMessageW(tracking_info.tme.hwndTrack, WM_MOUSEHOVER,
+                PostMessageW(tracking_info->tme.hwndTrack, WM_MOUSEHOVER,
                              get_key_state(), MAKELPARAM( pos.x, pos.y ));
             }
             else
             {
-                if (tracking_info.tme.dwFlags & TME_NONCLIENT)
-                    PostMessageW(tracking_info.tme.hwndTrack, WM_NCMOUSEHOVER,
+                if (tracking_info->tme.dwFlags & TME_NONCLIENT)
+                    PostMessageW(tracking_info->tme.hwndTrack, WM_NCMOUSEHOVER,
                                  hittest, MAKELPARAM( pos.x, pos.y ));
             }
 
             /* stop tracking mouse hover */
-            tracking_info.tme.dwFlags &= ~TME_HOVER;
+            tracking_info->tme.dwFlags &= ~TME_HOVER;
         }
     }
 
     /* stop the timer if the tracking list is empty */
-    if (!(tracking_info.tme.dwFlags & (TME_HOVER | TME_LEAVE)))
+    if (!(tracking_info->tme.dwFlags & TME_HOVER))
     {
-        KillSystemTimer(tracking_info.tme.hwndTrack, timer);
-        timer = 0;
-        tracking_info.tme.hwndTrack = 0;
-        tracking_info.tme.dwFlags = 0;
-        tracking_info.tme.dwHoverTime = 0;
+        KillSystemTimer(tracking_info->tme.hwndTrack, tracking_info->timer);
+        tracking_info->timer = 0;
+    }
+    maybe_clean_tracking_info(tracking_info);
+}
+
+void track_mouse_event_internal(HWND hwnd_track, DWORD flags, DWORD hover_time)
+{
+    HWND hwnd;
+    POINT pos;
+    INT hittest;
+    struct tracking_info *tracking_info = &get_user_thread_info()->tracking_info;
+
+    if (flags & TME_HOVER)
+    {
+        /* if HOVER_DEFAULT was specified replace this with the system's current value.
+         * TME_LEAVE doesn't need to specify hover time so use default */
+        if (hover_time == HOVER_DEFAULT || hover_time == 0)
+            SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &hover_time, 0);
+    }
+    else
+        hover_time = 0;
+
+    if (flags & TME_CANCEL)
+    {
+        if (tracking_info->tme.hwndTrack == hwnd_track)
+        {
+            /* cancel old tracking request */
+            tracking_info->tme.dwFlags &= ~(flags & ~TME_CANCEL);
+
+            /* if we aren't tracking on hover or leave remove this entry */
+            if (!(tracking_info->tme.dwFlags & TME_HOVER) && tracking_info->timer)
+            {
+                KillSystemTimer(tracking_info->tme.hwndTrack, tracking_info->timer);
+                tracking_info->timer = 0;
+            }
+            maybe_clean_tracking_info(tracking_info);
+        }
+    } else {
+        GetCursorPos(&pos);
+        hwnd = WINPOS_WindowFromPoint(hwnd_track, pos, &hittest);
+        TRACE("point %s hwnd %p hittest %d\n", wine_dbgstr_point(&pos), hwnd, hittest);
+
+        if (hwnd_track != hwnd)
+            PostMessageW(hwnd_track, WM_MOUSELEAVE, 0, 0);
+        else
+        {
+            /* save tracking request */
+            tracking_info->tme.hwndTrack = hwnd_track;
+            tracking_info->tme.dwFlags |= flags;
+            if (flags & TME_HOVER)
+                tracking_info->tme.dwHoverTime = hover_time;
+
+            /* Initialize HoverInfo variables even if not hover tracking */
+            tracking_info->pos = pos;
+
+            if (flags & TME_HOVER)
+            {
+                /* recreate timer */
+                if (tracking_info->timer)
+                    KillSystemTimer(tracking_info->tme.hwndTrack, tracking_info->timer);
+                tracking_info->timer = SetSystemTimer(tracking_info->tme.hwndTrack, (UINT_PTR)&tracking_info->tme, hover_time, TrackMouseEventProc);
+            }
+        }
     }
 }
 
@@ -1391,8 +1455,9 @@ TrackMouseEvent (TRACKMOUSEEVENT *ptme)
 {
     HWND hwnd;
     POINT pos;
-    DWORD hover_time;
+    DWORD hover_time, invalid_flags;
     INT hittest;
+    struct tracking_info *tracking_info = &get_user_thread_info()->tracking_info;
 
     TRACE("%x, %x, %p, %u\n", ptme->cbSize, ptme->dwFlags, ptme->hwndTrack, ptme->dwHoverTime);
 
@@ -1402,10 +1467,18 @@ TrackMouseEvent (TRACKMOUSEEVENT *ptme)
         return FALSE;
     }
 
+    invalid_flags = ptme->dwFlags & ~(TME_QUERY | TME_CANCEL | TME_HOVER | TME_LEAVE | TME_NONCLIENT);
+    if (invalid_flags != 0)
+    {
+        WARN("Unknown flag(s) %08x\n", invalid_flags);
+        SetLastError(ERROR_INVALID_FLAGS);
+        return FALSE;
+    }
+
     /* fill the TRACKMOUSEEVENT struct with the current tracking for the given hwnd */
-    if (ptme->dwFlags & TME_QUERY )
+    if (ptme->dwFlags & TME_QUERY)
     {
-        *ptme = tracking_info.tme;
+        *ptme = tracking_info->tme;
         /* set cbSize in the case it's not initialized yet */
         ptme->cbSize = sizeof(TRACKMOUSEEVENT);
 
@@ -1418,68 +1491,11 @@ TrackMouseEvent (TRACKMOUSEEVENT *ptme)
         return FALSE;
     }
 
-    hover_time = (ptme->dwFlags & TME_HOVER) ? ptme->dwHoverTime : HOVER_DEFAULT;
-
-    /* if HOVER_DEFAULT was specified replace this with the system's current value.
-     * TME_LEAVE doesn't need to specify hover time so use default */
-    if (hover_time == HOVER_DEFAULT || hover_time == 0)
-        SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &hover_time, 0);
-
-    GetCursorPos(&pos);
-    hwnd = WINPOS_WindowFromPoint(ptme->hwndTrack, pos, &hittest);
-    TRACE("point %s hwnd %p hittest %d\n", wine_dbgstr_point(&pos), hwnd, hittest);
-
-    if (ptme->dwFlags & ~(TME_CANCEL | TME_HOVER | TME_LEAVE | TME_NONCLIENT))
-        FIXME("Unknown flag(s) %08x\n", ptme->dwFlags & ~(TME_CANCEL | TME_HOVER | TME_LEAVE | TME_NONCLIENT));
-
-    if (ptme->dwFlags & TME_CANCEL)
-    {
-        if (tracking_info.tme.hwndTrack == ptme->hwndTrack)
-        {
-            tracking_info.tme.dwFlags &= ~(ptme->dwFlags & ~TME_CANCEL);
-
-            /* if we aren't tracking on hover or leave remove this entry */
-            if (!(tracking_info.tme.dwFlags & (TME_HOVER | TME_LEAVE)))
-            {
-                KillSystemTimer(tracking_info.tme.hwndTrack, timer);
-                timer = 0;
-                tracking_info.tme.hwndTrack = 0;
-                tracking_info.tme.dwFlags = 0;
-                tracking_info.tme.dwHoverTime = 0;
-            }
-        }
-    } else {
-        /* In our implementation it's possible that another window will receive a
-         * WM_MOUSEMOVE and call TrackMouseEvent before TrackMouseEventProc is
-         * called. In such a situation post the WM_MOUSELEAVE now */
-        if (tracking_info.tme.dwFlags & TME_LEAVE && tracking_info.tme.hwndTrack != NULL)
-            check_mouse_leave(hwnd, hittest);
-
-        if (timer)
-        {
-            KillSystemTimer(tracking_info.tme.hwndTrack, timer);
-            timer = 0;
-            tracking_info.tme.hwndTrack = 0;
-            tracking_info.tme.dwFlags = 0;
-            tracking_info.tme.dwHoverTime = 0;
-        }
-
-        if (ptme->hwndTrack == hwnd)
-        {
-            /* Adding new mouse event to the tracking list */
-            tracking_info.tme = *ptme;
-            tracking_info.tme.dwHoverTime = hover_time;
-
-            /* Initialize HoverInfo variables even if not hover tracking */
-            tracking_info.pos = pos;
-
-            timer = SetSystemTimer(tracking_info.tme.hwndTrack, (UINT_PTR)&tracking_info.tme, hover_time, TrackMouseEventProc);
-        }
-    }
-
-    return TRUE;
+    /* use internal message to get access to user_thread_info for tracked window */
+    return SendNotifyMessageW(ptme->hwndTrack, WM_WINE_TRACKMOUSEEVENT, ptme->dwFlags, ptme->dwHoverTime);
 }
 
+
 /***********************************************************************
  * GetMouseMovePointsEx [USER32]
  *
diff --git a/dlls/user32/message.c b/dlls/user32/message.c
index 406eff3..21710c3 100644
--- a/dlls/user32/message.c
+++ b/dlls/user32/message.c
@@ -1888,6 +1888,9 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR
             return USER_Driver->pClipCursor( &rect );
         }
         return USER_Driver->pClipCursor( NULL );
+    case WM_WINE_TRACKMOUSEEVENT:
+        track_mouse_event_internal(hwnd, (DWORD)wparam, (DWORD)lparam);
+        return 0;
     default:
         if (msg >= WM_WINE_FIRST_DRIVER_MSG && msg <= WM_WINE_LAST_DRIVER_MSG)
             return USER_Driver->pWindowMessage( hwnd, msg, wparam, lparam );
@@ -2503,6 +2506,8 @@ static BOOL process_mouse_message( MSG *msg, UINT hw_id, ULONG_PTR extra_info, H
     else
     {
         msg->hwnd = WINPOS_WindowFromPoint( msg->hwnd, msg->pt, &hittest );
+        /* TrackMouseEvent support */
+        check_mouse_leave(msg->hwnd, hittest);
     }
 
     if (!msg->hwnd || !WIN_IsCurrentThread( msg->hwnd ))
diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c
index 64c7967..3d4d7b0 100644
--- a/dlls/user32/tests/msg.c
+++ b/dlls/user32/tests/msg.c
@@ -7817,6 +7817,18 @@ static DWORD WINAPI thread_proc(void *param)
 
     while (GetMessageA(&msg, 0, 0, 0))
     {
+        if ((msg.message == WM_TIMER || msg.message == WM_SYSTIMER) && msg.lParam)
+        {
+            struct recvd_message s_msg;
+
+            s_msg.hwnd = msg.hwnd;
+            s_msg.message = msg.message;
+            s_msg.flags = sent|wparam|lparam;
+            s_msg.wParam = msg.wParam;
+            s_msg.lParam = msg.lParam;
+            s_msg.descr = "msg_loop";
+            add_message(&s_msg);
+        }
 	TranslateMessage(&msg);
 	DispatchMessageA(&msg);
     }
@@ -11938,6 +11950,11 @@ static const struct message WmMouseHoverSeq[] = {
     { 0 }
 };
 
+static const struct message WmMouseLeaveSeq[] = {
+    { WM_MOUSELEAVE, sent|wparam, 0 },
+    { 0 }
+};
+
 static void pump_msg_loop_timeout(DWORD timeout, BOOL inject_mouse_move)
 {
     MSG msg;
@@ -11989,6 +12006,9 @@ static void test_TrackMouseEvent(void)
     HWND hwnd, hchild;
     RECT rc_parent, rc_child;
     UINT default_hover_time, hover_width = 0, hover_height = 0;
+    struct wnd_event wnd_event;
+    HANDLE hthread;
+    DWORD tid;
 
 #define track_hover(track_hwnd, track_hover_time) \
     tme.cbSize = sizeof(tme); \
@@ -11999,6 +12019,15 @@ static void test_TrackMouseEvent(void)
     ret = pTrackMouseEvent(&tme); \
     ok(ret, "TrackMouseEvent(TME_HOVER) error %d\n", GetLastError())
 
+#define track_leave(track_hwnd) \
+    tme.cbSize = sizeof(tme); \
+    tme.dwFlags = TME_LEAVE; \
+    tme.hwndTrack = track_hwnd; \
+    tme.dwHoverTime = 0xdeadbeef; \
+    SetLastError(0xdeadbeef); \
+    ret = pTrackMouseEvent(&tme); \
+    ok(ret, "TrackMouseEvent(TME_LEAVE) error %d\n", GetLastError());
+
 #define track_query(expected_track_flags, expected_track_hwnd, expected_hover_time) \
     tme.cbSize = sizeof(tme); \
     tme.dwFlags = TME_QUERY; \
@@ -12024,6 +12053,15 @@ static void test_TrackMouseEvent(void)
     ret = pTrackMouseEvent(&tme); \
     ok(ret, "TrackMouseEvent(TME_HOVER | TME_CANCEL) error %d\n", GetLastError())
 
+#define track_leave_cancel(track_hwnd) \
+    tme.cbSize = sizeof(tme); \
+    tme.dwFlags = TME_LEAVE | TME_CANCEL; \
+    tme.hwndTrack = track_hwnd; \
+    tme.dwHoverTime = 0xdeadbeef; \
+    SetLastError(0xdeadbeef); \
+    ret = pTrackMouseEvent(&tme); \
+    ok(ret, "TrackMouseEvent(TME_LEAVE | TME_CANCEL) error %d\n", GetLastError())
+
     default_hover_time = 0xdeadbeef;
     SetLastError(0xdeadbeef);
     ret = SystemParametersInfoA(SPI_GETMOUSEHOVERTIME, 0, &default_hover_time, 0);
@@ -12090,6 +12128,17 @@ static void test_TrackMouseEvent(void)
     ok(GetLastError() == ERROR_INVALID_WINDOW_HANDLE || broken(GetLastError() == 0xdeadbeef),
        "not expected error %u\n", GetLastError());
 
+    /* Invalid flags */
+    tme.cbSize = sizeof(tme);
+    tme.dwFlags = ~TME_CANCEL;
+    tme.hwndTrack = hwnd;
+    tme.dwHoverTime = HOVER_DEFAULT;
+    SetLastError(0xdeadbeef);
+    ret = pTrackMouseEvent(&tme);
+    ok(!ret, "TrackMouseEvent should fail\n");
+    ok(GetLastError() == ERROR_INVALID_FLAGS, "not expected error %u\n", GetLastError());
+    track_query(0, NULL, 0);
+
     GetWindowRect(hwnd, &rc_parent);
     GetWindowRect(hchild, &rc_child);
     SetCursorPos(rc_child.left - 10, rc_child.top - 10);
@@ -12159,11 +12208,127 @@ static void test_TrackMouseEvent(void)
     track_query(TME_HOVER, hwnd, default_hover_time);
     track_hover_cancel(hwnd);
 
+    /* cursor is over child window */
+    mouse_event(MOUSEEVENTF_MOVE, 20, 20, 0, 0); /* rc_child.left + 10, rc_child.top + 10 */
+    flush_events();
+    flush_sequence();
+
+    /* cancel TME_LEAVE */
+    track_leave(hchild);
+    ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+    track_query(TME_LEAVE, hchild, 0);
+    track_leave_cancel(hchild);
+    track_query(0, NULL, 0);
+    pump_msg_loop_timeout(default_hover_time, FALSE);
+    ok_sequence(WmEmptySeq, "WmEmptySeq", FALSE);
+
+    /* requests for the same window are merged */
+    track_hover(hchild, HOVER_DEFAULT);
+    track_query(TME_HOVER, hchild, default_hover_time);
+    track_leave(hchild);
+    ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+    track_query(TME_LEAVE|TME_HOVER, hchild, default_hover_time);
+    track_hover_cancel(hchild);
+    track_query(TME_LEAVE, hchild, 0);
+    /* TME_LEAVE for window not being under cursor doesn't change TME_QUERY result */
+    track_leave(hwnd);
+    track_query(TME_LEAVE, hchild, 0);
+    track_leave_cancel(hchild);
+    track_query(0, 0, 0);
+    flush_events();
+    flush_sequence();
+
+    /* try changing hover time */
+    track_hover(hchild, 0);
+    track_query(TME_HOVER, hchild, default_hover_time);
+    track_hover(hchild, 1);
+    track_query(TME_HOVER, hchild, 1);
+    track_hover(hchild, HOVER_DEFAULT);
+    track_query(TME_HOVER, hchild, default_hover_time);
+    track_hover(hchild, default_hover_time*2);
+    track_query(TME_HOVER, hchild, default_hover_time*2);
+    track_hover_cancel(hchild);
+
+    /* test TME_LEAVE */
+    track_leave(hchild);
+    ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+    track_query(TME_LEAVE, hchild, 0);
+    ok(!GetCapture(), "expected NULL\n");
+    mouse_event(MOUSEEVENTF_MOVE, -20, 0, 0, 0); /* rc_child.left - 10, rc_child.top + 10 */
+    pump_msg_loop_timeout(default_hover_time, FALSE);
+    ok_sequence(WmMouseLeaveSeq, "WmMouseLeaveSeq", FALSE);
+    track_query(0, NULL, 0);
+
+    /* window is not under cursor - immediate WM_MOUSELEAVE is expected */
+    track_leave(hchild);
+    ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+    track_query(0, NULL, 0);
+    pump_msg_loop_timeout(default_hover_time, FALSE);
+    ok_sequence(WmMouseLeaveSeq, "WmMouseLeaveSeq", FALSE);
+
+    mouse_event(MOUSEEVENTF_MOVE, 20, 0, 0, 0); /* rc_child.left + 10, rc_child.top + 10 */
+    flush_events();
+    flush_sequence();
+
+    /* move cursor outside top-window */
+    track_leave(hchild);
+    track_query(TME_LEAVE, hchild, 0);
+    mouse_event(MOUSEEVENTF_MOVE, 500, 0, 0, 0); /* rc_child.left + 510, rc_child.top + 10 */
+    pump_msg_loop_timeout(default_hover_time, FALSE); //flush_events();
+    ok_sequence(WmMouseLeaveSeq, "WmMouseLeaveSeq", FALSE);
+    track_query(0, NULL, 0);
+
     DestroyWindow(hwnd);
 
+    /* Try tracking cursor over window from other thread */
+    wnd_event.start_event = CreateEventW(NULL, 0, 0, NULL);
+    if (!wnd_event.start_event)
+    {
+        win_skip("CreateEventW failed\n");
+        return;
+    }
+    hthread = CreateThread(NULL, 0, thread_proc, &wnd_event, 0, &tid);
+    ok(hthread != NULL, "CreateThread failed, error %d\n", GetLastError());
+    ok(WaitForSingleObject(wnd_event.start_event, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failed\n");
+    CloseHandle(wnd_event.start_event);
+    SetCursorPos(150, 150);
+    ShowWindow(wnd_event.hwnd, SW_SHOW);
+    flush_events();
+    flush_sequence();
+
+    /* window is under cursor */
+    track_hover(wnd_event.hwnd, HOVER_DEFAULT);
+    track_query(0, 0, 0);
+    pump_msg_loop_timeout(default_hover_time, TRUE);
+    ok_sequence(WmMouseHoverSeq, "WmMouseHoverSeq", FALSE);
+
+    track_leave(wnd_event.hwnd);
+    ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+    track_query(0, 0, 0);
+    pump_msg_loop_timeout(default_hover_time, FALSE);
+    ok_sequence(WmEmptySeq, "WmEmptySeq", FALSE);
+
+    /* window is not under cursor */
+    SetCursorPos(600, 150);
+    pump_msg_loop_timeout(default_hover_time, FALSE);
+    ok_sequence(WmMouseLeaveSeq, "WmMouseLeaveSeq", FALSE);
+
+    track_leave(wnd_event.hwnd);
+    ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+    track_query(0, 0, 0);
+    pump_msg_loop_timeout(default_hover_time, FALSE);
+    ok_sequence(WmMouseLeaveSeq, "WmMouseLeaveSeq", FALSE);
+
+    ret = PostMessageA(wnd_event.hwnd, WM_QUIT, 0, 0);
+    ok( ret, "PostMessageA(WM_QUIT) error %d\n", GetLastError());
+    ok(WaitForSingleObject(hthread, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failed\n");
+    CloseHandle(hthread);
+
 #undef track_hover
 #undef track_query
+#undef track_leave
 #undef track_hover_cancel
+#undef track_leave_cancel
 }
 
 
diff --git a/dlls/user32/user_private.h b/dlls/user32/user_private.h
index 052fdd8..efff279 100644
--- a/dlls/user32/user_private.h
+++ b/dlls/user32/user_private.h
@@ -53,6 +53,7 @@ enum wine_internal_message
     WM_WINE_KEYBOARD_LL_HOOK,
     WM_WINE_MOUSE_LL_HOOK,
     WM_WINE_CLIPCURSOR,
+    WM_WINE_TRACKMOUSEEVENT,
     WM_WINE_FIRST_DRIVER_MSG = 0x80001000,  /* range of messages reserved for the USER driver */
     WM_WINE_LAST_DRIVER_MSG = 0x80001fff
 };
@@ -163,6 +164,13 @@ struct wm_char_mapping_data
     MSG  get_msg;
 };
 
+/* data for TrackMouseEvent */
+struct tracking_info {
+    TRACKMOUSEEVENT tme;
+    POINT           pos; /* center of hover rectangle */
+    UINT_PTR        timer;
+};
+
 /* this is the structure stored in TEB->Win32ClientInfo */
 /* no attempt is made to keep the layout compatible with the Windows one */
 struct user_thread_info
@@ -185,6 +193,7 @@ struct user_thread_info
     HWND                          top_window;             /* Desktop window */
     HWND                          msg_window;             /* HWND_MESSAGE parent window */
     RAWINPUT                     *rawinput;
+    struct tracking_info          tracking_info;
 };
 
 C_ASSERT( sizeof(struct user_thread_info) <= sizeof(((TEB *)0)->Win32ClientInfo) );
@@ -225,6 +234,8 @@ struct tagWND;
 extern void CLIPBOARD_ReleaseOwner( HWND hwnd ) DECLSPEC_HIDDEN;
 extern BOOL FOCUS_MouseActivate( HWND hwnd ) DECLSPEC_HIDDEN;
 extern BOOL set_capture_window( HWND hwnd, UINT gui_flags, HWND *prev_ret ) DECLSPEC_HIDDEN;
+extern void check_mouse_leave(HWND hwnd, int hittest) DECLSPEC_HIDDEN;
+extern void track_mouse_event_internal(HWND hwnd, DWORD flags, DWORD hover_time) DECLSPEC_HIDDEN;
 extern void free_dce( struct dce *dce, HWND hwnd ) DECLSPEC_HIDDEN;
 extern void invalidate_dce( struct tagWND *win, const RECT *rect ) DECLSPEC_HIDDEN;
 extern void erase_now( HWND hwnd, UINT rdw_flags ) DECLSPEC_HIDDEN;
diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c
index a0bfe05..cc8dac8 100644
--- a/dlls/winex11.drv/event.c
+++ b/dlls/winex11.drv/event.c
@@ -114,7 +114,7 @@ static x11drv_event_handler handlers[MAX_EVENT_HANDLERS] =
     X11DRV_ButtonRelease,     /*  5 ButtonRelease */
     X11DRV_MotionNotify,      /*  6 MotionNotify */
     X11DRV_EnterNotify,       /*  7 EnterNotify */
-    NULL,                     /*  8 LeaveNotify */
+    X11DRV_LeaveNotify,       /*  8 LeaveNotify */
     X11DRV_FocusIn,           /*  9 FocusIn */
     X11DRV_FocusOut,          /* 10 FocusOut */
     X11DRV_KeymapNotify,      /* 11 KeymapNotify */
diff --git a/dlls/winex11.drv/mouse.c b/dlls/winex11.drv/mouse.c
index 5ace405..092c381 100644
--- a/dlls/winex11.drv/mouse.c
+++ b/dlls/winex11.drv/mouse.c
@@ -1447,6 +1447,7 @@ BOOL CDECL X11DRV_GetCursorPos(LPPOINT pos)
         *pos = root_to_virtual_screen( winX, winY );
         TRACE( "pointer at %s server pos %s\n", wine_dbgstr_point(pos), wine_dbgstr_point(&old) );
     }
+
     return ret;
 }
 
@@ -1690,6 +1691,34 @@ BOOL X11DRV_EnterNotify( HWND hwnd, XEvent *xev )
     return TRUE;
 }
 
+/***********************************************************************
+ *           X11DRV_LeaveNotify
+ */
+BOOL X11DRV_LeaveNotify( HWND hwnd, XEvent *xev )
+{
+    XCrossingEvent *event = &xev->xcrossing;
+    INPUT input;
+
+    TRACE( "hwnd %p/%lx pos %d,%d detail %d\n", hwnd, event->window, event->x, event->y, event->detail );
+
+    if (event->detail == NotifyVirtual) return FALSE;
+    if (hwnd == x11drv_thread_data()->grab_hwnd) return FALSE;
+
+    /* simulate a mouse motion event - needed for TrackMouseEvent */
+    input.u.mi.dx          = event->x;
+    input.u.mi.dy          = event->y;
+    input.u.mi.mouseData   = 0;
+    input.u.mi.dwFlags     = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
+    input.u.mi.time        = EVENT_x11_time_to_win32_time( event->time );
+    input.u.mi.dwExtraInfo = 0;
+
+    /* Note: not calling is_old_motion_event because leave message is not simulated when warping cursor */
+
+    send_mouse_input( hwnd, event->window, event->state, &input );
+    return TRUE;
+}
+
+
 #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H
 
 /***********************************************************************
diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c
index d35328c..2626c93 100644
--- a/dlls/winex11.drv/window.c
+++ b/dlls/winex11.drv/window.c
@@ -326,7 +326,7 @@ static int get_window_attributes( struct x11drv_win_data *data, XSetWindowAttrib
     attr->backing_store     = NotUseful;
     attr->border_pixel      = 0;
     attr->event_mask        = (ExposureMask | PointerMotionMask |
-                               ButtonPressMask | ButtonReleaseMask | EnterWindowMask |
+                               ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask |
                                KeyPressMask | KeyReleaseMask | FocusChangeMask |
                                KeymapStateMask | StructureNotifyMask);
     if (data->managed) attr->event_mask |= PropertyChangeMask;
diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h
index 938ff22..1ee1c15 100644
--- a/dlls/winex11.drv/x11drv.h
+++ b/dlls/winex11.drv/x11drv.h
@@ -508,6 +508,7 @@ extern BOOL X11DRV_ButtonPress( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
 extern BOOL X11DRV_ButtonRelease( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
 extern BOOL X11DRV_MotionNotify( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
 extern BOOL X11DRV_EnterNotify( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
+extern BOOL X11DRV_LeaveNotify( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
 extern BOOL X11DRV_KeyEvent( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
 extern BOOL X11DRV_KeymapNotify( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
 extern BOOL X11DRV_DestroyNotify( HWND hwnd, XEvent *event ) DECLSPEC_HIDDEN;
-- 
2.7.4




More information about the wine-devel mailing list