Download

[4.8 - Bug] GameViewportClient ReceivedFocus and LostFocus no longer called in standalone windowed a

Heya guys,
Sorry for posting this in two places, but I’m getting no traction on the answerhub.

After upgrading to 4.8, we are no longer getting ReceivedFocus and LostFocus being called in our GameViewportClient. I tested in ContentExamples on the release branch of vanilla unreal and it appears to be a problem with Unreal.

Repro steps:

Set Breakpoints in ReceivedFocus and LostFocus in GameViewportClient.cpp

Launch as Standalone Game

Alt-tab into/out of the window

No breakpoints hit

It seems to work as intended when I run in single process and tab between unreal windows.

I think we don’t get ReceivedFocus events is because in SlateApplication.cpp in SetUserFocus we hit this line:


 if (WidgetToFocus.Widget == OldFocusedWidget)

But when we go to another process/switch back from another process the OldFocusedWidget is the last widget we focused inside our process instead of being set to some invalid value when we leave the window entirely. I’m guessing that’s just because we never fire any sort of LostFocus/SetUserFocus stuff when the window is deactivated.

So after more investigation, it looks like somebody on our side modified SWindow.cpp at some point so it didn’t match up with UE even before 4.8. I have a work around that gets LostFocus and ReceivedFocus functions firing in the GameViewportClient, but I’m a little worried about the implications of doing this, and wanted to know if anybody could tell me how bad an idea this is.

In SWindow.cpp OnIsActiveChanged I added another call to SetKeyboardFocus to the deactivated section, but I’m a little worried about what it might do when it is called on windows that I don’t want it to be called on.



bool SWindow::OnIsActiveChanged( const FWindowActivateEvent& ActivateEvent )
{
	const bool bWasDeactivated = ( ActivateEvent.GetActivationType() == FWindowActivateEvent::EA_Deactivate );
	if ( bWasDeactivated )
	{
		OnWindowDeactivated.ExecuteIfBound();

		const EWindowMode::Type WindowMode = GetWindowMode();
		// If the window is not fullscreen, we do not want to automatically recapture the mouse unless an external UI such as Steam is open. Fullscreen windows we do.
		if( WindowMode != EWindowMode::Fullscreen && WidgetToFocusOnActivate.IsValid() && WidgetToFocusOnActivate.Pin()->HasMouseCapture() && !FSlateApplicationBase::Get().IsExternalUIOpened())
		{
			//For a windowed application with an OS border, if the user is giving focus back to the application by clicking on the close/(X) button, then we must clear 
			//the weak pointer to WidgetToFocus--so that the application's main viewport does not steal focus immediately (thus canceling the close attempt).
			
			//This change introduces a different bug where slate context is lost when closing popup menus.  However, this issue is negated by a 
			//change to FMenuStack::PushMenu, where we ReleaseMouseCapture when immediately shifting focus.
			WidgetToFocusOnActivate.Reset();
		}

/////////////////////////////////// NEW STUFF //////////////////////////////////////////////////////////////////////////////////////////
		if (SupportsKeyboardFocus())
		{
			FSlateApplicationBase::Get().SetKeyboardFocus(FWidgetPath(), EFocusCause::OtherWidgetLostFocus);
		}
/////////////////////////////////// END NEW STUFF ///////////////////////////////////////////////////////////////////////////////////
	}
	else
	{
		if (SupportsKeyboardFocus())
		{
			TArray< TSharedRef<SWindow> > JustThisWindow;
			JustThisWindow.Add( SharedThis(this) );
			
			// If we're becoming active and we were set to restore keyboard focus to a specific widget
			// after reactivating, then do so now
			TSharedPtr< SWidget > PinnedWidgetToFocus( WidgetToFocusOnActivate.Pin() );
			
			if (PinnedWidgetToFocus.IsValid())
			{
				FWidgetPath WidgetToFocusPath;
				if( FSlateWindowHelper::FindPathToWidget( JustThisWindow, PinnedWidgetToFocus.ToSharedRef(), WidgetToFocusPath ) )
				{
					FSlateApplicationBase::Get().SetKeyboardFocus( WidgetToFocusPath, EFocusCause::SetDirectly );
				}
			}
			else
			{
				FWidgetPath WindowWidgetPath;
				if( FSlateWindowHelper::FindPathToWidget( JustThisWindow, AsShared(), WindowWidgetPath ) )
				{
					FWeakWidgetPath WeakWindowPath(WindowWidgetPath);
					FSlateApplicationBase::Get().SetKeyboardFocus( WeakWindowPath.ToNextFocusedPath(EUINavigation::Next), EFocusCause::SetDirectly );
				}
			}
		}

		OnWindowActivated.ExecuteIfBound();
	}

	return true;
}


This seems to work ok, but I’m a little worried of potential knockons.

I did notice we have two delegates in SWindow for OnWindowActivated and OnWindowDeactivated, but they don’t look like multicast delegates and I’m worried that binding to them might break other things or my binding might get lost at some point, and I’m not entirely sure how to get my game’s base window to get focus events from.

It’s very possible that I’m doing something totally incorrectly, so if anybody has a better way to get notified when my game gains/loses focus to handle muting game audio in the background or auto pausing the game when you alt tab that would be awesome.