[Bf-blender-cvs] [a3a9459050a] master: Fix erratic mouse wrapping movement on Windows (2)

Germano Cavalcante noreply at git.blender.org
Wed Dec 14 20:08:35 CET 2022


Commit: a3a9459050a96e75138b3441c069898f211f179c
Author: Germano Cavalcante
Date:   Wed Dec 14 15:37:49 2022 -0300
Branches: master
https://developer.blender.org/rBa3a9459050a96e75138b3441c069898f211f179c

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.

===================================================================

M	intern/ghost/intern/GHOST_SystemWin32.cpp

===================================================================

diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp
index 75a4cc8389a..8cb007a756a 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemWin32.cpp
@@ -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 */
-
-      /* 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);
-        }
-      }
-
-      if (!is_warping_y) {
-        is_warping_y = y_new != y_screen;
-        if (is_warping_y) {
-          y_accum += (y_screen - y_new);
-        }
-      }
+      /* WORKAROUND: Store the current time so that we ignore outdated mousemove events. */
+      last_warp_time = ::GetTickCount64();
+
+      /* 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;
+
+      /* 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;
   }



More information about the Bf-blender-cvs mailing list