UE5.6 I discovered a malicious bug regarding the UI input method change (about OnInputMethodChanged event)

About you used OnInputMethodChanged to real-time change UI input change. You will find in this 5.6 upgrade that when you use the gamepad to input, the input mode will switch wildly between the mouse and the gamepad, and then your controller will completely fail.

This problem is mainly caused by the following code:

SlateApplication.cpp

bool FSlateApplication::OnMouseMove()
{
	if (!PlatformApplication->Cursor.IsValid())
    {
    	return false;
    }

    // If the left button is pressed we fake 
	if (IsFakingTouchEvents() && (GetPressedMouseButtons().Num() == 0 || GetPressedMouseButtons().Contains(EKeys::LeftMouseButton)))
	{
		// convert to touch event if we are faking it
		if (bIsFakingTouched)
		{
			return OnTouchMoved(PlatformApplication->Cursor->GetPosition(), 1.0f, 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice());
		}

		// Throw out the mouse move event if we're faking touch events but the mouse button isn't down.
		return false;
	}

	bool Result = true;
	const FVector2f CurrentCursorPosition = UE::Slate::CastToVector2f(PlatformApplication->Cursor->GetPosition());
	if (LastPlatformCursorPosition != CurrentCursorPosition)
	{
		// Force the cursor user index to use the platform cursor since we've been notified that the platform 
		// cursor position has changed. This is done intentionally after getting the positions in order to avoid
		// false positives.

		// NOTE: When we swap out the real OS cursor for the faux slate cursor ie. UsePlatformCursorForCursorUser(false)
		// we reset this event count to 0.  This occurs typically when a gamepad is being used and you don't want to manipulate the real
		// OS cursor, instead move around a fake cursor so that you can still do development stuff outside the game window.  Anyway,
		// when this occurs, in the future the OS will send you a long delayed mouse movement.  I'm not exactly sure what's triggering it,
		// it's not a movement from the application, I think it's something more subtle, like swapping true cursor visibility for using
		// a None cursor, it could also be some combination.
		// 
		// In any event, we track the number of events, and if we get more than 3, then we start trying to swap back to the OS cursor.
		// the 3 should give us any buffer needed for either a last frame mouse movement, or a weird condition like noted above.
		PlatformMouseMovementEvents++;

		if (PlatformMouseMovementEvents > 3)
		{
			UsePlatformCursorForCursorUser(true);
		}

		LastMouseMoveTime = GetCurrentTime();

		FPointerEvent MouseEvent(
			GetUserIndexForMouse(),
			CursorPointerIndex,
			CurrentCursorPosition,
			LastPlatformCursorPosition,
			PressedMouseButtons,
			EKeys::Invalid,
			0,
			PlatformApplication->GetModifierKeys()
			);

		Result = ProcessMouseMoveEvent( MouseEvent );
		LastPlatformCursorPosition = CurrentCursorPosition;
	}

	return Result;
}

The logic written above is so foolish. This code is so foolish that I am too lazy to look for its logical bugs. I choose to revert it back to version 5.5.3.

To prevent anyone from not downloading version 5.5.3, I have attached the following correct code:

bool FSlateApplication::OnMouseMove()
{
	// If the left button is pressed we fake 
	if (IsFakingTouchEvents() && (GetPressedMouseButtons().Num() == 0 || GetPressedMouseButtons().Contains(EKeys::LeftMouseButton)))
	{
		// convert to touch event if we are faking it
		if (bIsFakingTouched)
		{
			return OnTouchMoved(PlatformApplication->Cursor->GetPosition(), 1.0f, 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice());
		}

		// Throw out the mouse move event if we're faking touch events but the mouse button isn't down.
		return false;
	}

	bool Result = true;
	const FVector2f CurrentCursorPosition = GetCursorPos();
	const FVector2f LastCursorPosition = GetLastCursorPos();

	// Force the cursor user index to use the platform cursor since we've been notified that the platform 
	// cursor position has changed. This is done intentionally after getting the positions in order to avoid
	// false positives.

	// NOTE: When we swap out the real OS cursor for the faux slate cursor ie. UsePlatformCursorForCursorUser(false)
	// we reset this event count to 0.  This occurs typically when a gamepad is being used and you don't want to manipulate the real
	// OS cursor, instead move around a fake cursor so that you can still do development stuff outside the game window.  Anyway,
	// when this occurs, in the future the OS will send you a long delayed mouse movement.  I'm not exactly sure what's triggering it,
	// it's not a movement from the application, I think it's something more subtle, like swapping true cursor visibility for using
	// a None cursor, it could also be some combination.
	// 
	// In any event, we track the number of events, and if we get more than 3, then we start trying to swap back to the OS cursor.
	// the 3 should give us any buffer needed for either a last frame mouse movement, or a weird condition like noted above.
	PlatformMouseMovementEvents++;

	if (PlatformMouseMovementEvents > 3)
	{
		UsePlatformCursorForCursorUser(true);
	}

	if (LastCursorPosition != CurrentCursorPosition)
	{
		LastMouseMoveTime = GetCurrentTime();

		FPointerEvent MouseEvent(
			GetUserIndexForMouse(),
			CursorPointerIndex,
			CurrentCursorPosition,
			LastCursorPosition,
			PressedMouseButtons,
			EKeys::Invalid,
			0,
			PlatformApplication->GetModifierKeys()
		);

		Result = ProcessMouseMoveEvent(MouseEvent);
	}

	return Result;
}

I am very skeptical about how the official wrote this code. it is obvious that it will cause serious input mode change problems.I spent two days troubleshooting the code I wrote. Finally, I discovered this error issue in the official update. I’m a little angry. I hope Unreal Engine can update more rigorously next time.

We also reverted to the previous version of the OnMouseMove function. It fixed 99% of the issue, but we still have some random jumps and switches to Mouse and Keyboard once in a while that I’m not sure why they would now happen…

1 Like

Indeed, this OnMouseMove update has caused many bugs.Looking forward to an official explanation and continuous updates.

So after investigating a little bit more, it seems like their logic to get the current and last cursor position is the culprit here. Before it would get them from the slate user, but now it’s using an internal value for the last position, but all the UI code updates the cursor position via SlateUser→SetCursorPosition that would update the current and last value correctly.

Mainly the analog cursor does a lot of this SetCursorPosition even if you are not using it. If you are not using the Analog Cursor at all in your game you can probably fix most of these issues by changing the bLinkCursorToGamepadFocus value in your CommonUI Input settings in the project settings.

For our case though we are using the analog cursor so we need to find a way to fix it properly. Reverting the SlateApplication→OnMouseMove() function does fix 95% of the issues, but there is still small glitches once in a while which I think is what they were trying to fix where the cursor jumps because some Windows events was triggered.

1 Like