-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Comments
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. |
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? |
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
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
From now on, I'll be using the latest The cursor can still sometimes end up in the wrong location. I've dug a bit deeper, and Windows really is ignoring those #4165 (comment) I've found that calling Excerpt from log when additionally calling In this case, the additional Notice the common pain point between
Excerpt from log when calling In this case the pain point between 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
I've been debugging this with the following logging: diffdiff --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);
} |
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;
} |
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.
A good result sets it to the center which is 1562,908:
And here is a bad result when I was moving the mouse around at the time I exited relative mode:
|
@mjdave Try also calling 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 |
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. |
This is hopefully fixed by #4831. Any testing would be appreciated. |
Please retest using the latest SDL snapshot at commit 88e9f77: 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! |
Thanks! |
Unfortunately, this is still a problem. Even on the latest 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 SDL/src/video/windows/SDL_windowsmouse.c Lines 251 to 256 in 881f747
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 So I suggest we go back to clipping the cursor to the whole window, as done here: PR#4266. #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;
} |
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:
video.mp4
How to reproduce:
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
and2.0.14
: On2.0.14
it's the same asmain
, 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:
Logs when the cursor doesn't warp:
The text was updated successfully, but these errors were encountered: