Skip to content

Commit fe7f21c

Browse files
committed
OpenVRBackend: Handle new focus events from SteamVR
Overhaul all this so we can have working keyboard/mouse focus logic across the entire SteamVR stack. This also allows us to forward keyboard stuff to arbitrary SteamVR fake overlay windows. In virtual connector modes (eg. VR) we also always have a dummy 0th global focus, for when we are only forwarding to handle those. Some stuff isn't in OpenVR headers yet, so define those.
1 parent b1e883b commit fe7f21c

5 files changed

Lines changed: 334 additions & 112 deletions

File tree

src/Backends/OpenVRBackend.cpp

Lines changed: 164 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,50 @@ gamescope::ConVar<bool> cv_vr_nudge_to_visible_per_connector( "vr_nudge_to_visib
7777
// Maximum interval between polling for VR events (normally paced by frame sync)
7878
gamescope::ConVar<uint32_t> cv_vr_poll_rate( "vr_poll_rate", 50ul, "Max time between input polls. In milliseconds." );
7979

80+
extern std::atomic<uint64_t> g_FocusedVROverlayMouse;
81+
extern std::atomic<uint64_t> g_FocusedVROverlayKeyboard;
82+
8083
// Not in public headers yet.
8184
namespace vr
8285
{
8386
const EVRButtonId k_EButton_Steam = (EVRButtonId)(50);
8487
const EVRButtonId k_EButton_QAM = (EVRButtonId)(51);
88+
89+
constexpr EVREventType VREvent_OverlayInputFocusChanged = ( EVREventType )(564); // data is overlayInputFocus
90+
91+
92+
/** Used in VREvent_OverlayInputFocus_t */
93+
enum EVRInputFocusEventFlags
94+
{
95+
// Indicates the overlay should actively have keyboard focus.
96+
k_EVRInputFocusEventFlags_OverlayShouldShowAffordanceForKeyboardInput = 1 << 0,
97+
// Indicates the overlay should actively have gamepad focus.
98+
k_EVRInputFocusEventFlags_OverlayShouldShowAffordanceForGamepadInput = 1 << 1,
99+
};
100+
/** Used for a few events about overlay input focus */
101+
struct VREvent_OverlayInputFocus_t
102+
{
103+
// Overlay that has keyboard and/or gamepad focus. If this is your overlay and its contents is a desktop or a desktop window,
104+
// this event is a great oppportunity to move that window to the foreground so it receives input.
105+
uint64_t overlayHandle; // VROverlayHandle_t
106+
uint32_t flags; // EVRInputFocusEventFlags
107+
};
108+
109+
typedef union
110+
{
111+
VREvent_OverlayInputFocus_t overlayInputFocus;
112+
} VREvent_Data_Gamescope_t;
113+
114+
static inline const VREvent_Data_Gamescope_t &CastToGamescopeEventData( const VREvent_Data_t &eventData )
115+
{
116+
return reinterpret_cast< const vr::VREvent_Data_Gamescope_t & >( eventData );
117+
}
118+
static inline VREvent_Data_Gamescope_t &CastToGamescopeEventData( VREvent_Data_t &eventData )
119+
{
120+
return reinterpret_cast< vr::VREvent_Data_Gamescope_t & >( eventData );
121+
}
85122
}
123+
//
86124

87125
extern std::atomic<uint32_t> g_unCurrentVRSceneAppId;
88126

@@ -740,8 +778,12 @@ namespace gamescope
740778

741779
virtual IBackendConnector *GetCurrentConnector() override
742780
{
743-
return m_pFocusConnector;
781+
return m_pKeyboardFocusConnector;
744782
}
783+
virtual IBackendConnector *GetCurrentMouseConnector()
784+
{
785+
return m_pMouseFocusConnector;
786+
}
745787
virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override
746788
{
747789
if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL )
@@ -832,20 +874,23 @@ namespace gamescope
832874
return TouchClickModes::Trackpad;
833875
}
834876

835-
if ( pConnector->IsTouchForbidden() )
877+
if ( pConnector )
836878
{
837-
return TouchClickModes::Left;
838-
}
879+
if ( pConnector->IsTouchForbidden() )
880+
{
881+
return TouchClickModes::Left;
882+
}
839883

840-
if ( VirtualConnectorKeyIsNonSteamWindow( pConnector->GetVirtualConnectorKey() ) )
841-
{
842-
return TouchClickModes::Passthrough;
843-
}
884+
if ( VirtualConnectorKeyIsNonSteamWindow( pConnector->GetVirtualConnectorKey() ) )
885+
{
886+
return TouchClickModes::Passthrough;
887+
}
844888

845-
if ( VirtualConnectorInSteamPerAppState() )
846-
{
847-
if ( !VirtualConnectorKeyIsSteam( pConnector->GetVirtualConnectorKey() ) )
848-
return TouchClickModes::Left;
889+
if ( VirtualConnectorInSteamPerAppState() )
890+
{
891+
if ( !VirtualConnectorKeyIsSteam( pConnector->GetVirtualConnectorKey() ) )
892+
return TouchClickModes::Left;
893+
}
849894
}
850895

