Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling SDL_WarpMouseInWindow immediatelly after turning off relative mode produces unexpected results #4339

Closed
Susko3 opened this issue Apr 26, 2021 · 14 comments
Assignees
Milestone

Comments

@Susko3
Copy link
Contributor

Susko3 commented Apr 26, 2021

If the mouse is moving, and you disable relative mode and then warp the cursor, sometimes the cursor will not warp.

Code used to reproduce the issue:

#include <SDL.h>
#include <SDL_hints.h>

int main(int argc, char** args)
{
    SDL_Init(SDL_INIT_EVENTS);
    SDL_Window* win = SDL_CreateWindow("test window", 100, 100, 1200, 500, SDL_WINDOW_RESIZABLE);
    SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2");

    bool loop = true;
    bool relative = false;
    SDL_SetRelativeMouseMode(SDL_bool::SDL_FALSE);

    while (loop)
    {
        SDL_Event e;
        while (SDL_PollEvent(&e) > 0)
        {
            if (e.type == SDL_EventType::SDL_KEYDOWN)
            {
                SDL_Keysym key = e.key.keysym;
                if (key.sym == SDLK_b)
                {
                    relative = !relative;
                    SDL_SetRelativeMouseMode(relative ? SDL_bool::SDL_TRUE : SDL_bool::SDL_FALSE);
                    if (!relative)
                    {
                        SDL_WarpMouseInWindow(win, 1000, 250);
                        SDL_Log("warp");
                    }
                    else
                        SDL_Log("toggle");
                }

                if (key.sym == SDLK_ESCAPE)
                    loop = false;
            }
        }
    }
    return 0;
}
video.mp4

How to reproduce:

  1. Press B once to enable relative mode (the OS cursor will dissapear)
  2. Start moving your mouse (I do it in a circle on the left side of the window, so it's easier to track)
  3. Press B to disable relative mode and warp the cursor
  4. The cursor will appear on the screen, but the location of the cursor is not predictable

The cursor is expected to be at the location that SDL_WarpMouseInWindow specified (on the right side of the window, with my example code). Sometimes the cursor will appear here as expected.
But if the bug occurs, the cursor will not warp, and will appear on the location of the last SDL_MOUSEMOTION event (on the left side in the video).

If you don't move your mouse, the cursor will always appear in the location specified by SDL_WarpMouseInWindow.

There is a slight difference between the latest main and 2.0.14: On 2.0.14 it's the same as main, but the cursor can also appear in the middle of the window.

Seems to happen much more frequently when mouse report rate is set to 1000 Hz. But I managed to reproduce it with 125 Hz, as seen in the logs below:

Normal logs:

INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=8916 windowid=1 which=0 state=0 x=83 y=116 xrel=29 yrel=-10)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=8923 windowid=1 which=0 state=0 x=122 y=104 xrel=39 yrel=-12)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=8932 windowid=1 which=0 state=0 x=167 y=94 xrel=45 yrel=-10)
INFO: SDL EVENT: SDL_KEYDOWN (timestamp=8936 windowid=1 state=pressed repeat=false scancode=5 keycode=98 mod=0)
INFO: SDL EVENT: SDL_TEXTINPUT (timestamp=8936 windowid=1 text='b')
INFO: warp
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=8939 windowid=1 which=0 state=0 x=1035 y=243 xrel=868 yrel=149)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=8947 windowid=1 which=0 state=0 x=1050 y=242 xrel=15 yrel=-1)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=8955 windowid=1 which=0 state=0 x=1048 y=242 xrel=-2 yrel=0)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=8963 windowid=1 which=0 state=0 x=1034 y=248 xrel=-14 yrel=6)

Logs when the cursor doesn't warp:

INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=10211 windowid=1 which=0 state=0 x=182 y=259 xrel=-3 yrel=2)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=10219 windowid=1 which=0 state=0 x=187 y=258 xrel=5 yrel=-1)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=10227 windowid=1 which=0 state=0 x=201 y=253 xrel=14 yrel=-5)
INFO: SDL EVENT: SDL_KEYDOWN (timestamp=10235 windowid=1 state=pressed repeat=false scancode=5 keycode=98 mod=0)
INFO: SDL EVENT: SDL_TEXTINPUT (timestamp=10235 windowid=1 text='b')
INFO: warp
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=10236 windowid=1 which=0 state=0 x=224 y=246 xrel=23 yrel=-7)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=10243 windowid=1 which=0 state=0 x=248 y=242 xrel=24 yrel=-4)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=10252 windowid=1 which=0 state=0 x=265 y=239 xrel=17 yrel=-3)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=10259 windowid=1 which=0 state=0 x=267 y=239 xrel=2 yrel=0)
@Susko3
Copy link
Contributor Author

Susko3 commented Apr 27, 2021

Looks to be similar to #4165. The linked PR (#4266) does not fix my issue.

@slouken slouken assigned slouken and unassigned icculus Jul 23, 2021
@slouken
Copy link
Collaborator

slouken commented Jul 26, 2021

As far as I can tell this is a bug in Windows. When relative mode is turned off, the mouse is warped back to where it was before relative mode was turned on. Your code immediately follows that with another call to set the mouse position. Both calls succeed, but occasionally, if the timing is close enough, the second one (in your code) is ignored.

@Susko3
Copy link
Contributor Author

Susko3 commented Oct 8, 2021

Unfortunately, the issue is still present. The issue is present even if not warping manually (only warped internally when SDL_SetRelativeMouseMode(SDL_FALSE) is called. The cursor can still jump to where Windows thinks it should be.

You can check where Windows thinks the cursor is by checking this option in main.cpl
image

The location displayed by Windows will not match the SDL_MOUSEMOTION x and y when using a high/low SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE. And turning off relative mode / warping in this state can cause the cursor to appear at the Windows location.

Slightly refined code:

#include <SDL.h>
#include <SDL_hints.h>

int main(int argc, char** args)
{
    SDL_Init(SDL_INIT_EVENTS);
    SDL_Window* win = SDL_CreateWindow("test window", 100, 100, 1200, 500, SDL_WINDOW_RESIZABLE);
    SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2");

    // speed scale is really high, so the SDL cursor will move faster than OS cursor
    SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE, "30");

    bool loop = true;
    bool relative = false;
    SDL_SetRelativeMouseMode(SDL_bool::SDL_FALSE);

    bool toggle = false;

    while (loop)
    {
        SDL_Event e;
        while (SDL_PollEvent(&e) > 0)
        {
            if (e.type == SDL_EventType::SDL_KEYDOWN)
            {
                SDL_Keysym key = e.key.keysym;
                if (key.sym == SDLK_b)
                    toggle = true;

                if (key.sym == SDLK_ESCAPE)
                    loop = false;
            }
        }

        if (toggle)
        {
            toggle = false;

            relative = !relative;
            SDL_SetRelativeMouseMode(relative ? SDL_bool::SDL_TRUE : SDL_bool::SDL_FALSE);

            if (!relative)
            {
                SDL_Log("rel off");

                // don't manually warp the cursor to test warping when SDL_SetRelativeMouseMode(SDL_FALSE)
                // SDL_Log("begin warp");
                // SDL_WarpMouseInWindow(win, 1000, 250);
                // SDL_Log("end warp");
            }
            else
            {
                SDL_Log("rel on");
            }
        }
    }
    return 0;
}

Excerpt from log:

INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18566 windowid=1 which=262203 state=0 x=1199 y=0 xrel=0 yrel=-240)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18568 windowid=1 which=262203 state=0 x=1139 y=0 xrel=-60 yrel=-270)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18570 windowid=1 which=262203 state=0 x=1079 y=0 xrel=-60 yrel=-240)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18572 windowid=1 which=262203 state=0 x=989 y=0 xrel=-90 yrel=-270)
INFO: SDL EVENT: SDL_KEYDOWN (timestamp=18575 windowid=1 state=pressed repeat=false scancode=5 keycode=98 mod=0)
INFO: SDL EVENT: SDL_TEXTINPUT (timestamp=18575 windowid=1 text='b')
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18575 windowid=1 which=262203 state=0 x=899 y=0 xrel=-90 yrel=-240)
INFO: rel off
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18580 windowid=1 which=0 state=0 x=547 y=195 xrel=-352 yrel=195)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18580 windowid=1 which=0 state=0 x=543 y=187 xrel=-4 yrel=-8)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18582 windowid=1 which=0 state=0 x=536 y=179 xrel=-7 yrel=-8)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=18584 windowid=1 which=0 state=0 x=530 y=171 xrel=-6 yrel=-8)

