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 v4 adds test for window in thread not handling messages.
Signed-off-by: Rafał Harabień <rafalh1992(a)o2.pl>
---
dlls/user32/input.c | 234 +++++++++++++++++----------------
dlls/user32/message.c | 5 +
dlls/user32/tests/msg.c | 314 +++++++++++++++++++++++++++++++++++++++++++--
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, 476 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..cfc1cde 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,114 @@ 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 DWORD WINAPI thread_proc_tme_no_msg(void *param)
+{
+ BOOL ret;
+ TRACKMOUSEEVENT tme;
+ struct wnd_event *wnd_event = param;
+ UINT default_hover_time;
+
+ 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");
+ ShowWindow(wnd_event->hwnd, SW_SHOW);
+ flush_events();
+
+ ret = SystemParametersInfoA(SPI_GETMOUSEHOVERTIME, 0, &default_hover_time, 0);
+ if (!ret) default_hover_time = 400;
+
+ SetEvent(wnd_event->start_event);
+
+ /* Sleep without handling messages */
+ Sleep(1000);
+
+ /* TME_QUERY should return flags even if message loop is not running.
+ It doesn't work in Wine (for other thread) because WM_WINE_TRACKMOUSEEVENT is used by TrackMouseEvent function. */
+ tme.cbSize = sizeof(tme);
+ tme.dwFlags = TME_QUERY;
+ tme.hwndTrack = (HWND)0xdeadbeef;
+ tme.dwHoverTime = 0xdeadbeef;
+ SetLastError(0xdeadbeef);
+ ret = pTrackMouseEvent(&tme);
+ ok(ret, "TrackMouseEvent(TME_QUERY) error %d\n", GetLastError());
+ ok(tme.cbSize == sizeof(tme), "wrong tme.cbSize %u\n", tme.cbSize);
+ todo_wine {
+ ok(tme.dwFlags == (TME_LEAVE|TME_HOVER), "wrong tme.dwFlags %08x, expected %08x\n", tme.dwFlags, TME_LEAVE|TME_HOVER);
+ ok(tme.hwndTrack == wnd_event->hwnd, "wrong tme.hwndTrack %p, expected %p\n", tme.hwndTrack, wnd_event->hwnd);
+ ok(tme.dwHoverTime == default_hover_time,"wrong tme.dwHoverTime %u, expected %u\n", tme.dwHoverTime, default_hover_time);
+ }
+
+ 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 +12216,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 +12296,170 @@ 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);
+
+ /* Window from other thread; no message loop is running */
+ SetCursorPos(150, 150);
+ flush_events();
+ flush_sequence();
+
+ 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_tme_no_msg, &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);
+
+ /* window is under cursor */
+ track_leave(wnd_event.hwnd);
+ ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+ track_hover(wnd_event.hwnd, HOVER_DEFAULT);
+ ok(GetLastError() == 0xdeadbeef, "not expected error %u\n", GetLastError());
+ track_query(0, NULL, 0);
+
+ 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