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@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;
Hi,
While running your changed tests on Windows, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check? Full results can be found at https://testbot.winehq.org/JobDetails.pl?Key=34248
Your paranoid android.
=== w864 (32 bit msg) === msg.c:14045: Test failed: unexpected message 31f msg.c:14046: Test failed: bad wparam 1 msg.c:14052: Test failed: unicode WM_CHAR: 0: the msg sequence is not complete: expected 0102 - actual 0000
Rafał Harabień rafalh1992@o2.pl writes:
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
I still don't see a test justifying the use of local thread data. In fact, if you have to do things like this:
- /* use internal message to get access to user_thread_info for tracked window */
- return SendNotifyMessageW(ptme->hwndTrack, WM_WINE_TRACKMOUSEEVENT, ptme->dwFlags, ptme->dwHoverTime);
it suggests that maybe the thread data is not the right place.
Tests show that TME_QUERY returns valid data if TME_LEAVE or TME_HOVER tracking is active for window in current thread and returns empty data after starting tracking for window from other thread ("track_query(0, NULL, 0)" in test code). It cannot work this way with global variable (as used already in HEAD).
I used SendNotifyMessageW because I don't see a way to get user_thread_info pointer for other threads. Is there any good alternative to store thread related data?
What other test can I provide?
W dniu 20.11.2017 o 11:52, Alexandre Julliard pisze:
Rafał Harabień rafalh1992@o2.pl writes:
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
I still don't see a test justifying the use of local thread data. In fact, if you have to do things like this:
- /* use internal message to get access to user_thread_info for tracked window */
- return SendNotifyMessageW(ptme->hwndTrack, WM_WINE_TRACKMOUSEEVENT, ptme->dwFlags, ptme->dwHoverTime);
it suggests that maybe the thread data is not the right place.
Rafał Harabień rafalh1992@o2.pl writes:
Tests show that TME_QUERY returns valid data if TME_LEAVE or TME_HOVER tracking is active for window in current thread and returns empty data after starting tracking for window from other thread ("track_query(0, NULL, 0)" in test code). It cannot work this way with global variable (as used already in HEAD).
In fact your test succeeds even with a global variable, because you are using SendNotifyMessageW but don't wait for the message to be processed; so it's not very convincing. You could add waits, but then I'd like to see a test showing that the API doesn't work if the other thread is not currently processing messages.
I agree, my use of SendNotifyMessageW was unfortunate for tests but I wanted to make sure current thread is not blocked by other thread not processing messages. I can change it to SendMessageW.
I uploaded patch v3 with more thread related tests (sleeps + checking TME_QUERY result in thread).
I cannot create a test showing that other thread not processing messages will break this API because it actually doesn't break TME_QUERY on Windows. On MS OS all processing is done in win32k I believe and it has full access to thread_info for all threads. In Wine I have two options - do tracking in usermode and use internal messages in case of windows from different threads/processes, or implement big amount of functionality in wineserver which would require much more work. This API is all about sending messages (WM_MOUSELEAVE and WM_MOUSEHOVER) so its useless for threads which doesn't process messages. Also I believe its usually used from WindowProc after receiving WM_MOUSEMOVE (->window thread) so complicating it for <5% of use cases is not worth it IMO.
Doing it in wineserver would require to support server-side timers (I think we only support client-side) and cursor tracking for child window (I think we do it for top-level windows only). Alternatively I could use wineserver for getting/saving state only and do all logic in user32 but it would require additional communication with wineserver making mouse related code slower.
To sum up, I know this is not implemented exactly like in Windows but I wanted to make code simple and make as little changes to existing architecture as possible. New implementation is passing more tests than old one and shouldn't break anything.
W dniu 21.11.2017 o 09:54, Alexandre Julliard pisze:
Rafał Harabień rafalh1992@o2.pl writes:
Tests show that TME_QUERY returns valid data if TME_LEAVE or TME_HOVER tracking is active for window in current thread and returns empty data after starting tracking for window from other thread ("track_query(0, NULL, 0)" in test code). It cannot work this way with global variable (as used already in HEAD).
In fact your test succeeds even with a global variable, because you are using SendNotifyMessageW but don't wait for the message to be processed; so it's not very convincing. You could add waits, but then I'd like to see a test showing that the API doesn't work if the other thread is not currently processing messages.
Rafał Harabień rafalh1992@o2.pl writes:
I agree, my use of SendNotifyMessageW was unfortunate for tests but I wanted to make sure current thread is not blocked by other thread not processing messages. I can change it to SendMessageW.
I uploaded patch v3 with more thread related tests (sleeps + checking TME_QUERY result in thread).
I cannot create a test showing that other thread not processing messages will break this API because it actually doesn't break TME_QUERY on Windows. On MS OS all processing is done in win32k I believe and it has full access to thread_info for all threads. In Wine I have two options - do tracking in usermode and use internal messages in case of windows from different threads/processes, or implement big amount of functionality in wineserver which would require much more work. This API is all about sending messages (WM_MOUSELEAVE and WM_MOUSEHOVER) so its useless for threads which doesn't process messages. Also I believe its usually used from WindowProc after receiving WM_MOUSEMOVE (->window thread) so complicating it for <5% of use cases is not worth it IMO.
It may be that we don't want to do it the same way as Windows, but I don't think we can decide that until we know how it's supposed to behave, which means writing more tests. If it turns out that the proper behavior is too hard to do right, we can then decide to leave the tests as todo for now.