Notice the big jump when turning off relative mode.

@slouken
Copy link
Collaborator

slouken commented Oct 8, 2021

I'm not seeing that with the latest snapshot - there's no big jump and the cursor appears at the location in the mouse motion event.

Can you confirm you're able to reproduce it with the latest snapshot?
http://www.libsdl.org/tmp/SDL-2.0.zip

@slouken slouken reopened this Oct 8, 2021
dimhotepus pushed a commit to The-White-Box/SDL that referenced this issue Oct 9, 2021
Also, since we're flushing mouse motion before and including the warp, we don't need the isWin10FCUorNewer hack to simulate mouse warp motion.

Fixes libsdl-org#4339 and libsdl-org#4165
dimhotepus pushed a commit to The-White-Box/SDL that referenced this issue Oct 9, 2021
Also, since we're flushing mouse motion before and including the warp, we don't need the isWin10FCUorNewer hack to simulate mouse warp motion.

Fixes libsdl-org#4339 and libsdl-org#4165
@Susko3
Copy link
Contributor Author

Susko3 commented Oct 10, 2021

From now on, I'll be using the latest main when commenting.

The cursor can still sometimes end up in the wrong location.

I've dug a bit deeper, and Windows really is ignoring those SetCursorPos calls. We're just playing cat and mouse with Windows, and probabilities of SetCursorPos not working.

#4165 (comment)
Looking at this, double calling SetCursorPos will increase the odds of the cursor appearing in the right spot (if the first one fails, we got the second one to try and fix it).

I've found that calling SDL_WarpMouseInWindow before SDL_SetRelativeMouseMode gives us much better odds of SetCursorPos working. To the point I can't repro my issue. More on that later.


Excerpt from log when additionally calling SDL_WarpMouseInWindow after SDL_SetRelativeMouseMode (this is all done consumer side, and I haven't touched SDL code sans for adding more logging):

In this case, the additional SetCursorPos moved the cursor to the right place.

Notice the common pain point between SDL_SetRelativeMouseMode GetCursorPos 2 and 3. In most of my testing, this is where the expected cursor position changed to an unexpected one (presumably Windows is moving the cursor due to the actual, phyiscal mouse moving:
SDL_SetRelativeMouseMode GetCursorPos 1: 508, 459SDL_SetRelativeMouseMode GetCursorPos 3: 485, 460
looks like a plausible mouse move event)

INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=3006 windowid=1 which=393275 state=0 x=40 y=514 xrel=-10 yrel=0)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=3007 windowid=1 which=393275 state=0 x=30 y=515 xrel=-10 yrel=1)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=3008 windowid=1 which=393275 state=0 x=16 y=516 xrel=-14 yrel=1)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=3009 windowid=1 which=393275 state=0 x=6 y=516 xrel=-10 yrel=0)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=3010 windowid=1 which=393275 state=0 x=0 y=517 xrel=-9 yrel=1)
INFO: SDL_SetRelativeMouseMode GetCursorPos 1: 508, 459
INFO: WIN_WarpMouse :: GetCursorPos-before: 494, 459
INFO: WIN_WarpMouse :: SetCursorPos: 0, 517
INFO: WIN_WarpMouse :: GetCursorPos-after: 0, 517
INFO: SDL_SetRelativeMouseMode GetCursorPos 2: 0, 517
INFO: SDL_SetRelativeMouseMode GetCursorPos 3: 485, 460
INFO: SDL_SetRelativeMouseMode GetCursorPos 4: 485, 460
INFO: WIN_WarpMouse :: GetCursorPos-before: 485, 460
INFO: WIN_WarpMouse :: SetCursorPos: 0, 517
INFO: WIN_WarpMouse :: GetCursorPos-after: 0, 517
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=3015 windowid=1 which=0 state=0 x=0 y=517 xrel=-21 yrel=0)
INFO: SDL EVENT: SDL_WINDOWEVENT (timestamp=3015 windowid=1 event=SDL_WINDOWEVENT_LEAVE data1=0 data2=0)

Excerpt from log when calling SDL_WarpMouseInWindow before and after SDL_SetRelativeMouseMode:

In this case the pain point between SDL_SetRelativeMouseMode GetCursorPos 2 and 3 -- where Windows moved the cursor unexpectedly has been reduced to a very small margin of error. By calling SetCursorPos before the warp, we set the Windows cursor to a sane position, and when we disable relative mode afterwards, Windows will use this sane position as a starting point for further mouse movement if it ignores our later SetCursorPos calls.

I think that moving the mouse before turning off relative mode is the direction you want to go.

There might still be cases where all SetCursorPos calls fail, but it is improbable that this would happen.

INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=1134 windowid=1 which=393275 state=0 x=3 y=210 xrel=-3 yrel=0)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=1135 windowid=1 which=393275 state=0 x=2 y=210 xrel=-1 yrel=0)
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=1136 windowid=1 which=393275 state=0 x=0 y=210 xrel=-4 yrel=0)
INFO: WIN_WarpMouse :: GetCursorPos-before: 730, 415
INFO: WIN_WarpMouse :: SetCursorPos: 0, 210
INFO: WIN_WarpMouse :: GetCursorPos-after: 0, 210
INFO: SDL_SetRelativeMouseMode GetCursorPos 1: 0, 210
INFO: WIN_WarpMouse :: GetCursorPos-before: 0, 210
INFO: WIN_WarpMouse :: SetCursorPos: 0, 210
INFO: WIN_WarpMouse :: GetCursorPos-after: 0, 210
INFO: SDL_SetRelativeMouseMode GetCursorPos 2: 0, 210
INFO: SDL_SetRelativeMouseMode GetCursorPos 3: -3, 211
INFO: SDL_SetRelativeMouseMode GetCursorPos 4: -3, 211
INFO: WIN_WarpMouse :: GetCursorPos-before: -3, 211
INFO: WIN_WarpMouse :: SetCursorPos: 0, 210
INFO: WIN_WarpMouse :: GetCursorPos-after: 0, 210
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=1139 windowid=1 which=0 state=0 x=0 y=212 xrel=-7 yrel=2)
INFO: SDL EVENT: SDL_WINDOWEVENT (timestamp=1139 windowid=1 event=SDL_WINDOWEVENT_LEAVE data1=0 data2=0)

I've been debugging this with the following logging:

diff
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 6bb8307b9..0872547a4 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -19,10 +19,11 @@
 /* General mouse handling code for SDL */
+#include "../video/windows/SDL_windowsvideo.h"
 
 #include "SDL_hints.h"
 #include "SDL_timer.h"
 #include "SDL_events.h"
 #include "SDL_events_c.h"
@@ -833,27 +834,43 @@ SDL_SetRelativeMouseMode(SDL_bool enabled)
 
         if (mouse->relative_mode_warp)
             SDL_WarpMouseInWindow(focusWindow, focusWindow->w/2, focusWindow->h/2);
     }
 
+    SDL_Window* window = SDL_GetFocusWindow();
+    SDL_WindowData* data = (SDL_WindowData*)window->driverdata;
+    HWND hwnd = data->hwnd;
+    POINT win_pos;
+
+    GetCursorPos(&win_pos); ScreenToClient(hwnd, &win_pos); SDL_Log("SDL_SetRelativeMouseMode GetCursorPos 1: %d, %d", win_pos.x, win_pos.y);
+
+
     if (focusWindow) {
         SDL_UpdateWindowGrab(focusWindow);
 
         /* Put the cursor back to where the application expects it */
         if (!enabled) {
             SDL_WarpMouseInWindow(focusWindow, mouse->x, mouse->y);
         }
     }
 
+    GetCursorPos(&win_pos); ScreenToClient(hwnd, &win_pos); SDL_Log("SDL_SetRelativeMouseMode GetCursorPos 2: %d, %d", win_pos.x, win_pos.y);
+
     if (!enabled) {
         /* Update cursor visibility after we restore the mouse position */
         SDL_SetCursor(NULL);
     }
 
+    GetCursorPos(&win_pos); ScreenToClient(hwnd, &win_pos); SDL_Log("SDL_SetRelativeMouseMode GetCursorPos 3: %d, %d", win_pos.x, win_pos.y);
+
+
     /* Flush pending mouse motion - ideally we would pump events, but that's not always safe */
     SDL_FlushEvent(SDL_MOUSEMOTION);
 
+    GetCursorPos(&win_pos); ScreenToClient(hwnd, &win_pos); SDL_Log("SDL_SetRelativeMouseMode GetCursorPos 4: %d, %d", win_pos.x, win_pos.y);
+
+
     return 0;
 }
 
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index a43ea45b7..80fafd5a2 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -255,21 +255,31 @@ static void
 WIN_WarpMouse(SDL_Window * window, int x, int y)
 {
     SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
     HWND hwnd = data->hwnd;
     POINT pt;
+    POINT windows_pt;
+
 
     /* Don't warp the mouse while we're doing a modal interaction */
     if (data->in_title_click || data->focus_click_pending) {
         return;
     }
 
     pt.x = x;
     pt.y = y;
+
+    GetCursorPos(&windows_pt); ScreenToClient(hwnd, &windows_pt); SDL_Log("WIN_WarpMouse :: GetCursorPos-before: %d, %d", windows_pt.x, windows_pt.y);
+
+    SDL_Log("WIN_WarpMouse :: SetCursorPos: %d, %d", pt.x, pt.y);
+
     ClientToScreen(hwnd, &pt);
     WIN_SetCursorPos(pt.x, pt.y);
 
+    GetCursorPos(&windows_pt); ScreenToClient(hwnd, &windows_pt); SDL_Log("WIN_WarpMouse :: GetCursorPos-after: %d, %d", windows_pt.x, windows_pt.y);
+
+
     /* Send the exact mouse motion associated with this warp */
     SDL_SendMouseMotion(window, SDL_GetMouse()->mouseID, 0, x, y);
 }

@Susko3
Copy link
Contributor Author

Susko3 commented Oct 10, 2021

FYI, I've been using this code (slightly modified from #4165 (comment))

#include "SDL.h"

int
main(int argc, char* argv[])
{
    int w = 1024;
    int h = 768;
    SDL_Window* window;
    SDL_Renderer* renderer;
    SDL_Surface* image;
    SDL_Texture* sprite;
    SDL_Rect rect;
    SDL_bool done = SDL_FALSE;
    SDL_bool was_relative = SDL_FALSE;
    SDL_bool relative = SDL_FALSE;

    SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2");

    if (SDL_CreateWindowAndRenderer(w, h, SDL_WINDOW_SHOWN, &window, &renderer) < 0) {
        return 1;
    }

    image = SDL_LoadBMP("icon.bmp");
    if (!image) {
        return 2;
    }
    rect.w = image->w;
    rect.h = image->h;

    sprite = SDL_CreateTextureFromSurface(renderer, image);
    if (!sprite) {
        return 3;
    }
    SDL_FreeSurface(image);

    while (!done) {
        SDL_Event e;

        while (SDL_PollEvent(&e) == 1) {
            if (e.type == SDL_QUIT) {
                done = 1;
            }
            if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) {
                done = 1;
            }
        }

        SDL_GetMouseState(&rect.x, &rect.y);

        if (rect.x == 0 || rect.x == (w - 1) ||
            rect.y == 0 || rect.y == (h - 1)) {
            relative = SDL_FALSE;
        }
        else {
            relative = SDL_TRUE;
        }
        if (relative != was_relative) {
            SDL_WarpMouseInWindow(window, rect.x, rect.y);
            SDL_SetRelativeMouseMode(relative);
            if (!relative) {
                SDL_WarpMouseInWindow(window, rect.x, rect.y);
                SDL_FlushEvent(SDL_MOUSEMOTION);
            }
            was_relative = relative;
        }

        SDL_RenderClear(renderer);
        rect.x -= rect.w / 2;
        rect.y -= rect.h / 2;
        SDL_RenderCopy(renderer, sprite, NULL, &rect);
        SDL_RenderPresent(renderer);
    }
    SDL_Quit();
    return 0;
}

