Fix erratic mouse wrapping movement on Windows (2)
This is a solution in response to the issues mentioned in comments on rBe4f1d719080a and T103088. Apparently the workaround of checking if the mouse is already inside the area on the next event doesn't work for some tablets. Perhaps the order of events or some very small jitter is causing this issue on tablets. (Couldn't confirm). Whatever the cause, the solution of checking the timestamp of the event and thus ignoring the outdated ones is theoretically safer. It is the same solution seen in MacOS. Also calling `SendInput` 3 times every warp ensures that at least one event is dispatched.
This commit is contained in:
parent
56237f33a1
commit
a3a9459050
Notes:
blender-bot
2024-05-02 21:33:31 +02:00
Referenced by commit1a986f7eba
, Revert "Fix erratic mouse wrapping movement on Windows (2)" Referenced by commit79a34758f5
, Fix T103253: Infinite drag of number buttons is broken on WIN32 Referenced by issue #103337, Regression: Infinite drag in the 3D viewport sometimes gets stuck on the edge of the screen. Referenced by issue #103253, Regression: Infinite drag of number buttons is broken Referenced by issue #103088, Select and slide in transform values Jumps all over Referenced by issue #89399, Mouse wrapping causes erratic movement
|
@ -1061,11 +1061,16 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
|
|||
|
||||
int32_t x_screen = screen_co[0], y_screen = screen_co[1];
|
||||
if (window->getCursorGrabModeIsWarp()) {
|
||||
/* WORKAROUND:
|
||||
* Sometimes Windows ignores `SetCursorPos()` or `SendInput()` calls or the mouse event is
|
||||
* outdated. Identify these cases by checking if the cursor is not yet within bounds. */
|
||||
static bool is_warping_x = false;
|
||||
static bool is_warping_y = false;
|
||||
static uint64_t last_warp_time = 0;
|
||||
{
|
||||
/* WORKAROUND: Check the mouse event timestamp so we can ignore mousemove events that were
|
||||
* already in the queue before we changed the cursor position. */
|
||||
MOUSEMOVEPOINT mp = {x_screen, y_screen};
|
||||
::GetMouseMovePointsEx(sizeof(MOUSEMOVEPOINT), &mp, &mp, 1, GMMP_USE_DISPLAY_POINTS);
|
||||
if (mp.time <= last_warp_time) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t x_new = x_screen;
|
||||
int32_t y_new = y_screen;
|
||||
|
@ -1112,31 +1117,35 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
|
|||
|
||||
window->getCursorGrabAccum(x_accum, y_accum);
|
||||
if (x_new != x_screen || y_new != y_screen) {
|
||||
system->setCursorPosition(x_new, y_new); /* wrap */
|
||||
/* WORKAROUND: Store the current time so that we ignore outdated mousemove events. */
|
||||
last_warp_time = ::GetTickCount64();
|
||||
|
||||
/* Do not update the accum values if we are an outdated or failed pos-warp event. */
|
||||
if (!is_warping_x) {
|
||||
is_warping_x = x_new != x_screen;
|
||||
if (is_warping_x) {
|
||||
x_accum += (x_screen - x_new);
|
||||
}
|
||||
}
|
||||
/* For more control over which timestamp to store in the event, we use `SendInput` instead of
|
||||
* `SetCursorPos` here.
|
||||
* It is quite unlikely to happen, but still possible that some event between
|
||||
* `last_warp_time` and `GHOST_SystemWin32::setCursorPosition` is sent. */
|
||||
INPUT input[3] = {0};
|
||||
input[0].type = INPUT_MOUSE;
|
||||
input[0].mi.dx = (LONG)(x_new * (65535.0f / GetSystemMetrics(SM_CXSCREEN)));
|
||||
input[0].mi.dy = (LONG)(y_new * (65535.0f / GetSystemMetrics(SM_CYSCREEN)));
|
||||
input[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
|
||||
input[0].mi.time = last_warp_time;
|
||||
|
||||
if (!is_warping_y) {
|
||||
is_warping_y = y_new != y_screen;
|
||||
if (is_warping_y) {
|
||||
y_accum += (y_screen - y_new);
|
||||
}
|
||||
}
|
||||
/* Send 3 events with a jitter to make sure Windows does not occasionally and
|
||||
* inexplicably ignore `SetCursorPos` or `SendInput`. */
|
||||
input[2] = input[1] = input[0];
|
||||
input[1].mi.dx += 1;
|
||||
::SendInput(3, input, sizeof(INPUT));
|
||||
|
||||
x_accum += (x_screen - x_new);
|
||||
y_accum += (y_screen - y_new);
|
||||
window->setCursorGrabAccum(x_accum, y_accum);
|
||||
|
||||
/* When wrapping we don't need to add an event because the setCursorPosition call will cause
|
||||
* a new event after. */
|
||||
/* When wrapping we don't need to add an event because the `SendInput` call will cause new
|
||||
* events after. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
is_warping_x = false;
|
||||
is_warping_y = false;
|
||||
x_screen += x_accum;
|
||||
y_screen += y_accum;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue