Sudden mouse deltas in the cursor callback handler when the window just grabbed mouse cursor

Hi! There is my code:

bool isMouseGrabbed(GLFWwindow* window)
{
	return glfwGetInputMode(window, GLFW_CURSOR) != GLFW_CURSOR_NORMAL;
}

void setMouseGrabbed(GLFWwindow* window, bool grabbed)
{
	if (grabbed)
	{
		oldMousePositionX = windowWidth / 2.0;
		oldMousePositionY = windowHeight / 2.0;
		glfwSetCursorPos(window, oldMousePositionX, oldMousePositionY);
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
	}
	else if (isMouseGrabbed(window))
	{
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
	}
}

void glfw_callback_MouseButton(GLFWwindow* window, int button, int action, int mods)
{
	if (action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_LEFT && !isMouseGrabbed(window))
	{
		std::cout << "button callback" << std::endl;

		double mousePositionX;
		double mousePositionY;
		glfwGetCursorPos(window, &mousePositionX, &mousePositionY);
		std::cout << "Left mouse click at [" << mousePositionX << "," << mousePositionY << "]" << std::endl;

		setMouseGrabbed(window, true);
		std::cout << "Mouse is grabbed." << std::endl;
	}
}

void glfw_callback_MouseMove(GLFWwindow* window, double x, double y)
{
	if (isMouseGrabbed(window))
	{
		int mouseDeltaX = static_cast<int>(x - oldMousePositionX);
		int mouseDeltaY = static_cast<int>(y - oldMousePositionY);
		std::cout << "Deltas: " << mouseDeltaX << "," << mouseDeltaY << std::endl;

		if (mouseDeltaX || mouseDeltaY)
		{
			//const float mouseSensitivity = 10.0f;
			//rotateCamera(camera, mouseDeltaX, mouseDeltaY, mouseSensitivity);

			oldMousePositionX = x;
			oldMousePositionY = y;
		}
	}
}

My problem is:
When my window is unfocused, i click on it and sometimes i see the output to the console Deltas: ... with some X / Y values. This leads to unnecessary rotation of the camera.
There is my log:

Left mouse click at [564,216]
Mouse is grabbed.
Mouse is released.
button callback
Left mouse click at [540,45]
Mouse is grabbed.
Deltas: 220,-195
Deltas: -220,195
Mouse is released.
button callback
Left mouse click at [496,18]
Mouse is grabbed.
Mouse is released.
button callback
Left mouse click at [505,140]
Mouse is grabbed.
Mouse is released.
button callback
Left mouse click at [491,88]
Mouse is grabbed.
Deltas: 171,-152
Deltas: -171,152
Mouse is released.

This bug(?) only appears on Windows. I have tested it on my old Linux notebook, but it’s fine.

In setMouseGrabbed rather than calling glfwSetCursorPos with your mouse positions I would call glfwGetCursorPos after glfwSetInputMode, like this:

void setMouseGrabbed(GLFWwindow* window, bool grabbed)
{
	if (grabbed)
	{
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
		glfwGetCursorPos(window, &oldMousePositionX, &oldMousePositionY);
	}
	else if (isMouseGrabbed(window))
	{
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
	}
}

This might help (I can’t test as you haven’t shown code for the entire application, and many issues will depend on how you run the update loop etc.), if not I can check my own code later on and get back to you on a better solution.

No :slightly_frowning_face: The problem stlill exists.

You could try the cursor.c and events.c tests to see if you can reproduce the issue there.

If you are able to create a minimal example code which demonstrates the problem (for instance based off one of the above tests) this would be helpful.

I’m unable to build GLFW and the tests on Windows. I don’t know how to do it. CMake GUI gives me an error:

CMake Error at CMakeLists.txt:3 (project):
  Generator

    Visual Studio 16 2019

  could not find any instance of Visual Studio.

Configuring incomplete, errors occurred!

I have Visual studio 2022, not 2019.

Here is my own example:

#include <iostream>
#include <GLFW/glfw3.h>

int windowWidth = 640;
int windowHeight = 480;

double oldMousePositionX;
double oldMousePositionY;

bool isMouseGrabbed(GLFWwindow* window)
{
	return glfwGetInputMode(window, GLFW_CURSOR) != GLFW_CURSOR_NORMAL;
}

void setMouseGrabbed(GLFWwindow* window, bool grabbed)
{
	if (grabbed)
	{
		//glfwSetCursorPos(window, windowWidth / 2.0, windowHeight / 2.0);
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
		glfwGetCursorPos(window, &oldMousePositionX, &oldMousePositionY);
	}
	else if (isMouseGrabbed(window))
	{
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
	}
}