@mjdave
Copy link

mjdave commented Oct 10, 2021

I've been hitting these issues also, and dived a bit deeper into them this morning. One thing in particular that I have noticed is that calls to WarpMouseInWindow will fail when called after SetRelativeMode(false) when there are active mouse move events. It succeeds if the mouse is not moving at the time.

I am testing with the stable version of 2.0.16. This code is called immediately after polling the event queue every tick.

if(needsToUpdateSDLMouseHidden)
{
	needsToUpdateSDLMouseHidden = false;
	MJLog("setMouseHidden:%d", mouseHidden)

	SDL_SetRelativeMouseMode(mouseHidden ? SDL_TRUE : SDL_FALSE);
	if(!mouseHidden)
	{
		SDL_WarpMouseInWindow(window, windowInfo->halfWindowWidth, windowInfo->halfWindowHeight);
	}
}

int testX, testY;
SDL_GetMouseState(&testX, &testY);
MJLog("mouse :%d,%d", testX, testY)

A good result sets it to the center which is 1562,908:

42.727118:mouse :2420,890
42.743881:mouse :2420,890
42.760397:mouse :2420,890
42.777173:mouse :2420,890
42.795959:mouse :2420,890
42.810435:setMouseHidden:0
42.811504:mouse :2420,890
42.827108:mouse :2420,890
42.844055:mouse :1562,908
42.860399:mouse :1562,908
42.877306:mouse :1562,908

And here is a bad result when I was moving the mouse around at the time I exited relative mode:

22.009893:mouse :756,1120
22.026578:mouse :838,1115
22.043189:mouse :934,1097
22.059996:mouse :1014,1062
22.076476:mouse :1071,1024
22.093965:mouse :1102,963
22.109875:mouse :1093,909
22.126461:mouse :1054,862
22.146402:mouse :985,828
22.159846:setMouseHidden:0
22.160526:mouse :937,815
22.176472:mouse :872,804
22.192755:mouse :811,804
22.209560:mouse :754,807
22.227131:mouse :691,814
22.245148:mouse :622,825
22.267512:mouse :550,838
22.268821:mouse :547,840
22.292947:mouse :530,855

@Susko3
Copy link
Contributor Author

Susko3 commented Oct 10, 2021

@mjdave Try also calling SDL_WarpMouseInWindow before the call to SDL_SetRelativeMouseMode(false). Keep the after call to catch cases when the first one fails.

If you want your mouse to always be in the middle of the screen while in relative mode, you could use https://wiki.libsdl.org/SDL_HINT_MOUSE_RELATIVE_MODE_WARP and set it using SDL_SetHint(SDL_HINT_..., "1"). Keep in mind this doesn't use raw input and will instead use standard windows mouse movements, so mouse acceleration might apply.

@mjdave
Copy link

mjdave commented Oct 10, 2021

Interesting thanks I wasn't aware of that hint.

However I've now tried both of these, and get the same issue. I see no change if I call SDL_WarpMouseInWindow before SDL_SetRelativeMouseMode (as well as after).

If I use the warp hint, I do see very different acceleration curves, but I still see the cursor jump in the same way as before on the switch out. Logging the mouse position as above also still shows that the position is not being reset to the center when in relative mode. It still moves freely around, constrained within the window bounds, and the cursor still shows up at the logged location if there is constant mouse movement on the switch.

@0x1F9F1
Copy link
Collaborator

0x1F9F1 commented Oct 12, 2021

This is hopefully fixed by #4831. Any testing would be appreciated.

@slouken slouken added this to the 2.0.18 milestone Oct 15, 2021
@slouken
Copy link
Collaborator

slouken commented Oct 15, 2021

Please retest using the latest SDL snapshot at commit 88e9f77:
http://www.libsdl.org/tmp/SDL-2.0.zip

Thanks!

@mjdave
Copy link

mjdave commented Oct 15, 2021

