Revert high fequency mouse input for Windows.

Windows mouse history function GetMouesMovePointsEx has well documented
bugs where it receives and returns 32 bit screen coordinates, but
internally truncates to unsigned 16 bits. For mouse (relative position)
input this is not a problem as motion events and the resulting screen
coordinates reliably fit within 16 bit precision.

For tablets (absolute position) the 16 bit truncation results in
corrupt history when tablet drivers use mouse_event or SendInput from
the Windows API to move the mouse cursor. Both of these functions take
absolute mouse position as singed 32 bit value on the range of 0-65535
(or 0x0-0xFFFF) inclusive. Values larger than 0x7FFF (the largest
signed 16 bit value) are reliably corrupt when retrieved from
GetMouesMovePointsEx history. This is true regardless of whether mouse
history is retrieved using display resolution (GMMP_USE_DISPLAY_POINTS)
or high resolution points (GMMP_USE_HIGH_RESOLUTION_POINTS), the latter
of which should return points in range 0-65535.

Reviewed By: brecht

Maniphest Tasks: T85874

Differential Revision: https://developer.blender.org/D10507
This commit is contained in:
Nicholas Rishel 2021-02-22 21:07:21 -08:00
parent e497c1b93c
commit cd9dbe317d
Notes: blender-bot 2023-02-14 09:48:25 +01:00
Referenced by issue #85918, Option menus disappear when hovering over the options with a Pen Display
Referenced by issue #85874, Fix remaining tablet regression issues
Referenced by issue #85733, Spikes when using tablet
2 changed files with 38 additions and 111 deletions

View File

