SetCursorPos() apparent intermittent behavior, solved with a hammer. Is there a better way?

tl;dr
In Windows 10, SetCursorPos() sometimes sends the cursor to the wrong location, so I ran the command 10 (and then 100) times instead of once and the problem is solved. Is there a nicer way to do this?


I recently had a need to leave my embedded development comfort zone and enter into desktop software. I needed a program that would move the mouse cursor when it get’s to the edge of my screen such that:

When x position = 0, move to 3960
When x position > 3970, move to 10

I ended up with this:

#include <iostream>
#include <windows.h>

using namespace std;
int x, y;
POINT xypos;

int main()
{
	while (1) {
		//cout << " Software is running . . .";
		GetCursorPos(&xypos);
		if (xypos.x == 0)
		{
			SetCursorPos(3960, xypos.y);
		}

		if (xypos.x > 3970)
		{
			SetCursorPos(10, xypos.y);
		}
	}
	return 0;

}

The first version worked perfectly, but was using 3% of a 9980xe, which seemed a bit much. I realized that it was polling the mouse position as fast as it possibly could, all the time. So I tried using a timer to slow things down:

#include <iostream>
#include <windows.h>

using namespace std;
int x, y;
POINT xypos;
MSG msg;

void CALLBACK f(HWND, UINT, UINT, DWORD);

int main()
{
	SetTimer(NULL, 0, 1, (TIMERPROC)&f);
	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessageW(&msg);
	}
	return 0;
}

void CALLBACK f(HWND hwnd, UINT uMsg, UINT timerId, DWORD dwTime)
{
	GetCursorPos(&xypos);

	if (xypos.x == 0)
	{
		SetCursorPos(3960, xypos.y);
	}

	if (xypos.x > 3970)
	{
		SetCursorPos(10, xypos.y);
	}

}

Which also “worked”, except that the cursor would intermittently end up at x = ~2560 instead of 3970, which correlates to the right side of the primary monitor. Here, my ‘sometimes doesn’t work’ really felt like a race condition. Attempts at logging revealed that my program was convinced that it successfully moved the cursor to the correct place, which makes me think that the cursor was intermittently being ‘pulled back’ by something else.

I tried orienting the screens above/below each other and changing x/y simultaneously, but to my surprise the behavior persisted. Not in exactly the same way, but the cursor still ended up in the wrong place (at a screen border) sometimes. So I returned the the original (horizontal) orientation because it is simpler.

After spending some time messing with nanosecond delays instead of a millisecond timer (only solved the problem when usage went up to 2-3%) I tried just running SetCursorPos() 10 times, as quickly as possible, instead of once. This worked, so I tried 100 and didn’t notice any additional performance hit.

#include <iostream>
#include <windows.h>

using namespace std;
int x, y;
POINT xypos;
MSG msg;

void CALLBACK f(HWND, UINT, UINT, DWORD);

int main()
{	
	SetTimer(NULL, 0, 10, (TIMERPROC)&f);

	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessageW(&msg);

	}
	return 0;
}

void CALLBACK f(HWND hwnd, UINT uMsg, UINT timerId, DWORD dwTime)
{
	//cout << " Software is running . . .";
	GetCursorPos(&xypos);
	
	if (xypos.x == 0)
	{
		for (int i = 0; i < 100; i++) SetCursorPos(3960, xypos.y);
	}


	if (xypos.x > 3970)
	{
		for (int i = 0; i < 100; i++) SetCursorPos(10, xypos.y);
	}
}

I’ve been successfully running this software since yesterday, and the CPU usage hovers around 0-0.1%, so I’m happy with my solution from a practical perspective. But I feel like I solved this problem with a hammer instead of a scalpel and I don’t really understand what was happening in the first place. I’d love to get some feedback on a more elegant way to solve this problem, or maybe some insight into what the root cause may be.

EDIT: Running my program with a fullscreeen game produces the same behavior, where the cursor jumps to x = ~2560, but it’s consistent. This makes sense because of how I understand that fullscreen games will bind the cursor, and isn’t something I want to change, but is noteworthy.

3 Likes

Changed the software up a bit to report the actual number of calls it took to successfully place the cursor. Looks like it takes up to 6 attempts called 1ms apart.

Without a short delay between set and get, the problem wasn’t detected. So the cursor is being successfully moved and subsequently moved again by another process.

1 Like