Cursor is offset from actual mouse location when opening GUIs while moving mouse
The bug
When opening a GUI, the location the game thinks the cursor is at will not match the actual position of the mouse, the mouse was moved before opening
How to reproduce
- Move the mouse sideways by a few inches.
- Press e to open your inventory.
- Observe that either no item is hovered, or the item that is hovered is not the one under your cursor (if not the case, move the mouse faster before).
- Move the mouse, and then see that the hovered item resyncs.
- Close the inventory with esc, and then reopen it with e (do not move the mouse between these steps).
- Observe that the hovered item is the one under your mouse.
Notes
- The offset seems to be the distance the mouse is moved since the last GUI was closed (acting as if the mouse was moving in a GUI and not being constantly snapped to the center for the camera)
- Another way to reproduce that is to move the mouse about a 3rd of an inch to the left and then press esc, which will cause the "Options" button to be hovered.
Linked Issues
Created Issue:
Cursor is offset from actual mouse location when opening GUIs while moving mouse
The bug
When opening a GUI, the location the game thinks the cursor is at will not match the actual position of the mouse, the mouse was moved before opening
How to reproduce
- Move the mouse sideways by a few inches.
- Press e to open your inventory.
- Observe that either no item is hovered, or the item that is hovered is not the one under your cursor (if not the case, move the mouse faster before).
- Move the mouse, and then see that the hovered item resyncs.
- Close the inventory with esc, and then reopen it with e (do not move the mouse between these steps).
- Observe that the hovered item is the one under your mouse.
Notes
- The offset seems to be the distance the mouse is moved since the last GUI was closed (acting as if the mouse was moving in a GUI and not being constantly snapped to the center for the camera)
- Another way to reproduce that is to move the mouse about a 3rd of an inch to the left and then press esc, which will cause the "Options" button to be hovered.
relates to
is duplicated by
Massive comment inbound:
I decided to look over this more thoroughly.
1.12.2
In 1.12.2, the majority of this logic is in MouseHelper, which uses Mouse.setGrabbed. This method is implemented by LWJGL here, but the main logic is in implementation.grabMouse, for windows implemented here. That calls WindowsMouse.grab, which doesn't do anything interesting; however, the relative movement does happen in WindowsMouse.handleMouseMoved.
The logic that actually moves the cursor to the center of the screen is in WindowsDisplay.updateCursor. This handles displaying the cursor and also setting it to a transparent/hidden cursor. It also calls centerCursor, which in turn calls two methods. The second is current_display.setMousePosition (where current_display is this; I'm not sure why it's set up like that), which calls WindowsMouse.setPosition. That just handles some internal stuff. The more important one is nSetCursorPosition, which is a native method implemented in org_lwjgl_opengl_Display.c. That calls windows' SetCursorPos, which presumably does correctly handle RDP things.
1.13
LWJGL 3 doesn't have a general equivalent to Mouse.setGrabbed, other than with nuklear (which is not used by Minecraft). MouseHelper was completely rewritten to manage mouse positions itself (per forge's MCPConfig). The code now looks something like this (these names are my guesses based on previous MCP names for similar code):
public void setIngameFocus() { if (this.minecraft.isDisplayActive()) { if (!this.inGameHasFocus) { if (!Minecraft.IS_RUNNING_ON_MAC) { KeyBinding.updateKeyBindState(); } this.inGameHasFocus = true; this.mouseX = (double)(this.minecraft.mainWindow.getWidth() / 2); this.mouseY = (double)(this.minecraft.mainWindow.getHeight() / 2); GLFW.glfwSetCursorPos(this.minecraft.mainWindow.getHandle(), this.mouseX, this.mouseY); GLFW.glfwSetInputMode(this.minecraft.mainWindow.getHandle(), 208897 /* GLFW_CURSOR */, 212995 /* GLFW_CURSOR_DISABLED */); this.minecraft.displayGuiScreen((GuiScreen)null); this.minecraft.leftClickCounter = 10000; } } } public void setIngameNotInFocus() { if (this.inGameHasFocus) { this.inGameHasFocus = false; GLFW.glfwSetInputMode(this.minecraft.mainWindow.getHandle(), 208897 /* GLFW_CURSOR */, 212993 /* GLFW_CURSOR_NORMAL */); this.mouseX = (double)(this.minecraft.mainWindow.getWidth() / 2); this.mouseY = (double)(this.minecraft.mainWindow.getHeight() / 2); GLFW.glfwSetCursorPos(this.minecraft.mainWindow.getHandle(), this.mouseX, this.mouseY); } }
These methods are each called once to change the current state (and actually match the general structure of methods declared in Minecraft before). In any case, LWJGL3's GLFW component is a pretty thin wrapper around GLFW itself, so I won't bother to link to it directly. Per glfwSetInputMode's documentation, GLFW_CURSOR_DISABLED is basically the same thing as grabbed cursor, and glfwSetCursorPos behaves differently in that mode. So, the order of setting the position before disabling, but re-enabling before moving the cursor is correct, as part of the fix to MC-121705.
The implementation of glfwSetInputMode is here. Note the call to _glfwPlatformGetCursorPos(window, &window->virtualCursorPosX, &window->virtualCursorPosY). Those fields are declared here and are described as "virtual cursor position when cursor is disabled"; they're used in glfwGetCursorPos and in glfwSetCursorPos when in the disabled mode.
glfwSetInputMode calls the platform-depended _glfwPlatformSetCursorMode; for windows that is this. That in turn calls functions that enable and disable the cursor, which stores the cursor position and centers the cursor. It also registers a raw input device (using windows' RegisterRawInputDevices). Apart from that last step, this more or less matches LWJGL2.
The differences come in the way it handles mouse movement. The handler for WM_MOUSEMOVE is the equivalent to LWJGL2's system... but it doesn't run when in the disabled cursor mode. Instead the raw input handler, WM_INPUT, is used. It looks for changes in cursor position and computes the deltas, and then calls _glfwInputCursorPos with the previous position plus the delta. However, in no part of the process does it move the cursor back to the center; it just keeps it in the same position. I suspect that's the cause of the issue. ignore that, it does center it, hm.
Unfortunately I'm going to need to fix my RDP setup to test further... but this is a start at least.
But in my case the Bug happents in the main menu, what to do?
@Til, this report here has been resolved for a rather long time, so you are likely experiencing a different issue.
Please create a new report and attach a forced debug crash report. The crash report can be created by pressing F3 + C for at least 10 seconds in the main menu; the crash report file can be found under minecraft/crash-reports/crash-<DATE>-client.txt.