void glfw_callback_Keyboard(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	switch (action)
	{
		case GLFW_PRESS:
			switch (key)
			{
				case GLFW_KEY_ESCAPE:
				case GLFW_KEY_ENTER:
				case GLFW_KEY_KP_ENTER:
					if (isMouseGrabbed(window))
					{
						setMouseGrabbed(window, false);
						std::cout << "Mouse is released." << std::endl;
					}
					else
					{
						std::cout << "Window will be closed soon..." << std::endl;
						glfwSetWindowShouldClose(window, GLFW_TRUE);
					}
				break;
			}

			break;
	}
}

void glfw_callback_MouseButton(GLFWwindow* window, int button, int action, int mods)
{
	if (action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_LEFT && !isMouseGrabbed(window))
	{
		std::cout << "button callback" << std::endl;

		double mousePositionX;
		double mousePositionY;
		glfwGetCursorPos(window, &mousePositionX, &mousePositionY);
		std::cout << "Left mouse click at [" << mousePositionX << "," << mousePositionY << "]" << std::endl;

		setMouseGrabbed(window, true);
		std::cout << "Mouse is grabbed." << std::endl;
	}
}

void glfw_callback_MouseMove(GLFWwindow* window, double x, double y)
{
	if (isMouseGrabbed(window))
	{
		int mouseDeltaX = static_cast<int>(x - oldMousePositionX);
		int mouseDeltaY = static_cast<int>(y - oldMousePositionY);
		std::cout << "Deltas: " << mouseDeltaX << "," << mouseDeltaY << std::endl;
		if (mouseDeltaX || mouseDeltaY)
		{
			//const float mouseSensitivity = 10.0f;
			//rotateCamera(camera, mouseDeltaX, mouseDeltaY, mouseSensitivity);

			oldMousePositionX = x;
			oldMousePositionY = y;
		}
	}
}

int main()
{
	std::cout << "Initializing GLFW...";

	if (glfwInit() == GLFW_FALSE)
	{
		std::cout << "FAILED!" << std::endl;
		return EXIT_FAILURE;
	}
	std::cout << "OK!" << std::endl;

	std::cout << "Creating a window...";

	const char* windowTitle = "C++";
	GLFWwindow* glfwWindow = glfwCreateWindow(windowWidth, windowHeight, windowTitle, NULL, NULL);

	if (!glfwWindow)
	{
		std::cout << "FAILED!" << std::endl;
		glfwTerminate();
		return EXIT_FAILURE;
	}
	std::cout << "OK!" << std::endl;

	std::cout << "Attaching OpenGL context to the window...";
	glfwMakeContextCurrent(glfwWindow);
	std::cout << "DONE!" << std::endl;

	glfwSetKeyCallback(glfwWindow, glfw_callback_Keyboard);
	glfwSetMouseButtonCallback(glfwWindow, glfw_callback_MouseButton);
	glfwSetCursorPosCallback(glfwWindow, glfw_callback_MouseMove);

	while (glfwWindowShouldClose(glfwWindow) == GLFW_FALSE)
	{
		glfwPollEvents();

		//glfwSwapBuffers(glfwWindow);
	}

	glfwMakeContextCurrent(NULL);
	glfwDestroyWindow(glfwWindow);
	glfwTerminate();

	return EXIT_SUCCESS;
}

It’s important to click to INACTIVE (unfocused) window.

I was able to replicate large mouse deltas when moving the mouse across the unfocussed window and clicking on it, but I did not see the paired results you had. What you might be able to do is ignore mouse inputs for N frames after setMouseGrabbed. I edited your code to add that and it filtered out the initial large value for me, see below.

Another thing you could try is setting raw mouse motion to see if this is available and might help.

#include <iostream>
#include <GLFW/glfw3.h>

int windowWidth = 640;
int windowHeight = 480;

double oldMousePositionX;
double oldMousePositionY;

int inputIgnore = 3;

bool isMouseGrabbed(GLFWwindow* window)
{
	return glfwGetInputMode(window, GLFW_CURSOR) != GLFW_CURSOR_NORMAL;
}

void setMouseGrabbed(GLFWwindow* window, bool grabbed)
{
	if (grabbed)
	{
		//glfwSetCursorPos(window, windowWidth / 2.0, windowHeight / 2.0);
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
		glfwGetCursorPos(window, &oldMousePositionX, &oldMousePositionY);
        inputIgnore = 3; // reset input ignore
	}
	else if (isMouseGrabbed(window))
	{
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
	}
}