851896
return CBaseBackend::GetTouchClickMode();
@@ -859,22 +904,23 @@ namespace gamescope
859904
{
860905
std::shared_ptr<COpenVRConnector> pConnector = std::make_shared<COpenVRConnector>( this, ulVirtualConnectorKey );
861906

862-
bool bSetCurrentConnector = false;
907+
if ( !pConnector->Init() )
863908
{
864-
if ( !m_pFocusConnector )
865-
{
866-
SetFocus( pConnector.get() );
867-
bSetCurrentConnector = true;
868-
}
909+
return nullptr;
910+
}
911+
912+
{
913+
COpenVRConnector *pExpected = nullptr;
914+
m_pMouseFocusConnector.compare_exchange_strong( pExpected, pConnector.get() );
869915
}
870916

871-
if ( !pConnector->Init() )
872917
{
873-
if ( bSetCurrentConnector )
918+
COpenVRConnector *pExpected = nullptr;
919+
if ( m_pKeyboardFocusConnector.compare_exchange_strong( pExpected, pConnector.get() ) )
874920
{
875-
SetFocus( nullptr );
921+
openvr_log.debugf( "Changed keyboard focus connector to %p ", pConnector.get() );
922+
update_connector_display_info_wl( NULL );
876923
}
877-
return nullptr;
878924
}
879925

880926
std::scoped_lock lock{ m_mutActiveConnectors };
@@ -998,16 +1044,6 @@ namespace gamescope
9981044
m_nOverlaysVisible.wait( 0 );
9991045
}
10001046

1001-
void SetFocus( COpenVRConnector *pFocus )
1002-
{
1003-
COpenVRConnector *pPreviousFocus = m_pFocusConnector.exchange( pFocus );
1004-
if ( pPreviousFocus != pFocus )
1005-
{
1006-
MakeFocusDirty();
1007-
update_connector_display_info_wl( NULL );
1008-
}
1009-
}
1010-
10111047
COpenVRPlane *GetPlaneByOverlayHandle( vr::VROverlayHandle_t hOverlay )
10121048
{
10131049
COpenVRPlane *pPlane = nullptr;
@@ -1020,6 +1056,75 @@ namespace gamescope
10201056
return pPlane;
10211057
}
10221058

