From: Rafał Harabień Subject: [PATCH v3] user32: Reimplement TrackMouseEvent. Message-Id: <1511363888-8058-1-git-send-email-rafalh1992@o2.pl> Date: Wed, 22 Nov 2017 16:18:08 +0100 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 Patch v2 fixes compiler warnings and some style issues. Signed-off-by: Rafał Harabień --- dlls/user32/input.c | 234 ++++++++++++++++++++++-------------------- dlls/user32/message.c | 5 + dlls/user32/tests/msg.c | 248 +++++++++++++++++++++++++++++++++++++++++++-- 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, 410 insertions(+), 122 deletions(-) diff --git a/dlls/user32/input.c b/dlls/user32/input.c index c475b19..5b589a5 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, 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 or 0 was specified replace this with the system's current value. */ + if (hover_time == HOVER_DEFAULT || hover_time == 0) + SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &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 { - KillSystemTimer(tracking_info.tme.hwndTrack, timer); - timer = 0; - tracking_info.tme.hwndTrack = 0; - tracking_info.tme.dwFlags = 0; - tracking_info.tme.dwHoverTime = 0; + /* check what window is under cursor */ + 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); + } + } } } @@ -1389,23 +1453,30 @@ static void CALLBACK TrackMouseEventProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, BOOL WINAPI TrackMouseEvent (TRACKMOUSEEVENT *ptme) { - HWND hwnd; - POINT pos; - DWORD hover_time; - INT hittest; + DWORD invalid_flags; + 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); - if (ptme->cbSize != sizeof(TRACKMOUSEEVENT)) { + if (ptme->cbSize != sizeof(TRACKMOUSEEVENT)) + { WARN("wrong TRACKMOUSEEVENT size from app\n"); SetLastError(ERROR_INVALID_PARAMETER); 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 +1489,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..c65ded7 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; @@ -11982,14 +11999,6 @@ static void pump_msg_loop_timeout(DWORD timeout, BOOL inject_mouse_move) } while (start_ticks + timeout >= end_ticks); } -static void test_TrackMouseEvent(void) -{ - TRACKMOUSEEVENT tme; - BOOL ret; - HWND hwnd, hchild; - RECT rc_parent, rc_child; - UINT default_hover_time, hover_width = 0, hover_height = 0; - #define track_hover(track_hwnd, track_hover_time) \ tme.cbSize = sizeof(tme); \ tme.dwFlags = TME_HOVER; \ @@ -11999,6 +12008,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 +12042,74 @@ 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()) + +static DWORD WINAPI thread_proc_tme(void *param) +{ + BOOL ret; + TRACKMOUSEEVENT tme; + MSG msg; + struct wnd_event *wnd_event = param; + + wnd_event->hwnd = CreateWindowExA(0, "TestWindowClass", "window caption text", WS_OVERLAPPEDWINDOW, + 100, 100, 200, 200, 0, 0, 0, NULL); + ok(wnd_event->hwnd != 0, "Failed to create overlapped window\n"); + + SetEvent(wnd_event->start_event); + + 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); + } + if (msg.message == WM_USER) + { + if (msg.wParam) + { + track_query((DWORD)msg.wParam, wnd_event->hwnd, (DWORD)msg.lParam); + } + else + { + track_query(0, NULL, 0); + } + continue; + } + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + ok(IsWindow(wnd_event->hwnd), "window should still exist\n"); + + return 0; +} + +static void test_TrackMouseEvent(void) +{ + TRACKMOUSEEVENT tme; + BOOL ret; + 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; + default_hover_time = 0xdeadbeef; SetLastError(0xdeadbeef); ret = SystemParametersInfoA(SPI_GETMOUSEHOVERTIME, 0, &default_hover_time, 0); @@ -12090,6 +12176,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,13 +12256,144 @@ 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, NULL, 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(0, 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(0, 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(0, FALSE); + 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; + } + SetCursorPos(150, 150); + hthread = CreateThread(NULL, 0, thread_proc_tme, &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); + ShowWindow(wnd_event.hwnd, SW_SHOW); + flush_events(); + flush_sequence(); + + /* window is under cursor */ + track_hover(wnd_event.hwnd, HOVER_DEFAULT); + ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError()); + track_query(0, NULL, 0); + PostMessageW(wnd_event.hwnd, WM_USER, TME_HOVER, default_hover_time); + Sleep(default_hover_time/2); + track_query(0, NULL, 0); + Sleep(default_hover_time); + flush_events(); + ok_sequence(WmMouseHoverSeq, "WmMouseHoverSeq", FALSE); + + track_leave(wnd_event.hwnd); + ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError()); + track_query(0, NULL, 0); + PostMessageW(wnd_event.hwnd, WM_USER, TME_LEAVE, 0); + Sleep(default_hover_time/2); + track_query(0, NULL, 0); + Sleep(default_hover_time); + flush_events(); + ok_sequence(WmEmptySeq, "WmEmptySeq", FALSE); + + /* window is not under cursor */ + SetCursorPos(600, 150); + flush_events(); + track_query(0, NULL, 0); + PostMessageW(wnd_event.hwnd, WM_USER, 0, 0); + Sleep(default_hover_time/2); + flush_events(); + ok_sequence(WmMouseLeaveSeq, "WmMouseLeaveSeq", FALSE); + + track_leave(wnd_event.hwnd); + ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError()); + track_query(0, NULL, 0); + PostMessageW(wnd_event.hwnd, WM_USER, 0, 0); + Sleep(default_hover_time/2); + flush_events(); + ok_sequence(WmMouseLeaveSeq, "WmMouseLeaveSeq", FALSE); + + ret = PostMessageW(wnd_event.hwnd, WM_QUIT, 0, 0); + ok( ret, "PostMessageW(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 static const struct message WmSetWindowRgn[] = { { WM_WINDOWPOSCHANGING, sent|wparam, SWP_NOCLIENTSIZE|SWP_NOCLIENTMOVE|SWP_NOACTIVATE|SWP_FRAMECHANGED|SWP_NOSIZE|SWP_NOMOVE }, 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