Skip to content

fix(rage-input-five): release the cursor clip when the window loses focus - #4092

Open
Github-Samuel wants to merge 1 commit into
citizenfx:masterfrom
Github-Samuel:fix/alt-tab-cursor-clip
Open

fix(rage-input-five): release the cursor clip when the window loses focus#4092
Github-Samuel wants to merge 1 commit into
citizenfx:masterfrom
Github-Samuel:fix/alt-tab-cursor-clip

Conversation

@Github-Samuel

Copy link
Copy Markdown

Goal of this PR

Stop the mouse escaping the game window after alt tabbing back in. This is the FiveM side of #3637, which fixed the same thing for RedM.

How is this PR achieving the goal

ClipCursor is hooked so repeated identical calls don't hit DWM, and ClipHostCursor holds the last rect for that:

static BOOL ClipHostCursor(const RECT* lpRekt)
{
	static RECT lastRect;
	static RECT* lastRectPtr;

	*g_isClippedCursor = lpRekt != nullptr;
	if ((lpRekt && !lastRectPtr) || (lastRectPtr && !lpRekt) || (lpRekt && !EqualRect(&lastRect, lpRekt)))
	{
		...
		return ClipCursor(lpRekt);
	}

	return TRUE;
}

Windows releases the clip itself once the window is no longer in the foreground, and nothing tells the cache. So after an alt tab the OS is not clipping anything while lastRectPtr still points at the old rect. When the game asks for that same rect again on the way back in, all three conditions are false, the call is treated as a duplicate, and the real ClipCursor is never reached. The cursor stays loose for the rest of the session.

rage-input-five already tracks focus for key messages, it just doesn't do anything with it here:

	if (uMsg == WM_ACTIVATEAPP)
	{
		g_isFocused = (wParam) ? true : false;
	}

rage-input-rdr3 clears the cache in the same spot, and its ClipHostCursor is identical to this one, so this is the same change:

	if (uMsg == WM_ACTIVATEAPP)
	{
		g_isFocused = (wParam) ? true : false;
		if (!g_isFocused)
		{
			ClipHostCursor(NULL);
		}
	}

ClipHostCursor(NULL) sets lastRectPtr back to null, so the next request from the game is seen as a change and actually applied.

Only the focus-lost path is touched, and the game re-requests the clip when it wants one, so nothing changes for a session that never loses focus.

This PR applies to the following area(s)

FiveM

Successfully tested on

Game builds: n/a, this does not depend on a game build

Platforms: Windows

Checklist

  • Code compiles and has been tested successfully.
  • Code explains itself well and/or is documented.
  • My commit message explains what the changes do and what they are for.
  • No extra compilation warnings are added by these changes.

I could not do a full client build locally so I left the first box unchecked. What I did check is the cache behaviour, by running ClipHostCursor as it is written against a model of ClipCursor through clip, lose focus, clip again with the same rect. As it stands the second clip does not reach ClipCursor at all and the cursor is left unclipped, with the change it does and the cursor is clipped again. Since I can't alt tab a real client here it would be worth someone confirming on an actual install before this goes in.

…ocus

`ClipHostCursor` keeps the last rect it applied and skips the call when it is
asked for the same one again, to avoid the repeated `ClipCursor` calls that
load DWM.

Windows drops the clip by itself when the window stops being the foreground
one, but that cache does not know about it, so when the game asks for the same
rect again after alt tabbing back the call is treated as a duplicate and never
reaches `ClipCursor`. The cursor is then free to leave the window during
gameplay.

Clear the cache on `WM_ACTIVATEAPP` when focus is lost so the game's own
request goes through on the way back in. `rage-input-rdr3` already does this,
from pr-3637.

Signed-off-by: Samuel Nicol <99494967+Github-Samuel@users.noreply.github.com>
@github-actions github-actions Bot added the invalid Requires changes before it's considered valid and can be (re)triaged label Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid Requires changes before it's considered valid and can be (re)triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant