Skip to content

Commit 8fee82d

Browse files
committed
Improve relative motion handling over RDP
CR and research: @danielj
1 parent eb3bf80 commit 8fee82d

File tree

3 files changed

+93
-9
lines changed

3 files changed

+93
-9
lines changed

src/video/windows/SDL_windowsevents.c

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -747,23 +747,86 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
747747
if ((rawmouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
748748
SDL_SendMouseMotion(data->window, mouseID, 1, (int)rawmouse->lLastX, (int)rawmouse->lLastY);
749749
} else if (rawmouse->lLastX || rawmouse->lLastY) {
750+
/* This is absolute motion, probably over RDP
751+
752+
Notes on how RDP appears to work, as of Windows 10 2004:
753+
- SetCursorPos() calls are cached, with multiple calls coalesced into a single call that's sent to the RDP client. If the last call to SetCursorPos() has the same value as the last one that was sent to the client, it appears to be ignored and not sent. This means that we need to jitter the SetCursorPos() position slightly in order for the recentering to work correctly.
754+
- User mouse motion is coalesced with SetCursorPos(), so the WM_INPUT positions we see will not necessarily match the positon we requested with SetCursorPos().
755+
- SetCursorPos() outside of the bounds of the focus window appears not to do anything.
756+
- SetCursorPos() while the cursor is NULL doesn't do anything
757+
758+
We handle this by creating a safe area within the application window, and when the mouse leaves that safe area, we warp back to the opposite side. Any single motion > 50% of the safe area is assumed to be a warp and ignored.
759+
*/
750760
/* synthesize relative moves from the abs position */
751-
static SDL_Point lastMousePoint;
752761
SDL_bool virtual_desktop = (rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) ? SDL_TRUE : SDL_FALSE;
753762
int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
754763
int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
755764
int x = (int)(((float)rawmouse->lLastX / 65535.0f) * w);
756765
int y = (int)(((float)rawmouse->lLastY / 65535.0f) * h);
757-
758-
if (lastMousePoint.x == 0 && lastMousePoint.y == 0) {
759-
lastMousePoint.x = x;
760-
lastMousePoint.y = y;
766+
int relX, relY;
767+
RECT screenRect;
768+
RECT hwndRect;
769+
RECT boundsRect;
770+
int boundsWidth, boundsHeight;
771+
772+
/* Calculate relative motion */
773+
if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) {
774+
data->last_raw_mouse_position.x = x;
775+
data->last_raw_mouse_position.y = y;
776+
}
777+
relX = (int)(x - data->last_raw_mouse_position.x);
778+
relY = (int)(y - data->last_raw_mouse_position.y);
779+
780+
/* Calculate screen rect */
781+
screenRect.left = 0;
782+
screenRect.right = w;
783+
screenRect.top = 0;
784+
screenRect.bottom = h;
785+
786+
/* Calculate client rect */
787+
GetClientRect(hwnd, &hwndRect);
788+
ClientToScreen(hwnd, (LPPOINT) & hwndRect);
789+
ClientToScreen(hwnd, (LPPOINT) & hwndRect + 1);
790+
791+
/* Calculate bounds rect */
792+
IntersectRect(&boundsRect, &screenRect, &hwndRect);
793+
InflateRect(&boundsRect, -32, -32);
794+
boundsWidth = (boundsRect.right - boundsRect.left);
795+
boundsHeight = (boundsRect.bottom - boundsRect.top);
796+
797+
if ((boundsWidth > 0 && SDL_abs(relX) > (boundsWidth / 2)) ||
798+
(boundsHeight > 0 && SDL_abs(relY) > (boundsHeight / 2))) {
799+
/* Expected motion for warping below, ignore this */
800+
} else {
801+
SDL_SendMouseMotion(data->window, mouseID, 1, relX, relY);
802+
803+
if (x < boundsRect.left || x > boundsRect.right ||
804+
y < boundsRect.top || y > boundsRect.bottom) {
805+
/* Warp back to the opposite side, assuming more motion in the current direction */
806+
int warpX;
807+
int warpY;
808+
809+
if (x < boundsRect.left) {
810+
warpX = boundsRect.right;
811+
} else if (x > boundsRect.right) {
812+
warpX = boundsRect.left;
813+
} else {
814+
warpX = x;
815+
}
816+
817+
if (y < boundsRect.top) {
818+
warpY = boundsRect.bottom;
819+
} else if (y > boundsRect.bottom) {
820+
warpY = boundsRect.top;
821+
} else {
822+
warpY = y;
823+
}
824+
SetCursorPos(warpX, warpY);
825+
}
761826
}
762827

763-
SDL_SendMouseMotion(data->window, mouseID, 1, (int)(x-lastMousePoint.x), (int)(y-lastMousePoint.y));
764-
765-
lastMousePoint.x = x;
766-
lastMousePoint.y = y;
828+
data->last_raw_mouse_position.x = x;
829+
data->last_raw_mouse_position.y = y;
767830
}
768831
WIN_CheckRawMouseButtons(rawmouse->usButtonFlags, data, mouseID);
769832
} else if (isCapture) {

src/video/windows/SDL_windowsmouse.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929

3030
HCURSOR SDL_cursor = NULL;
31+
static SDL_Cursor *SDL_blank_cursor = NULL;
3132

3233
static int rawInputEnableCount = 0;
3334

@@ -159,6 +160,16 @@ WIN_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
159160
return cursor;
160161
}
161162

163+
static SDL_Cursor *
164+
WIN_CreateBlankCursor()
165+
{
166+
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, 32, 32, 32, SDL_PIXELFORMAT_ARGB8888);
167+
if (surface) {
168+
return WIN_CreateCursor(surface, 0, 0);
169+
}
170+
return NULL;
171+
}
172+
162173
static SDL_Cursor *
163174
WIN_CreateSystemCursor(SDL_SystemCursor id)
164175
{
@@ -210,6 +221,9 @@ WIN_FreeCursor(SDL_Cursor * cursor)
210221
static int
211222
WIN_ShowCursor(SDL_Cursor * cursor)
212223
{
224+
if (!cursor) {
225+
cursor = SDL_blank_cursor;
226+
}
213227
if (cursor) {
214228
SDL_cursor = (HCURSOR)cursor->driverdata;
215229
} else {
@@ -309,6 +323,8 @@ WIN_InitMouse(_THIS)
309323
mouse->GetGlobalMouseState = WIN_GetGlobalMouseState;
310324

311325
SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
326+
327+
SDL_blank_cursor = WIN_CreateBlankCursor();
312328
}
313329

314330
void
@@ -318,6 +334,10 @@ WIN_QuitMouse(_THIS)
318334
rawInputEnableCount = 1;
319335
ToggleRawInput(SDL_FALSE);
320336
}
337+
338+
if (SDL_blank_cursor) {
339+
SDL_FreeCursor(SDL_blank_cursor);
340+
}
321341
}
322342

323343
#endif /* SDL_VIDEO_DRIVER_WINDOWS */

src/video/windows/SDL_windowswindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ typedef struct
5252
SDL_bool windowed_mode_was_maximized;
5353
SDL_bool in_window_deactivation;
5454
RECT cursor_clipped_rect;
55+
SDL_Point last_raw_mouse_position;
5556
struct SDL_VideoData *videodata;
5657
#if SDL_VIDEO_OPENGL_EGL
5758
EGLSurface egl_surface;

0 commit comments

Comments
 (0)