1059+
void SetMouseFocus( uint64_t ulFocusOverlay )
1060+
{
1061+
uint64_t oldOverlayHandle = g_FocusedVROverlayMouse.exchange( ulFocusOverlay );
1062+
1063+
if ( oldOverlayHandle == ulFocusOverlay )
1064+
return;
1065+
1066+
openvr_log.debugf( "Changing mouse focus from %lx to %lx", oldOverlayHandle, ulFocusOverlay );
1067+
1068+
COpenVRConnector *pInputConnector = nullptr;
1069+
if ( ulFocusOverlay != vr::k_ulOverlayHandleInvalid )
1070+
{
1071+
COpenVRPlane *pInputPlane = GetPlaneByOverlayHandle( ulFocusOverlay );
1072+
if ( pInputPlane )
1073+
{
1074+
pInputConnector = pInputPlane->GetConnector();
1075+
}
1076+
}
1077+
1078+
if ( pInputConnector )
1079+
{
1080+
pInputConnector->m_bUsingVRMouse = true;
1081+
1082+
COpenVRConnector *pOldConnector = m_pMouseFocusConnector.exchange( pInputConnector );
1083+
1084+
if ( pOldConnector != pInputConnector )
1085+
{
1086+
openvr_log.debugf( "Changing mouse focus connector to %p", pInputConnector );
1087+
1088+
// We don't do anything with mouse focus that isn't local,
1089+
// so only dirty focus if the focus connector changed.
1090+
//
1091+
// Unlike with Keyboard where we need to focus for forwarders too!
1092+
1093+
MakeFocusDirty();
1094+
nudge_steamcompmgr();
1095+
}
1096+
}
1097+
}
1098+
1099+
void SetKeyboardFocus( uint64_t ulFocusOverlay )
1100+
{
1101+
uint64_t oldOverlayHandle = g_FocusedVROverlayKeyboard.exchange( ulFocusOverlay );
1102+
1103+
if ( oldOverlayHandle == ulFocusOverlay )
1104+
return;
1105+
1106+
openvr_log.debugf( "Changing keyboard focus from %lx to %lx", oldOverlayHandle, ulFocusOverlay );
1107+
1108+
COpenVRConnector *pInputConnector = nullptr;
1109+
if ( ulFocusOverlay != vr::k_ulOverlayHandleInvalid )
1110+
{
1111+
COpenVRPlane *pInputPlane = GetPlaneByOverlayHandle( ulFocusOverlay );
1112+
if ( pInputPlane )
1113+
{
1114+
pInputConnector = pInputPlane->GetConnector();
1115+
}
1116+
}
1117+
1118+
if ( pInputConnector )
1119+
{
1120+
openvr_log.debugf( "Changing keyboard focus connector to %p", pInputConnector );
1121+
m_pKeyboardFocusConnector.exchange( pInputConnector );
1122+
}
1123+
1124+
MakeFocusDirty();
1125+
nudge_steamcompmgr();
1126+
}
1127+
10231128
void ProcessVRInput()
10241129
{
10251130
std::scoped_lock lock{m_mutActiveConnectors};
@@ -1161,7 +1266,8 @@ namespace gamescope
11611266
{
11621267
if (pConnector && pConnector->m_bUsingVRMouse)
11631268
{
1164-
SetFocus(pConnector);
1269+
SetMouseFocus( hOverlay );
1270+
11651271
float flX = vrEvent.data.mouse.x / float(g_nOutputWidth);
11661272
float flY = (g_nOutputHeight - vrEvent.data.mouse.y) / float(g_nOutputHeight);
11671273

@@ -1197,17 +1303,31 @@ namespace gamescope
11971303
break;
11981304
}
11991305
case vr::VREvent_FocusEnter:
1200-
if (pConnector)
12011306
{
1202-
pConnector->m_bUsingVRMouse = true;
1203-
SetFocus(pConnector);
1307+
if (pConnector)
1308+
{
1309+
pConnector->m_bUsingVRMouse = true;
1310+
SetMouseFocus( hOverlay );
1311+
}
1312+
}
1313+
break;
1314+
1315+
case vr::VREvent_OverlayFocusChanged:
1316+
{
1317+
SetMouseFocus( vrEvent.data.overlay.overlayHandle );
1318+
}
1319+
break;
1320+
case vr::VREvent_OverlayInputFocusChanged:
1321+
{
1322+
const vr::VREvent_Data_Gamescope_t &data = CastToGamescopeEventData( vrEvent.data );
1323+
SetKeyboardFocus( data.overlayInputFocus.overlayHandle );
12041324
}
12051325
break;
12061326
case vr::VREvent_MouseButtonUp:
12071327
case vr::VREvent_MouseButtonDown:
12081328
if (pConnector)
12091329
{
1210-
SetFocus(pConnector);
1330+
SetMouseFocus( hOverlay );
12111331

12121332
if (!pConnector->m_bUsingVRMouse)
12131333
{
@@ -1289,7 +1409,8 @@ namespace gamescope
12891409
case vr::VREvent_ScrollSmooth:
12901410
if (pConnector)
12911411
{
1292-
SetFocus(pConnector);
1412+
SetMouseFocus( hOverlay );
1413+
12931414
float flX = -vrEvent.data.scroll.xdelta * m_flScrollSpeed;
12941415
float flY = -vrEvent.data.scroll.ydelta * m_flScrollSpeed;
12951416
wlserver_lock();
@@ -1299,24 +1420,6 @@ namespace gamescope
12991420
}
13001421
break;
13011422

1302-
case vr::VREvent_ButtonPress:
1303-
if (pConnector)
1304-
{
1305-
SetFocus(pConnector);
1306-
vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button;
1307-
1308-
if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM)
1309-
break;
1310-
1311-
if (button == vr::k_EButton_Steam)
1312-
openvr_log.infof("STEAM button pressed.");
1313-
else
1314-
openvr_log.infof("QAM button pressed.");
1315-
1316-
wlserver_open_steam_menu(button == vr::k_EButton_QAM);
1317-
}
1318-
break;
1319-
13201423
case vr::VREvent_OverlayShown:
13211424
case vr::VREvent_OverlayHidden:
13221425
if (pConnector)
@@ -1419,7 +1522,8 @@ namespace gamescope
14191522
friend COpenVRConnector;
14201523
std::vector<COpenVRConnector*> m_pActiveConnectors;
14211524
std::mutex m_mutActiveConnectors;
1422-
std::atomic<COpenVRConnector *> m_pFocusConnector;
1525+
std::atomic<COpenVRConnector *> m_pMouseFocusConnector;
1526+
std::atomic<COpenVRConnector *> m_pKeyboardFocusConnector;
14231527

14241528
std::atomic<bool> m_bInitted = { false };
14251529
std::atomic<bool> m_bRunning = { false };
@@ -1462,9 +1566,10 @@ namespace gamescope
14621566
m_pBackend->m_pActiveConnectors.erase( iter );
14631567

14641568
COpenVRConnector *pThis = this;
1465-
m_pBackend->m_pFocusConnector.compare_exchange_strong( pThis, nullptr );
1569+
m_pBackend->m_pMouseFocusConnector.compare_exchange_strong( pThis, nullptr );
1570+
pThis = this;
1571+
m_pBackend->m_pKeyboardFocusConnector.compare_exchange_strong( pThis, nullptr );
14661572
}
1467-
14681573
GamescopeScreenType COpenVRConnector::GetScreenType() const
14691574
{
14701575
return GAMESCOPE_SCREEN_TYPE_INTERNAL;

src/backend.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ namespace gamescope
349349
}
350350

351351
virtual IBackendConnector *GetCurrentConnector() = 0;
352+
virtual IBackendConnector *GetCurrentMouseConnector()
353+
{
354+
return this->GetCurrentConnector();
355+
}
352356
virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) = 0;
353357

354358
virtual bool SupportsPlaneHardwareCursor() const = 0;

0 commit comments

Comments
 (0)