From 49914ab15bc40026628e1412df3b104676dccaff Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 25 May 2018 14:11:40 -0700 Subject: [PATCH] Fix disabled cursor mode over remote desktop There were two major issues: - RDP uses MOUSE_MOVE_ABSOLUTE for its input events, and the implementation of this was incorrect. Although poorly documented, the values actually range from 0-65535 and represent monitor positions, instead of being positions beforehand. The equally poorly documented MOUSE_VIRTUAL_DESKTOP flag specifies whether or not to use the SCREEN or VIRTUALSCREEN system metrics. - Using `SetCursor(NULL)` causes `SetCursorPos` to behave incorrectly over RDP; it doesn't actually move it on the connected machine. This has been fixed by creating an invisible cursor, which does get moved correctly. Aside from that, the center of the window is now cached and the cursor test now supports pressing M to move the cursor to the center. Fixes #1276. --- README.md | 2 ++ src/win32_platform.h | 4 +++ src/win32_window.c | 67 +++++++++++++++++++++++++++++++++++--------- tests/cursor.c | 8 ++++++ 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0573dcd93..32ea9cc2c 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,7 @@ information on what to include when reporting a bug. - [Win32] Bugfix: The HID device notification was not unregistered (#1170) - [Win32] Bugfix: `glfwCreateWindow` activated window even with `GLFW_FOCUSED` hint set to false (#1179,#1180) +- [Win32] Bugfix: Disabled cursor mode did not work over remote desktop (#1276) - [X11] Moved to XI2 `XI_RawMotion` for disable cursor mode motion input (#125) - [X11] Replaced `_GLFW_HAS_XF86VM` compile-time option with dynamic loading - [X11] Bugfix: `glfwGetVideoMode` would segfault on Cygwin/X @@ -458,6 +459,7 @@ skills. - Santi Zupancic - Jonas Ådahl - Lasse Öörni + - Pokechu22 - All the unmentioned and anonymous contributors in the GLFW community, for bug reports, patches, feedback, testing and encouragement diff --git a/src/win32_platform.h b/src/win32_platform.h index 598151468..a096eb341 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -279,6 +279,10 @@ typedef struct _GLFWwindowWin32 // The last received cursor position, regardless of source int lastCursorPosX, lastCursorPosY; + // Cached center positions + int centerX, centerY; + // An invisible cursor, needed for special cases (see WM_INPUT handler) + HCURSOR blankCursor; } _GLFWwindowWin32; diff --git a/src/win32_window.c b/src/win32_window.c index d467f2ae3..6b1ff29bd 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -230,9 +230,7 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area) // static void centerCursor(_GLFWwindow* window) { - int width, height; - _glfwPlatformGetWindowSize(window, &width, &height); - _glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0); + _glfwPlatformSetCursorPos(window, window->win32.centerX, window->win32.centerY); } // Updates the cursor image according to its cursor mode @@ -247,7 +245,7 @@ static void updateCursorImage(_GLFWwindow* window) SetCursor(LoadCursorW(NULL, IDC_ARROW)); } else - SetCursor(NULL); + SetCursor(window->win32.blankCursor); } // Updates the cursor clip rect @@ -856,11 +854,32 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, data = _glfw.win32.rawInput; if (data->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - dx = data->data.mouse.lLastX - window->win32.lastCursorPosX; - dy = data->data.mouse.lLastY - window->win32.lastCursorPosY; + // As per https://github.com/Microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555 + // MOUSE_MOVE_ABSOLUTE is a range from 0 through 65535, based on the screen size. + // As far as I can tell, absolute mode only occurs over RDP though. + const int width = GetSystemMetrics((data->data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) ? SM_CXVIRTUALSCREEN : SM_CXSCREEN); + const int height = GetSystemMetrics((data->data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) ? SM_CYVIRTUALSCREEN : SM_CYSCREEN); + POINT pos; + pos.x = (int) ((data->data.mouse.lLastX / 65535.0f) * width); + pos.y = (int) ((data->data.mouse.lLastY / 65535.0f) * height); + ScreenToClient(window->win32.handle, &pos); + + // One other unfortunate thing is that re-centering the cursor will still fire an + // input event; assume that any motion to the center is our re-centering and ignore it + if (pos.x == window->win32.centerX && pos.y == window->win32.centerY) + break; + + dx = pos.x - window->win32.lastCursorPosX; + dy = pos.y - window->win32.lastCursorPosY; } else { + if (data->data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Win32: Unexpected raw input combination MOUSE_VIRTUAL_DESKTOP but not MOUSE_MOVE_ABSOLUTE"); + break; + } dx = data->data.mouse.lLastX; dy = data->data.mouse.lLastY; } @@ -923,6 +942,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, const GLFWbool maximized = wParam == SIZE_MAXIMIZED || (window->win32.maximized && wParam != SIZE_RESTORED); + int width, height; + _glfwPlatformGetWindowSize(window, &width, &height); if (_glfw.win32.disabledCursorWindow == window) updateClipRect(window); @@ -949,6 +970,9 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, window->win32.iconified = iconified; window->win32.maximized = maximized; + + window->win32.centerX = width / 2; + window->win32.centerY = height / 2; return 0; } @@ -1176,6 +1200,19 @@ static int createNativeWindow(_GLFWwindow* window, window->win32.transparent = GLFW_TRUE; } + // HACK: Create a transparent cursor as using the NULL cursor breaks + // using SetCursorPos when connected over RDP + int cursorWidth = GetSystemMetrics(SM_CXCURSOR); + int cursorHeight = GetSystemMetrics(SM_CYCURSOR); + unsigned char* andMask = calloc(cursorWidth * cursorHeight / 8, sizeof(unsigned char)); + memset(andMask, 0xFF, cursorWidth * cursorHeight / 8); + unsigned char* xorMask = calloc(cursorWidth * cursorHeight / 8, sizeof(unsigned char)); + // Cursor creation might fail, but that's fine as we get NULL in that case, + // which serves as an acceptable fallback blank cursor (other than on RDP) + window->win32.blankCursor = CreateCursor(NULL, 0, 0, cursorWidth, cursorHeight, andMask, xorMask); + free(andMask); + free(xorMask); + return GLFW_TRUE; } @@ -1313,6 +1350,9 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) if (window->win32.smallIcon) DestroyIcon(window->win32.smallIcon); + + if (window->win32.blankCursor) + DestroyCursor(window->win32.blankCursor); } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) @@ -1750,15 +1790,12 @@ void _glfwPlatformPollEvents(void) window = _glfw.win32.disabledCursorWindow; if (window) { - int width, height; - _glfwPlatformGetWindowSize(window, &width, &height); - // NOTE: Re-center the cursor only if it has moved since the last call, // to avoid breaking glfwWaitEvents with WM_MOUSEMOVE - if (window->win32.lastCursorPosX != width / 2 || - window->win32.lastCursorPosY != height / 2) + if (window->win32.lastCursorPosX != window->win32.centerX || + window->win32.lastCursorPosY != window->win32.centerY) { - _glfwPlatformSetCursorPos(window, width / 2, height / 2); + centerCursor(window); } } } @@ -1806,7 +1843,11 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos) window->win32.lastCursorPosY = pos.y; ClientToScreen(window->win32.handle, &pos); - SetCursorPos(pos.x, pos.y); + if (!SetCursorPos(pos.x, pos.y)) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, + "Win32: Failed to set cursor position"); + } } void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) diff --git a/tests/cursor.c b/tests/cursor.c index 4cc74ad51..6f51d1493 100644 --- a/tests/cursor.c +++ b/tests/cursor.c @@ -205,6 +205,14 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, case GLFW_KEY_6: glfwSetCursor(window, standard_cursors[5]); break; + + case GLFW_KEY_M: + { + int width, height; + glfwGetWindowSize(window, &width, &height); + glfwSetCursorPos(window, width / 2, height / 2); + break; + } } }