internal/glfw: bug fix: disabling cursor doesn't work well on remote desktop

This change applies the bug fix at glfw/glfw#1276

Updates #2961
This commit is contained in:
Hajime Hoshi 2024-04-14 15:58:31 +09:00
parent 86e0bcc264
commit 6df42f1a4b
4 changed files with 149 additions and 6 deletions

View File

@ -100,6 +100,7 @@ const (
_MAPVK_VSC_TO_VK = 1
_MONITOR_DEFAULTTONEAREST = 0x00000002
_MOUSE_MOVE_ABSOLUTE = 0x01
_MOUSE_VIRTUAL_DESKTOP = 0x02
_MSGFLT_ALLOW = 1
_OCR_CROSS = 32515
_OCR_HAND = 32649
@ -141,11 +142,18 @@ const (
_SIZE_MAXIMIZED = 2
_SIZE_MINIMIZED = 1
_SIZE_RESTORED = 0
_SM_CXCURSOR = 13
_SM_CXICON = 11
_SM_CXSMICON = 49
_SM_CYCAPTION = 4
_SM_CYCURSOR = 14
_SM_CYICON = 12
_SM_CXSCREEN = 0
_SM_CYSCREEN = 1
_SM_CYSMICON = 50
_SM_CXVIRTUALSCREEN = 78
_SM_CYVIRTUALSCREEN = 79
_SM_REMOTESESSION = 0x1000
_SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000
_SPI_GETMOUSETRAILS = 94
_SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001
@ -755,9 +763,11 @@ var (
procChangeWindowMessageFilterEx = user32.NewProc("ChangeWindowMessageFilterEx")
procClientToScreen = user32.NewProc("ClientToScreen")
procClipCursor = user32.NewProc("ClipCursor")
procCreateCursor = user32.NewProc("CreateCursor")
procCreateIconIndirect = user32.NewProc("CreateIconIndirect")
procCreateWindowExW = user32.NewProc("CreateWindowExW")
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procDestroyCursor = user32.NewProc("DestroyCursor")
procDestroyIcon = user32.NewProc("DestroyIcon")
procDestroyWindow = user32.NewProc("DestroyWindow")
procDispatchMessageW = user32.NewProc("DispatchMessageW")
@ -915,6 +925,26 @@ func _ClipCursor(lpRect *_RECT) error {
return nil
}
func _CreateCursor(hInst _HINSTANCE, xHotSpot int32, yHotSpot int32, nWidth int32, nHeight int32, pvANDPlane, pvXORPlane []byte) (_HCURSOR, error) {
var andPlane *byte
if len(pvANDPlane) > 0 {
andPlane = &pvANDPlane[0]
}
var xorPlane *byte
if len(pvXORPlane) > 0 {
xorPlane = &pvXORPlane[0]
}
r, _, e := procCreateCursor.Call(uintptr(hInst), uintptr(xHotSpot), uintptr(yHotSpot), uintptr(nWidth), uintptr(nHeight), uintptr(unsafe.Pointer(andPlane)), uintptr(unsafe.Pointer(xorPlane)))
runtime.KeepAlive(pvANDPlane)
runtime.KeepAlive(pvXORPlane)
if _HCURSOR(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) {
return 0, fmt.Errorf("glfw: CreateCursor failed: %w", e)
}
return _HCURSOR(r), nil
}
func _CreateBitmap(nWidth int32, nHeight int32, nPlanes uint32, nBitCount uint32, lpBits unsafe.Pointer) (_HBITMAP, error) {
r, _, e := procCreateBitmap.Call(uintptr(nWidth), uintptr(nHeight), uintptr(nPlanes), uintptr(nBitCount), uintptr(lpBits))
if _HBITMAP(r) == 0 {
@ -986,6 +1016,14 @@ func _DefWindowProcW(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPA
return _LRESULT(r)
}
func _DestroyCursor(hCursor _HCURSOR) error {
r, _, e := procDestroyCursor.Call(uintptr(hCursor))
if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) {
return fmt.Errorf("glfw: DestroyCursor failed: %w", e)
}
return nil
}
func _DestroyIcon(hIcon _HICON) error {
r, _, e := procDestroyIcon.Call(uintptr(hIcon))
if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) {

View File

@ -239,6 +239,55 @@ func createHelperWindow() error {
return nil
}
func createBlankCursor() error {
// HACK: Create a transparent cursor as using the NULL cursor breaks
// using SetCursorPos when connected over RDP
cursorWidth, err := _GetSystemMetrics(_SM_CXCURSOR)
if err != nil {
return err
}
cursorHeight, err := _GetSystemMetrics(_SM_CYCURSOR)
if err != nil {
return err
}
andMask := make([]byte, cursorWidth*cursorHeight/8)
for i := range andMask {
andMask[i] = 0xff
}
xorMask := make([]byte, cursorWidth*cursorHeight/8)
// 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)
c, _ := _CreateCursor(0, 0, 0, cursorWidth, cursorHeight, andMask, xorMask)
_glfw.platformWindow.blankCursor = c
return nil
}
func initRemoteSession() error {
if microsoftgdk.IsXbox() {
return nil
}
// Check if the current progress was started with Remote Desktop.
r, err := _GetSystemMetrics(_SM_REMOTESESSION)
if err != nil {
return err
}
_glfw.platformWindow.isRemoteSession = r > 0
// With Remote desktop, we need to create a blank cursor because of the cursor is Set to nil
// if cannot be moved to center in capture mode. If not Remote Desktop platformWindow.blankCursor stays nil
// and will perform has before (normal).
if _glfw.platformWindow.isRemoteSession {
if err := createBlankCursor(); err != nil {
return err
}
}
return nil
}
func platformInit() error {
// Changing the foreground lock timeout was removed from the original code.
// See https://github.com/glfw/glfw/commit/58b48a3a00d9c2a5ca10cc23069a71d8773cc7a4
@ -293,6 +342,10 @@ func platformInit() error {
return err
}
} else {
// Some hacks are needed to support Remote Desktop...
if err := initRemoteSession(); err != nil {
return err
}
if err := pollMonitorsWin32(); err != nil {
return err
}
@ -301,6 +354,12 @@ func platformInit() error {
}
func platformTerminate() error {
if _glfw.platformWindow.blankCursor != 0 {
if err := _DestroyCursor(_glfw.platformWindow.blankCursor); err != nil {
return err
}
}
if _glfw.platformWindow.deviceNotificationHandle != 0 {
if err := _UnregisterDeviceNotification(_glfw.platformWindow.deviceNotificationHandle); err != nil {
return err

View File

@ -66,14 +66,18 @@ type platformLibraryWindowState struct {
scancodes [KeyLast + 1]int
keynames [KeyLast + 1]string
// Where to place the cursor when re-enabled
// restoreCursorPosX and restoreCursorPosY indicates where to place the cursor when re-enabled
restoreCursorPosX float64
restoreCursorPosY float64
// The window whose disabled cursor mode is active
// disabledCursorWindow is the window whose disabled cursor mode is active
disabledCursorWindow *Window
// The window the cursor is captured in
// capturedCursorWindow is the window the cursor is captured in
capturedCursorWindow *Window
rawInput []byte
mouseTrailSize uint32
// isRemoteSession indicates if the process was started behind Remote Destop
isRemoteSession bool
// blankCursor is an invisible cursor, needed for special cases (see WM_INPUT handler)
blankCursor _HCURSOR
}

View File

@ -167,7 +167,10 @@ func (w *Window) updateCursorImage() error {
_SetCursor(cursor)
}
} else {
_SetCursor(0)
// Connected via Remote Desktop, nil cursor will present SetCursorPos the move the cursor.
// using a blank cursor fix that.
// When not via Remote Desktop, platformWindow.blankCursor should be nil.
_SetCursor(_glfw.platformWindow.blankCursor)
}
return nil
}
@ -907,8 +910,46 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
var dx, dy int
data := (*_RAWINPUT)(unsafe.Pointer(&_glfw.platformWindow.rawInput[0]))
if data.mouse.usFlags&_MOUSE_MOVE_ABSOLUTE != 0 {
dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX
dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY
if _glfw.platformWindow.isRemoteSession {
// Remote Desktop Mode
// As per https://github.com/Microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555
// MOUSE_MOVE_ABSOLUTE is a range from 0 through 65535, based on the screen size.
// Apparently, absolute mode only occurs over RDP though.
var smx int32 = _SM_CXSCREEN
var smy int32 = _SM_CYSCREEN
if data.mouse.usFlags&_MOUSE_VIRTUAL_DESKTOP != 0 {
smx = _SM_CXVIRTUALSCREEN
smy = _SM_CYVIRTUALSCREEN
}
width, err := _GetSystemMetrics(smx)
if err != nil {
_glfw.errors = append(_glfw.errors, err)
return 0
}
height, err := _GetSystemMetrics(smy)
if err != nil {
_glfw.errors = append(_glfw.errors, err)
return 0
}
pos := _POINT{
x: int32(float64(data.mouse.lLastX) / 65535.0 * float64(width)),
y: int32(float64(data.mouse.lLastY) / 65535.0 * float64(height)),
}
if err := _ScreenToClient(window.platform.handle, &pos); err != nil {
_glfw.errors = append(_glfw.errors, err)
return 0
}
dx = int(pos.x) - window.platform.lastCursorPosX
dy = int(pos.y) - window.platform.lastCursorPosY
} else {
// Normal mode
// We should have the right absolute coords in data.mouse
dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX
dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY
}
} else {
dx = int(data.mouse.lLastX)
dy = int(data.mouse.lLastY)
@ -2159,6 +2200,7 @@ func platformPollEvents() error {
// NOTE: Re-center the cursor only if it has moved since the last call,
// to avoid breaking glfwWaitEvents with WM_MOUSEMOVE
// The re-center is required in order to prevent the mouse cursor stopping at the edges of the screen.
if window.platform.lastCursorPosX != width/2 || window.platform.lastCursorPosY != height/2 {
if err := window.platformSetCursorPos(float64(width/2), float64(height/2)); err != nil {
return err