void glfw_callback_Keyboard(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	switch (action)
	{
		case GLFW_PRESS:
			switch (key)
			{
				case GLFW_KEY_ESCAPE:
				case GLFW_KEY_ENTER:
				case GLFW_KEY_KP_ENTER:
					if (isMouseGrabbed(window))
					{
						setMouseGrabbed(window, false);
						std::cout << "Mouse is released." << std::endl;
					}
					else
					{
						std::cout << "Window will be closed soon..." << std::endl;
						glfwSetWindowShouldClose(window, GLFW_TRUE);
					}
				break;
			}

			break;
	}
}

void glfw_callback_MouseButton(GLFWwindow* window, int button, int action, int mods)
{
	if (action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_LEFT && !isMouseGrabbed(window))
	{
		std::cout << "button callback" << std::endl;

		double mousePositionX;
		double mousePositionY;
		glfwGetCursorPos(window, &mousePositionX, &mousePositionY);
		std::cout << "Left mouse click at [" << mousePositionX << "," << mousePositionY << "]" << std::endl;

		setMouseGrabbed(window, true);
		std::cout << "Mouse is grabbed." << std::endl;
	}
}

void glfw_callback_MouseMove(GLFWwindow* window, double x, double y)
{
	if (isMouseGrabbed(window) && inputIgnore <= 0 )
	{
		int mouseDeltaX = static_cast<int>(x - oldMousePositionX);
		int mouseDeltaY = static_cast<int>(y - oldMousePositionY);
        if( abs( mouseDeltaX ) > 2 || abs( mouseDeltaY ) > 2 )
        {
		    std::cout << "Deltas: " << mouseDeltaX << "," << mouseDeltaY << std::endl;
        }

	}
	oldMousePositionX = x;
	oldMousePositionY = y;
}

int main()
{
	std::cout << "Initializing GLFW...";

	if (glfwInit() == GLFW_FALSE)
	{
		std::cout << "FAILED!" << std::endl;
		return EXIT_FAILURE;
	}
	std::cout << "OK!" << std::endl;

	std::cout << "Creating a window...";

	const char* windowTitle = "C++";
	GLFWwindow* glfwWindow = glfwCreateWindow(windowWidth, windowHeight, windowTitle, NULL, NULL);

	if (!glfwWindow)
	{
		std::cout << "FAILED!" << std::endl;
		glfwTerminate();
		return EXIT_FAILURE;
	}
	std::cout << "OK!" << std::endl;

	std::cout << "Attaching OpenGL context to the window...";
	glfwMakeContextCurrent(glfwWindow);
	std::cout << "DONE!" << std::endl;

	glfwSetKeyCallback(glfwWindow, glfw_callback_Keyboard);
	glfwSetMouseButtonCallback(glfwWindow, glfw_callback_MouseButton);
	glfwSetCursorPosCallback(glfwWindow, glfw_callback_MouseMove);

	while (glfwWindowShouldClose(glfwWindow) == GLFW_FALSE)
	{
		glfwPollEvents();
        inputIgnore = std::max( --inputIgnore, 0 ); // make sure it does not overflow

		//glfwSwapBuffers(glfwWindow);
	}

	glfwMakeContextCurrent(NULL);
	glfwDestroyWindow(glfwWindow);
	glfwTerminate();

	return EXIT_SUCCESS;
}

Ok, i have successfully compiled GLFW, so i have both cursor.exe and events.exe files. What exactly i must to do with it?

Can you explain what exactly you mean?

Also i want to have possibility to use the “mouse keys” (windows feature) to control camera rotation with keyboard. It’s important for me. But it’s impossible when raw mouse motion is enabled. So, it’s unwantable to use the raw mouse motion anyway :man_shrugging:

Why > 2 is used? :thinking:

This was in order to check if you can reproduce the issue with that code.

Can you explain what exactly you mean?

You get to sets of large deltas, one being the same but with the opposite sign:

Deltas: 220,-195
Deltas: -220,195

Why > 2 is used? :thinking:

This was because so many small values were being output I couldn’t see the large deltas, so I set a small limit to reduce the debug output noise.

Also i want to have possibility to use the “mouse keys” (windows feature) to control camera rotation with keyboard.

You could just program key callbacks to mimic the same behaviour if you use raw mouse motion.

Since you don’t want to use raw input, does my workaround of ignoring the first few frames of input work for you?

I can’t do it because these tests does not capturing the cursor when i just clicked to the window :man_shrugging:

I’m not sure, but it seems to me that it works. Thank you anyway!
I am currently trying to find an error in the library code.

There’s a GLFW issue reported which I think covers this:

I think this is due to the cursor position being set to the middle, but then an event being received which resets the cursor to the old position.