@ -225,9 +225,6 @@ GHOST_SystemWin32::GHOST_SystemWin32()
#ifdef WITH_INPUT_NDOF
m_ndofManager = new GHOST_NDOFManagerWin32(*this);
#endif
getCursorPosition(m_mousePosX, m_mousePosY);
m_mouseTimestamp = ::GetTickCount();
}
GHOST_SystemWin32::~GHOST_SystemWin32()
@ -941,16 +938,6 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type,
if (window->m_tabletInRange) {
td = window->getTabletData();
/* Check if tablet cursor position is in sync with Win32 cursor position, if not then move
* cursor to position where button event occurred. */
DWORD msgPos = ::GetMessagePos();
int msgPosX = GET_X_LPARAM(msgPos);
int msgPosY = GET_Y_LPARAM(msgPos);
if (msgPosX != system->m_mousePosX || msgPosY != system->m_mousePosY) {
system->pushEvent(new GHOST_EventCursor(
::GetMessageTime(), GHOST_kEventCursorMove, window, msgPosX, msgPosY, td));
}
}
return new GHOST_EventButton(system->getMilliSeconds(), type, window, mask, td);
@ -1033,82 +1020,26 @@ void GHOST_SystemWin32::processPointerEvent(
system->setCursorPosition(pointerInfo[0].pixelLocation.x, pointerInfo[0].pixelLocation.y);
}
void GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
{
if (window->m_tabletInRange && window->useTabletAPI(GHOST_kTabletNative)) {
/* Tablet input handled in WM_POINTER* events. WM_MOUSEMOVE events in response to tablet
* input aren't normally generated when using WM_POINTER events, but manually moving the
* system cursor as we do in WM_POINTER handling does. */
return;
}
GHOST_TInt32 x_screen, y_screen;
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
GHOST_TabletData td = window->getTabletData();
DWORD msgPos = ::GetMessagePos();
LONG msgTime = ::GetMessageTime();
/* GetMessagePointsEx processes points as 16 bit integers and can fail or return erroneous values
* if negative input is not truncated. */
int msgPosX = GET_X_LPARAM(msgPos) & 0x0000FFFF;
int msgPosY = GET_Y_LPARAM(msgPos) & 0x0000FFFF;
const int maxPoints = 64;
MOUSEMOVEPOINT currentPoint = {msgPosX, msgPosY, (DWORD)msgTime, 0};
MOUSEMOVEPOINT points[maxPoints] = {0};
/* GetMouseMovePointsEx returns the number of points returned that are less than or equal to the
* requested point. If the requested point is the most recent, this returns up to 64 requested
* points. */
int numPoints = ::GetMouseMovePointsEx(
sizeof(MOUSEMOVEPOINT), &currentPoint, points, maxPoints, GMMP_USE_DISPLAY_POINTS);
if (numPoints == -1) {
/* Points at edge of screen are often not in the queue, use the message's point instead. */
numPoints = 1;
points[0] = currentPoint;
}
GHOST_TInt32 x_accum = 0, y_accum = 0;
window->getCursorGrabAccum(x_accum, y_accum);
/* Points are in reverse chronological order. Find least recent, unprocessed mouse move. */
int i;
for (i = 0; i < numPoints; i++) {
if (points[i].time < system->m_mouseTimestamp) {
break;
}
/* GetMouseMovePointsEx returns 16 bit number as 32 bit. If negative, we need to sign extend.
*/
points[i].x = points[i].x > 32767 ? points[i].x | 0xFFFF0000 : points[i].x;
points[i].y = points[i].y > 32767 ? points[i].y | 0xFFFF0000 : points[i].y;
if (points[i].time == system->m_mouseTimestamp && points[i].x == system->m_mousePosX &&
points[i].y == system->m_mousePosY) {
break;
if (window->m_tabletInRange) {
if (window->useTabletAPI(GHOST_kTabletNative)) {
/* Tablet input handled in WM_POINTER* events. WM_MOUSEMOVE events in response to tablet
* input aren't normally generated when using WM_POINTER events, but manually moving the
* system cursor as we do in WM_POINTER handling does. */
return NULL;
}
}
while (--i >= 0) {
system->pushEvent(new GHOST_EventCursor(system->getMilliSeconds(),
GHOST_kEventCursorMove,
window,
points[i].x + x_accum,
points[i].y + y_accum,
td));
}
DWORD lastTimestamp = points[0].time;
system->getCursorPosition(x_screen, y_screen);
/* Check if we need to wrap the cursor. */
if (window->getCursorGrabModeIsWarp() && !window->m_tabletInRange) {
/* Wrap based on current cursor position in case Win32 mouse move queue is out of order due to
* prior wrap. */
POINT point;
::GetCursorPos(&point);
GHOST_TInt32 x_current = point.x;
GHOST_TInt32 y_current = point.y;
GHOST_TInt32 x_wrap = point.x;
GHOST_TInt32 y_wrap = point.y;
GHOST_TInt32 x_new = x_screen;
GHOST_TInt32 y_new = y_screen;
GHOST_TInt32 x_accum, y_accum;
GHOST_Rect bounds;
/* Fallback to window bounds. */
@ -1118,24 +1049,33 @@ void GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
/* Could also clamp to screen bounds wrap with a window outside the view will fail atm.
* Use offset of 8 in case the window is at screen bounds. */
bounds.wrapPoint(x_wrap, y_wrap, 2, window->getCursorGrabAxis());
bounds.wrapPoint(x_new, y_new, 2, window->getCursorGrabAxis());
if (x_wrap != x_current || y_wrap != y_current) {
system->setCursorPosition(x_wrap, y_wrap);
window->setCursorGrabAccum(x_accum + (x_current - x_wrap), y_accum + (y_current - y_wrap));
/* First message after SendInput wrap is invalid for unknown reasons, skip events until one
* tick after SendInput event time. */
lastTimestamp = ::GetTickCount() + 1;
window->getCursorGrabAccum(x_accum, y_accum);
if (x_new != x_screen || y_new != y_screen) {
/* When wrapping we don't need to add an event because the setCursorPosition call will cause
* a new event after. */
system->setCursorPosition(x_new, y_new); /* wrap */
window->setCursorGrabAccum(x_accum + (x_screen - x_new), y_accum + (y_screen - y_new));
}
else {
return new GHOST_EventCursor(system->getMilliSeconds(),
GHOST_kEventCursorMove,
window,
x_screen + x_accum,
y_screen + y_accum,
window->getTabletData());
}
}
system->m_mousePosX = points[0].x;
system->m_mousePosY = points[0].y;
/* Use latest time, checking for overflow. */
if (lastTimestamp > system->m_mouseTimestamp || ::GetTickCount() < system->m_mouseTimestamp) {
system->m_mouseTimestamp = lastTimestamp;
else {
return new GHOST_EventCursor(system->getMilliSeconds(),
GHOST_kEventCursorMove,
window,
x_screen,
y_screen,
window->getTabletData());
}
return NULL;
}
void GHOST_SystemWin32::processWheelEvent(GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam)
@ -1586,8 +1526,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
}
break;
case WM_MOUSEMOVE:
processCursorEvent(window);
eventHandled = true;
event = processCursorEvent(window);
break;
case WM_MOUSEWHEEL: {
/* The WM_MOUSEWHEEL message is sent to the focus window

View File

@ -308,12 +308,6 @@ class GHOST_SystemWin32 : public GHOST_System {
GHOST_WindowWin32 *window,
GHOST_TButtonMask mask);
/**
* Creates tablet events from Wintab events.
* \param window: The window receiving the event (the active window).
*/
static void processWintabEvent(GHOST_WindowWin32 *window);
/**
* Creates tablet events from pointer events.
* \param type: The type of pointer event.
@ -328,8 +322,9 @@ class GHOST_SystemWin32 : public GHOST_System {
/**
* Creates cursor event.
* \param window: The window receiving the event (the active window).
* \return The event created.
*/
static void processCursorEvent(GHOST_WindowWin32 *window);
static GHOST_EventCursor *processCursorEvent(GHOST_WindowWin32 *window);
/**
* Handles a mouse wheel event.
@ -453,13 +448,6 @@ class GHOST_SystemWin32 : public GHOST_System {
/** Wheel delta accumulator. */
int m_wheelDeltaAccum;
/** Last mouse x position. */
int m_mousePosX;
/** Last mouse y position. */
int m_mousePosY;
/** Last mouse timestamp. */
DWORD m_mouseTimestamp;
};
inline void GHOST_SystemWin32::retrieveModifierKeys(GHOST_ModifierKeys &keys) const