Please retest using the latest SDL snapshot at commit 88e9f77: http://www.libsdl.org/tmp/SDL-2.0.zip

Thanks!

I can confirm that my issue is no longer present after this commit, and I haven't hit any other problems yet, will continue to test. Thank You very much!

@slouken
Copy link
Collaborator

slouken commented Oct 15, 2021

Thanks!

@slouken slouken closed this as completed Oct 15, 2021
@Susko3
Copy link
Contributor Author

Susko3 commented Nov 13, 2021

Unfortunately, this is still a problem. Even on the latest main branch.
Especially when one's mouse is set to 1000 Hz, where the unexpected jump is far more common.

With the test code, when the cursor leaves the window (and relative mode is therefore disabled) it will sometimes jump to the center of the window.

More precisely, it will jump to where the OS cursor currently is, which is the center of the window because it was previously clipped to there.

The jump happens precisely because Windows sometimes ignores the SetCursorPos() calls here, and doesn't move the cursor:
(This is probably a bug in Windows, and there probably isn't a quick fix for it. The only thing we can do is call SetCursorPos() multiple times and pray that one goes trough, but this is prone to failure.)

WIN_SetCursorPos(int x, int y)
{
/* We need to jitter the value because otherwise Windows will occasionally inexplicably ignore the SetCursorPos() or SendInput() */
SetCursorPos(x, y);
SetCursorPos(x+1, y);
SetCursorPos(x, y);

Probably the only real way to fix this issue is to keep the SDL and OS cursor in sync, so when relative mode is disabled (and SetCursorPos() fails), the OS and SDL cursors are close enough, so the jump will be unnoticeable.

So I suggest we go back to clipping the cursor to the whole window, as done here: PR#4266.
With that PR, the two cursors usually stay in sync, with a few pixel difference at most.

#4266 is merged, but that change was reverted here without any comment or reasoning.

If there is a specific reason for clipping the cursor to a 2x2 rect, we can settle on a hint that toggles the behavior between the two.

Latest test code (all of this was tested on latest `main` branch)
#include "SDL.h"

SDL_Window* window;
SDL_Renderer* renderer;

void draw_cursor()
{
    SDL_Rect cursor_rect;

    SDL_GetMouseState(&cursor_rect.x, &cursor_rect.y);
    cursor_rect.w = 26;
    cursor_rect.h = 26;
    cursor_rect.x -= cursor_rect.w / 2;
    cursor_rect.y -= cursor_rect.h / 2;

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);
    SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE);
    SDL_RenderFillRect(renderer, &cursor_rect);
    SDL_RenderPresent(renderer);
}

SDL_bool use_relative = SDL_FALSE;

void update_relative(int dx, int dy)
{
    int x, y, w, h;

    SDL_GetMouseState(&x, &y);
    SDL_GetWindowSize(window, &w, &h);

    x += dx;
    y += dy;

    // disable relative when outside of window.
    if (x < 0 || y < 0 || x >= w || y >= h) {
        use_relative = SDL_FALSE;
    }

    if (use_relative != SDL_GetRelativeMouseMode()) {
        SDL_SetRelativeMouseMode(use_relative);
    }
}

int main()
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_CreateWindowAndRenderer(1000, 700, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE, &window, &renderer);
    //SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2");

    SDL_bool main_loop = SDL_TRUE;
    while (main_loop) {
        SDL_Event e;
        while (SDL_PollEvent(&e) == 1) {
            switch (e.type)
            {
            case SDL_QUIT:
                main_loop = SDL_FALSE;
                break;

            case SDL_KEYDOWN:
                if (e.key.keysym.sym == SDLK_ESCAPE)
                    main_loop = SDL_FALSE;
                break;

            case SDL_MOUSEMOTION:
                if (SDL_GetRelativeMouseMode() == SDL_TRUE) {
                    update_relative(e.motion.xrel, e.motion.yrel);
                }
                break;

            case SDL_WINDOWEVENT:
                if (e.window.event == SDL_WINDOWEVENT_ENTER) {
                    use_relative = SDL_TRUE;
                    update_relative(0, 0);
                }
                break;
            }
        }
        draw_cursor();
    }
    SDL_Quit();
    return 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants