diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp
index 718a2604f3..569bfd06eb 100644
--- a/layer/VkLayer_FROG_gamescope_wsi.cpp
+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp
@@ -394,6 +394,7 @@ namespace GamescopeWSILayer {
struct GamescopeInstanceData {
wl_display* display;
uint32_t appId = 0;
+ std::string engineName;
GamescopeLayerClient::Flags flags = 0;
};
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE(GamescopeInstance, VkInstance);
@@ -616,9 +617,14 @@ namespace GamescopeWSILayer {
{
uint32_t appId = clientAppId();
+ std::string engineName;
+ if (pCreateInfo->pApplicationInfo && pCreateInfo->pApplicationInfo->pEngineName)
+ engineName = pCreateInfo->pApplicationInfo->pEngineName;
+
auto state = GamescopeInstance::create(*pInstance, GamescopeInstanceData {
.display = display,
.appId = appId,
+ .engineName = engineName,
.flags = defaultLayerClientFlags(pCreateInfo->pApplicationInfo, appId),
});
@@ -1251,7 +1257,8 @@ namespace GamescopeWSILayer {
uint32_t(pCreateInfo->imageColorSpace),
uint32_t(pCreateInfo->compositeAlpha),
uint32_t(pCreateInfo->preTransform),
- uint32_t(pCreateInfo->clipped));
+ uint32_t(pCreateInfo->clipped),
+ gamescopeInstance->engineName.c_str());
return VK_SUCCESS;
}
diff --git a/protocol/gamescope-swapchain.xml b/protocol/gamescope-swapchain.xml
index 91be3fc02d..58ac8463b7 100644
--- a/protocol/gamescope-swapchain.xml
+++ b/protocol/gamescope-swapchain.xml
@@ -89,6 +89,7 @@
+
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 0b121e8416..9e2e27bba8 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -72,8 +72,96 @@ gamescope::ConVar cv_drm_debug_disable_color_range( "drm_debug_disable_col
gamescope::ConVar cv_drm_debug_disable_explicit_sync( "drm_debug_disable_explicit_sync", false, "Force disable explicit sync on the DRM backend." );
gamescope::ConVar cv_drm_debug_disable_in_fence_fd( "drm_debug_disable_in_fence_fd", false, "Force disable IN_FENCE_FD being set to avoid over-synchronization on the DRM backend." );
+
+int HackyDRMPresent( const FrameInfo_t *pFrameInfo, bool bAsync );
+
+struct saved_mode {
+ int width;
+ int height;
+ int refresh;
+};
+
namespace gamescope
{
+ class CDRMPlane;
+ class CDRMCRTC;
+ class CDRMConnector;
+}
+
+struct drm_t {
+ bool bUseLiftoff;
+
+ int fd = -1;
+
+ int preferred_width, preferred_height, preferred_refresh;
+
+ uint64_t cursor_width, cursor_height;
+ bool allow_modifiers;
+ struct wlr_drm_format_set formats;
+
+ std::vector< std::unique_ptr< gamescope::CDRMPlane > > planes;
+ std::vector< std::unique_ptr< gamescope::CDRMCRTC > > crtcs;
+ std::unordered_map< uint32_t, gamescope::CDRMConnector > connectors;
+
+ gamescope::CDRMPlane *pPrimaryPlane;
+ gamescope::CDRMCRTC *pCRTC;
+ gamescope::CDRMConnector *pConnector;
+
+ struct wlr_drm_format_set primary_formats;
+
+ drmModeAtomicReq *req;
+ uint32_t flags;
+
+ struct liftoff_device *lo_device;
+ struct liftoff_output *lo_output;
+ struct liftoff_layer *lo_layers[ k_nMaxLayers ];
+
+ std::shared_ptr sdr_static_metadata;
+
+ struct drm_state_t {
+ std::shared_ptr mode_id;
+ uint32_t color_mgmt_serial;
+ std::shared_ptr lut3d_id[ EOTF_Count ];
+ std::shared_ptr shaperlut_id[ EOTF_Count ];
+ amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
+ } current, pending;
+
+ // FBs in the atomic request, but not yet submitted to KMS
+ // Accessed only on req thread
+ std::vector> m_FbIdsInRequest;
+
+ // FBs currently queued to go on screen.
+ // May be accessed by page flip handler thread and req thread, thus mutex.
+ std::mutex m_QueuedFbIdsMutex;
+ std::vector> m_QueuedFbIds;
+ // FBs currently on screen.
+ // Accessed only on page flip handler thread.
+ std::mutex m_mutVisibleFbIds;
+ std::vector> m_VisibleFbIds;
+
+ std::atomic < uint32_t > uPendingFlipCount = { 0 };
+
+ std::atomic < bool > paused = { false };
+ std::atomic < int > out_of_date = { false };
+ std::atomic < bool > needs_modeset = { false };
+
+ std::unordered_map< std::string, int > connector_priorities;
+
+ char *device_name = nullptr;
+};
+
+void drm_drop_fbid( struct drm_t *drm, uint32_t fbid );
+bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode );
+
+
+using namespace std::literals;
+
+struct drm_t g_DRM = {};
+
+namespace gamescope
+{
+ class CDRMBackend;
+
std::tuple GetKernelVersion()
{
utsname name;
@@ -259,10 +347,10 @@ namespace gamescope
CRTCProperties m_Props;
};
- class CDRMConnector final : public IBackendConnector, public CDRMAtomicTypedObject
+ class CDRMConnector final : public CBaseBackendConnector, public CDRMAtomicTypedObject
{
public:
- CDRMConnector( drmModeConnector *pConnector );
+ CDRMConnector( CDRMBackend *pBackend, drmModeConnector *pConnector );
void RefreshState();
@@ -343,6 +431,14 @@ namespace gamescope
const BackendConnectorHDRInfo &GetHDRInfo() const override { return m_Mutable.HDR; }
+ virtual bool IsVRRActive() const override
+ {
+ if ( !g_DRM.pCRTC || !g_DRM.pCRTC->GetProperties().VRR_ENABLED )
+ return false;
+
+ return !!g_DRM.pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue();
+ }
+
virtual std::span GetModes() const override { return m_Mutable.BackendModes; }
bool SupportsVRR() const override
@@ -371,17 +467,20 @@ namespace gamescope
}
}
- void UpdateEffectiveOrientation( const drmModeModeInfo *pMode );
+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override;
using DRMModeGenerator = std::function;
const DRMModeGenerator &GetModeGenerator() const
{
return m_Mutable.fnDynamicModeGenerator;
}
+ void UpdateEffectiveOrientation( const drmModeModeInfo *pMode );
private:
void ParseEDID();
+
+ CDRMBackend *m_pBackend = nullptr;
CAutoDeletePtr m_pConnector;
struct MutableConnectorState
@@ -393,8 +492,8 @@ namespace gamescope
char szMakePNP[4]{};
char szModel[16]{};
const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP.
- std::vector ValidDynamicRefreshRates{};
DRMModeGenerator fnDynamicModeGenerator;
+ std::vector ValidDynamicRefreshRates{};
std::vector EdidData; // Raw, unmodified.
std::vector BackendModes;
@@ -420,82 +519,6 @@ namespace gamescope
};
}
-struct saved_mode {
- int width;
- int height;
- int refresh;
-};
-
-struct drm_t {
- bool bUseLiftoff;
-
- int fd = -1;
-
- int preferred_width, preferred_height, preferred_refresh;
-
- uint64_t cursor_width, cursor_height;
- bool allow_modifiers;
- struct wlr_drm_format_set formats;
-
- std::vector< std::unique_ptr< gamescope::CDRMPlane > > planes;
- std::vector< std::unique_ptr< gamescope::CDRMCRTC > > crtcs;
- std::unordered_map< uint32_t, gamescope::CDRMConnector > connectors;
-
- gamescope::CDRMPlane *pPrimaryPlane;
- gamescope::CDRMCRTC *pCRTC;
- gamescope::CDRMConnector *pConnector;
-
- struct wlr_drm_format_set primary_formats;
-
- drmModeAtomicReq *req;
- uint32_t flags;
-
- struct liftoff_device *lo_device;
- struct liftoff_output *lo_output;
- struct liftoff_layer *lo_layers[ k_nMaxLayers ];
-
- std::shared_ptr sdr_static_metadata;
-
- struct drm_state_t {
- std::shared_ptr mode_id;
- uint32_t color_mgmt_serial;
- std::shared_ptr lut3d_id[ EOTF_Count ];
- std::shared_ptr shaperlut_id[ EOTF_Count ];
- amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
- } current, pending;
-
- // FBs in the atomic request, but not yet submitted to KMS
- // Accessed only on req thread
- std::vector> m_FbIdsInRequest;
-
- // FBs currently queued to go on screen.
- // May be accessed by page flip handler thread and req thread, thus mutex.
- std::mutex m_QueuedFbIdsMutex;
- std::vector> m_QueuedFbIds;
- // FBs currently on screen.
- // Accessed only on page flip handler thread.
- std::mutex m_mutVisibleFbIds;
- std::vector> m_VisibleFbIds;
-
- std::atomic < uint32_t > uPendingFlipCount = { 0 };
-
- std::atomic < bool > paused = { false };
- std::atomic < int > out_of_date = { false };
- std::atomic < bool > needs_modeset = { false };
-
- std::unordered_map< std::string, int > connector_priorities;
-
- char *device_name = nullptr;
-};
-
-void drm_drop_fbid( struct drm_t *drm, uint32_t fbid );
-bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode );
-
-
-using namespace std::literals;
-
-struct drm_t g_DRM = {};
-
uint32_t g_nDRMFormat = DRM_FORMAT_INVALID;
uint32_t g_nDRMFormatOverlay = DRM_FORMAT_INVALID; // for partial composition, we may have more limited formats than base planes + alpha.
bool g_bRotated = false;
@@ -560,7 +583,7 @@ static constexpr uint32_t s_kSteamDeckOLEDRates[] =
90,
};
-static void update_connector_display_info_wl(struct drm_t *drm)
+void update_connector_display_info_wl(struct drm_t *drm)
{
wlserver_lock();
for ( const auto &control : wlserver.gamescope_controls )
@@ -682,7 +705,7 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi
DRMPresentCtx *pCtx = reinterpret_cast( data );
// Make this const when we move into CDRMBackend.
- GetBackend()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount;
+ GetBackend()->GetCurrentConnector()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount;
if ( !g_DRM.pCRTC )
return;
@@ -768,7 +791,7 @@ static bool refresh_state( drm_t *drm )
drm->connectors.emplace(
std::piecewise_construct,
std::forward_as_tuple( uConnectorId ),
- std::forward_as_tuple( pConnector ) );
+ std::forward_as_tuple( reinterpret_cast( GetBackend() ), pConnector ) );
}
}
@@ -1963,8 +1986,9 @@ namespace gamescope
/////////////////////////
// CDRMConnector
/////////////////////////
- CDRMConnector::CDRMConnector( drmModeConnector *pConnector )
+ CDRMConnector::CDRMConnector( CDRMBackend *pBackend, drmModeConnector *pConnector )
: CDRMAtomicTypedObject( pConnector->connector_id )
+ , m_pBackend{ pBackend }
, m_pConnector{ pConnector, []( drmModeConnector *pConnector ){ drmModeFreeConnector( pConnector ); } }
{
RefreshState();
@@ -2043,6 +2067,11 @@ namespace gamescope
ParseEDID();
}
+ int CDRMConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync )
+ {
+ return HackyDRMPresent( pFrameInfo, bAsync );
+ }
+
void CDRMConnector::UpdateEffectiveOrientation( const drmModeModeInfo *pMode )
{
if ( this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL && g_DesiredInternalOrientation != GAMESCOPE_PANEL_ORIENTATION_AUTO )
@@ -3230,8 +3259,13 @@ namespace gamescope
return true;
}
- virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override
+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync )
{
+ static uint64_t s_ulLastTime = get_time_in_nanos();
+ uint64_t ulNow = get_time_in_nanos();
+ drm_log.debugf( "CDRMBackend::Present Begin: %lu -> delta: %lu", ulNow, ulNow - s_ulLastTime );
+ s_ulLastTime = ulNow;
+
bool bWantsPartialComposite = pFrameInfo->layerCount >= 3 && !kDisablePartialComposition;
static bool s_bWasFirstFrame = true;
@@ -3584,14 +3618,6 @@ namespace gamescope
return nullptr;
}
- virtual bool IsVRRActive() const override
- {
- if ( !g_DRM.pCRTC || !g_DRM.pCRTC->GetProperties().VRR_ENABLED )
- return false;
-
- return !!g_DRM.pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue();
- }
-
virtual bool SupportsPlaneHardwareCursor() const override
{
return true;
@@ -3702,14 +3728,14 @@ namespace gamescope
drm->m_QueuedFbIds.swap( drm->m_FbIdsInRequest );
}
- m_PresentFeedback.m_uQueuedPresents++;
+ GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents++;
uint32_t uCurrentPresentCtx = m_uNextPresentCtx;
m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3;
- m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = m_PresentFeedback.m_uQueuedPresents;
+ m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents;
- drm_log.debugf("flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents);
- gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents );
+ drm_log.debugf("flip commit %" PRIu64, (uint64_t)GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents);
+ gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents );
ret = drmModeAtomicCommit(drm->fd, drm->req, drm->flags, &m_PresentCtxs[uCurrentPresentCtx] );
if ( ret != 0 )
@@ -3735,7 +3761,7 @@ namespace gamescope
// Clear our refs.
drm->m_FbIdsInRequest.clear();
- m_PresentFeedback.m_uQueuedPresents--;
+ GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents--;
if ( isPageFlip )
drm->uPendingFlipCount--;
@@ -3806,3 +3832,9 @@ namespace gamescope
return Set( new CDRMBackend{} );
}
}
+
+int HackyDRMPresent( const FrameInfo_t *pFrameInfo, bool bAsync )
+{
+ return static_cast( GetBackend() )->Present( pFrameInfo, bAsync );
+}
+
diff --git a/src/Backends/HeadlessBackend.cpp b/src/Backends/HeadlessBackend.cpp
index 49987f69fc..205485fe1f 100644
--- a/src/Backends/HeadlessBackend.cpp
+++ b/src/Backends/HeadlessBackend.cpp
@@ -8,7 +8,7 @@ extern int g_nPreferredOutputHeight;
namespace gamescope
{
- class CHeadlessConnector final : public IBackendConnector
+ class CHeadlessConnector final : public CBaseBackendConnector
{
public:
CHeadlessConnector()
@@ -38,6 +38,10 @@ namespace gamescope
{
return m_HDRInfo;
}
+ virtual bool IsVRRActive() const override
+ {
+ return false;
+ }
virtual std::span GetModes() const override
{
return std::span{};
@@ -81,6 +85,11 @@ namespace gamescope
return "Virtual Display";
}
+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override
+ {
+ return 0;
+ }
+
private:
BackendConnectorHDRInfo m_HDRInfo{};
};
@@ -157,11 +166,6 @@ namespace gamescope
return true;
}
- virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override
- {
- return 0;
- }
-
virtual void DirtyState( bool bForce, bool bForceModeset ) override
{
}
@@ -202,11 +206,6 @@ namespace gamescope
return nullptr;
}
- virtual bool IsVRRActive() const override
- {
- return false;
- }
-
virtual bool SupportsPlaneHardwareCursor() const override
{
return false;
diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp
index c39caa54b7..8998831867 100644
--- a/src/Backends/OpenVRBackend.cpp
+++ b/src/Backends/OpenVRBackend.cpp
@@ -19,6 +19,7 @@
#include "refresh_rate.h"
#include "edid.h"
#include "Ratio.h"
+#include "LibInputHandler.h"
#include
#include
@@ -48,6 +49,9 @@ extern gamescope::ConVar cv_hdr_enabled;
extern uint64_t g_SteamCompMgrLimitedAppRefreshCycle;
+void MakeFocusDirty();
+void update_connector_display_info_wl(struct drm_t *drm);
+
static LogScope openvr_log("openvr");
static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList );
@@ -62,6 +66,8 @@ gamescope::ConVar cv_vr_trackpad_relative_mouse_mode( "vr_trackpad_relativ
gamescope::ConVar cv_vr_trackpad_sensitivity( "vr_trackpad_sensitivity", 1500.f, "Sensitivity for VR Trackpad Mode" );
gamescope::ConVar cv_vr_trackpad_click_time( "vr_trackpad_click_time", 250'000'000ul, "Time to consider a 'click' vs a 'drag' when using trackpad mode. In nanoseconds." );
gamescope::ConVar cv_vr_trackpad_click_max_delta( "vr_trackpad_click_max_delta", 0.14f, "Max amount the cursor can move before not clicking." );
+gamescope::ConVar cv_vr_debug_force_opaque( "vr_debug_force_opaque", false, "Force textures to be treated as opaque." );
+gamescope::ConVar cv_vr_nudge_to_visible_per_connector( "vr_nudge_to_visible_per_connector", false, "" );
// Just below half of 120Hz, so we always at least poll input once per frame, regardless of cadence/cycles.
gamescope::ConVar cv_vr_poll_rate( "vr_poll_rate", 4'000'000ul, "Time between input polls. In nanoseconds." );
@@ -75,6 +81,8 @@ namespace vr
const EVRButtonId k_EButton_QAM = (EVRButtonId)(51);
}
+uint32_t get_appid_from_pid( pid_t pid );
+
///////////////////////////////////////////////
// Josh:
// GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired return *space separated* exts :(
@@ -170,98 +178,10 @@ static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice,
namespace gamescope
{
- class CVROverlayConnector final : public IBackendConnector
- {
- public:
-
- //////////////////////
- // IBackendConnector
- //////////////////////
-
- CVROverlayConnector()
- {
- }
- virtual ~CVROverlayConnector()
- {
- }
-
- virtual GamescopeScreenType GetScreenType() const override
- {
- return GAMESCOPE_SCREEN_TYPE_INTERNAL;
- }
- virtual GamescopePanelOrientation GetCurrentOrientation() const override
- {
- return GAMESCOPE_PANEL_ORIENTATION_0;
- }
- virtual bool SupportsHDR() const override
- {
- return false;
- }
- virtual bool IsHDRActive() const override
- {
- return false;
- }
- virtual const BackendConnectorHDRInfo &GetHDRInfo() const override
- {
- return m_HDRInfo;
- }
- virtual std::span GetModes() const override
- {
- return std::span{};
- }
-
- virtual bool SupportsVRR() const override
- {
- return false;
- }
-
- virtual std::span GetRawEDID() const override
- {
- return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() };
- }
- virtual std::span GetValidDynamicRefreshRates() const override
- {
- return std::span{};
- }
-
- virtual void GetNativeColorimetry(
- bool bHDR10,
- displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF,
- displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override
- {
- *displayColorimetry = displaycolorimetry_709;
- *displayEOTF = EOTF_Gamma22;
- *outputEncodingColorimetry = displaycolorimetry_709;
- *outputEncodingEOTF = EOTF_Gamma22;
- }
-
- virtual const char *GetName() const override
- {
- return "OpenVR";
- }
- virtual const char *GetMake() const override
- {
- return "Gamescope";
- }
- virtual const char *GetModel() const override
- {
- return "Virtual Display";
- }
-
- bool UpdateEdid()
- {
- m_FakeEdid = GenerateSimpleEdid( g_nNestedWidth, g_nNestedHeight );
-
- return true;
- }
-
- private:
- BackendConnectorHDRInfo m_HDRInfo{};
- std::vector m_FakeEdid;
- };
-
-
class COpenVRBackend;
+ class COpenVRPlane;
+ class COpenVRFb;
+ class COpenVRConnector;
class COpenVRFb final : public CBaseBackendFb
{
@@ -295,7 +215,7 @@ namespace gamescope
class COpenVRPlane
{
public:
- COpenVRPlane( COpenVRBackend *pBackend );
+ COpenVRPlane( COpenVRConnector *pConnector );
~COpenVRPlane();
bool Init( COpenVRPlane *pParent, COpenVRPlane *pSiblingBelow );
@@ -309,28 +229,190 @@ namespace gamescope
uint32_t GetSortOrder() const { return m_uSortOrder; }
bool IsSubview() const { return m_bIsSubview; }
+ COpenVRBackend *GetBackend() const { return m_pBackend; }
+
+ void OnPageFlip();
+
private:
+ COpenVRConnector *m_pConnector = nullptr;
COpenVRBackend *m_pBackend = nullptr;
+ std::string m_sDashboardOverlayKey;
+
bool m_bIsSubview = false;
uint32_t m_uSortOrder = 0;
vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid;
vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid;
+
+ std::mutex m_mutFbIds;
+ Rc m_pQueuedFbId;
+ Rc m_pVisibleFbId;
};
+ class COpenVRConnector final : public CBaseBackendConnector, public INestedHints
+ {
+ public:
+
+ COpenVRConnector( COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey );
+
+ //////////////////////
+ // IBackendConnector
+ //////////////////////
+
+ ~COpenVRConnector();
+ virtual GamescopeScreenType GetScreenType() const override;
+ virtual GamescopePanelOrientation GetCurrentOrientation() const override;
+ virtual bool SupportsHDR() const override;
+ virtual bool IsHDRActive() const override;
+ virtual const BackendConnectorHDRInfo &GetHDRInfo() const override;
+ virtual bool IsVRRActive() const override;
+ virtual std::span GetModes() const override;
+
+ virtual bool SupportsVRR() const override;
+
+ virtual std::span GetRawEDID() const override;
+ virtual std::span GetValidDynamicRefreshRates() const override;
+
+ virtual void GetNativeColorimetry(
+ bool bHDR10,
+ displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF,
+ displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override;
+
+ virtual const char *GetName() const override;
+ virtual const char *GetMake() const override;
+ virtual const char *GetModel() const override;
+
+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override;
+
+ virtual INestedHints *GetNestedHints() override
+ {
+ return this;
+ }
+
+ ///////////////////
+ // INestedHints
+ ///////////////////
+
+ virtual void SetCursorImage( std::shared_ptr info ) override;
+ virtual void SetRelativeMouseMode( bool bRelative ) override;
+ virtual void SetVisible( bool bVisible ) override;
+ virtual void SetTitle( std::shared_ptr szTitle ) override;
+ virtual void SetIcon( std::shared_ptr> uIconPixels ) override;
+ virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override;
+
+ bool UpdateEdid();
+
+ bool Init();
+
+ COpenVRBackend *GetBackend() const { return m_pBackend; }
+
+ COpenVRPlane *GetPrimaryPlane()
+ {
+ return &m_Planes[0];
+ }
+
+ std::span GetPlanes() { return std::span( &m_Planes[0], std::size( m_Planes ) ); }
+
+ bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); }
+ bool IsRelativeMouse() const { return m_bRelativeMouse; }
+
+ // Thread safe.
+ bool IsVisible() const
+ {
+ return m_bOverlayShown || m_bSceneAppVisible;
+ }
+
+ // Only called from event thread
+ void MarkOverlayShown( bool bShown )
+ {
+ m_bOverlayShown = bShown;
+ UpdateVisibility( "Overlay Visibility" );
+ }
+
+ // Only called from event thread
+ void MarkSceneAppShown( bool bShown )
+ {
+ m_bSceneAppVisible = bShown;
+ UpdateVisibility( "Scene App Visibility" );
+ }
+
+ void UpdateVisibility( const char *pszReason );
+
+ // XXX
+ std::atomic m_bUsingVRMouse = { true };
+ bool m_bCurrentlyOverridingPosition = false;
+
+ private:
+ COpenVRBackend *m_pBackend = nullptr;
+ COpenVRPlane m_Planes[8];
+
+ BackendConnectorHDRInfo m_HDRInfo{};
+ std::vector m_FakeEdid;
+
+ bool m_bNudgeToVisible = false;
+ std::atomic m_bRelativeMouse = false;
+
+ bool m_bWasVisible = false; // Event thread only
+ std::atomic m_bOverlayShown = { false };
+ std::atomic m_bSceneAppVisible = { false };
+ };
- class COpenVRBackend final : public CBaseBackend, public INestedHints
+ class COpenVRBackend final : public CBaseBackend
{
public:
COpenVRBackend()
- : m_Planes{ this, this, this, this, this, this, this, this }
+ : m_Thread{ [this](){ this->VRInputThread(); } }
+ , m_FlipHandlerThread{ [this](){ this->FlipHandlerThread(); } }
+ , m_LibInputWaiter{ "gamescope-libinput" }
{
}
virtual ~COpenVRBackend()
{
+ m_bRunning = false;
+
+ m_bInitted = true;
+ m_bInitted.notify_all();
+
+ m_Thread.join();
+ m_FlipHandlerThread.join();
}
+ void FlipHandlerThread()
+ {
+ pthread_setname_np( pthread_self(), "gamescope-vrflip" );
+
+ m_bInitted.wait( false );
+
+ while ( m_bRunning )
+ {
+ if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None )
+ openvr_log.errorf( "WaitFrameSync failed!" );
+
+ static constexpr uint64_t k_ulSchedulingFudge = 100'000; // 0.1ms
+ uint64_t ulNow = get_time_in_nanos() - k_ulSchedulingFudge;
+
+ GetVBlankTimer().MarkVBlank( ulNow, true );
+
+ // Nudge so that steamcompmgr releases commits.
+ nudge_steamcompmgr();
+
+ // Flush out any pending commits -> visible
+ // and any visible commits -> release.
+ {
+ std::scoped_lock lock{ m_mutActiveConnectors };
+
+ for ( COpenVRConnector *pConnector : m_pActiveConnectors )
+ {
+ for ( COpenVRPlane &plane : pConnector->GetPlanes() )
+ {
+ plane.OnPageFlip();
+ }
+ }
+ }
+ }
+ }
+
/////////////
// IBackend
/////////////
@@ -387,6 +469,8 @@ namespace gamescope
opt_name = gamescope_options[opt_index].name;
if (strcmp(opt_name, "vr-overlay-key") == 0) {
m_szOverlayKey = optarg;
+ } else if (strcmp(opt_name, "vr-app-overlay-key") == 0) {
+ m_szAppOverlayKey = optarg;
} else if (strcmp(opt_name, "vr-overlay-explicit-name") == 0) {
m_pchOverlayName = optarg;
m_bExplicitOverlayName = true;
@@ -414,6 +498,19 @@ namespace gamescope
m_flPhysicalPreCurvePitch = atof( optarg );
} else if (strcmp(opt_name, "vr-scroll-speed") == 0) {
m_flScrollSpeed = atof( optarg );
+ } else if (strcmp(opt_name, "vr-session-manager") == 0) {
+ openvr_log.infof( "Becoming the VR session manager." );
+
+ std::unique_ptr pLibInput = std::make_unique();
+ if ( pLibInput->Init() )
+ {
+ m_pLibInput = std::move( pLibInput );
+ m_LibInputWaiter.AddWaitable( m_pLibInput.get() );
+ }
+ else
+ {
+ openvr_log.errorf( "Could not start libinput for being the vr session manager" );
+ }
}
break;
case '?':
@@ -421,9 +518,6 @@ namespace gamescope
}
}
- if ( m_szOverlayKey.empty() )
- m_szOverlayKey = std::string( "gamescope." ) + wlserver_get_wl_display_name();
-
if ( !m_pchOverlayName )
m_pchOverlayName = "Gamescope";
@@ -475,27 +569,23 @@ namespace gamescope
// Setup misc. stuff
g_nOutputRefresh = (int32_t) ConvertHztomHz( roundf( vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ) ) );
- std::thread input_thread_vrinput( [this](){ this->VRInputThread(); } );
- input_thread_vrinput.detach();
+ m_bRunning = true;
+
+ m_bInitted = true;
+ m_bInitted.notify_all();
return true;
}
virtual bool PostInit() override
{
+ if ( m_szOverlayKey.empty() )
+ m_szOverlayKey = std::string( "gamescope." ) + wlserver_get_wl_display_name();
+
m_pIME = create_local_ime();
if ( !m_pIME )
return false;
- for ( uint32_t i = 0; i < 8; i++ )
- m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] );
-
- m_Connector.UpdateEdid();
- this->HackUpdatePatchedEdid();
-
- if ( cv_hdr_enabled && m_Connector.GetHDRInfo().bExposeHDRSupport )
- setenv( "DXVK_HDR", "1", false );
-
// This breaks cursor intersection right now.
// Come back to me later.
//Ratio aspectRatio{ g_nOutputWidth, g_nOutputHeight };
@@ -542,102 +632,6 @@ namespace gamescope
return true;
}
- virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override
- {
- bool bNeedsFullComposite = false;
-
- // TODO: Dedupe some of this composite check code between us and drm.cpp
- bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f);
-
- bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize;
-
- bNeedsFullComposite |= cv_composite_force;
- bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
- bNeedsFullComposite |= pFrameInfo->useNISLayer0;
- bNeedsFullComposite |= pFrameInfo->blurLayer0;
- bNeedsFullComposite |= bNeedsCompositeFromFilter;
- bNeedsFullComposite |= g_bColorSliderInUse;
- bNeedsFullComposite |= pFrameInfo->bFadingOut;
- bNeedsFullComposite |= !g_reshade_effect.empty();
- bNeedsFullComposite |= !UsesModifiers();
-
- if ( g_bOutputHDREnabled )
- bNeedsFullComposite |= g_bHDRItmEnable;
-
- if ( !SupportsColorManagement() )
- bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace );
-
- bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap);
-
- if ( !bNeedsFullComposite )
- {
- bool bNeedsBacking = true;
- if ( pFrameInfo->layerCount >= 1 )
- {
- if ( pFrameInfo->layers[0].isScreenSize() && ( !pFrameInfo->layers[0].hasAlpha() || cv_vr_transparent_backing ) )
- bNeedsBacking = false;
- }
-
- uint32_t uCurrentPlane = 0;
- if ( bNeedsBacking )
- {
- COpenVRPlane *pPlane = &m_Planes[uCurrentPlane++];
- pPlane->Present(
- OpenVRPlaneState
- {
- .pTexture = m_pBlackTexture.get(),
- .flSrcWidth = double( g_nOutputWidth ),
- .flSrcHeight = double( g_nOutputHeight ),
- .nDstWidth = int32_t( g_nOutputWidth ),
- .nDstHeight = int32_t( g_nOutputHeight ),
- .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU,
- .bOpaque = !cv_vr_transparent_backing,
- .flAlpha = cv_vr_transparent_backing ? 0.0f : 1.0f,
- } );
- }
-
- for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ )
- m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr );
- }
- else
- {
- std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false );
- if ( !oCompositeResult )
- {
- openvr_log.errorf( "vulkan_composite failed" );
- return -EINVAL;
- }
-
- vulkan_wait( *oCompositeResult, true );
-
- FrameInfo_t::Layer_t compositeLayer{};
- compositeLayer.scale.x = 1.0;
- compositeLayer.scale.y = 1.0;
- compositeLayer.opacity = 1.0;
- compositeLayer.zpos = g_zposBase;
-
- compositeLayer.tex = vulkan_get_last_output_image( false, false );
- compositeLayer.applyColorMgmt = false;
-
- compositeLayer.filter = GamescopeUpscaleFilter::NEAREST;
- compositeLayer.ctm = nullptr;
- compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB;
-
- GetPrimaryPlane()->Present( &compositeLayer );
-
- for ( int i = 1; i < 8; i++ )
- m_Planes[i].Present( nullptr );
- }
-
-
- GetVBlankTimer().UpdateWasCompositing( true );
- GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime );
-
- this->PollState();
-
- return 0;
- }
-
virtual void DirtyState( bool bForce, bool bForceModeset ) override
{
}
@@ -733,21 +727,16 @@ namespace gamescope
virtual IBackendConnector *GetCurrentConnector() override
{
- return &m_Connector;
+ return m_pFocusConnector;
}
virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override
{
if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL )
- return &m_Connector;
+ return GetCurrentConnector();
return nullptr;
}
- virtual bool IsVRRActive() const override
- {
- return false;
- }
-
virtual bool SupportsPlaneHardwareCursor() const override
{
return false;
@@ -780,7 +769,7 @@ namespace gamescope
if ( ShouldNudgeToVisible() )
return true;
- return m_bOverlayVisible.load();
+ return m_nOverlaysVisible.load() != 0;
}
virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override
@@ -798,109 +787,89 @@ namespace gamescope
if ( !GetCurrentConnector() )
return;
+ // XXX: We should do this a better way that handles per-window and appid stuff
+ // down the line
+ if ( cv_hdr_enabled && GetCurrentConnector()->GetHDRInfo().bExposeHDRSupport )
+ {
+ setenv( "DXVK_HDR", "1", true );
+ }
+ else
+ {
+ setenv( "DXVK_HDR", "0", true );
+ }
+
WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), false );
}
virtual bool NeedsFrameSync() const override
{
- return true;
- }
- virtual VBlankScheduleTime FrameSync() override
- {
- WaitUntilVisible();
-
- if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None )
- openvr_log.errorf( "WaitFrameSync failed!" );
-
- uint64_t ulNow = get_time_in_nanos();
- return VBlankScheduleTime
- {
- .ulTargetVBlank = ulNow + 3'000'000, // Not right. just a stop-gap for now.
- .ulScheduledWakeupPoint = ulNow,
- };
+ return false;
}
virtual TouchClickMode GetTouchClickMode() override
{
- if ( cv_vr_trackpad_relative_mouse_mode && m_bRelativeMouse )
+ COpenVRConnector *pConnector = static_cast( GetCurrentConnector() );
+ if ( cv_vr_trackpad_relative_mouse_mode && pConnector && pConnector->IsRelativeMouse() )
{
return TouchClickModes::Trackpad;
}
+ if ( VirtualConnectorInSteamPerAppState() )
+ {
+ if ( !VirtualConnectorKeyIsSteam( pConnector->GetVirtualConnectorKey() ) )
+ return TouchClickModes::Left;
+ }
+
return CBaseBackend::GetTouchClickMode();
}
- virtual INestedHints *GetNestedHints() override
+ bool UsesVirtualConnectors() override
{
- return this;
+ return true;
}
+ std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override
+ {
+ std::shared_ptr pConnector = std::make_shared( this, ulVirtualConnectorKey );
- ///////////////////
- // INestedHints
- ///////////////////
+ bool bSetCurrentConnector = false;
+ {
+ if ( !m_pFocusConnector )
+ {
+ SetFocus( pConnector.get() );
+ bSetCurrentConnector = true;
+ }
+ }
- virtual void SetCursorImage( std::shared_ptr info ) override
- {
- }
- virtual void SetRelativeMouseMode( bool bRelative ) override
- {
- if ( bRelative != m_bRelativeMouse )
+ if ( !pConnector->Init() )
{
- for ( COpenVRPlane &plane : m_Planes )
+ if ( bSetCurrentConnector )
{
- vr::VROverlay()->SetOverlayFlag( plane.GetOverlay(), vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && bRelative );
+ SetFocus( nullptr );
}
- m_bRelativeMouse = bRelative;
+ return nullptr;
}
- }
- virtual void SetVisible( bool bVisible ) override
- {
- vr::VROverlay()->SetOverlayFlag( GetPrimaryPlane()->GetOverlay(), vr::VROverlayFlags_VisibleInDashboard, bVisible );
- }
- virtual void SetTitle( std::shared_ptr szTitle ) override
- {
- if ( !m_bExplicitOverlayName )
- vr::VROverlay()->SetOverlayName( GetPrimaryPlane()->GetOverlay(), szTitle ? szTitle->c_str() : m_pchOverlayName );
+ std::scoped_lock lock{ m_mutActiveConnectors };
+ m_pActiveConnectors.push_back( pConnector.get() );
+ return pConnector;
}
- virtual void SetIcon( std::shared_ptr> uIconPixels ) override
+
+ void NotifyPhysicalInput( InputType eInputType ) override
{
- if ( cv_vr_use_window_icons && uIconPixels && uIconPixels->size() >= 3 )
+ if ( eInputType == InputType::Mouse )
{
- const uint32_t uWidth = (*uIconPixels)[0];
- const uint32_t uHeight = (*uIconPixels)[1];
+ // TODO: Avoid this lock someday.
+ // Can we make this a shared_mutex for r/w?
- struct rgba_t
- {
- uint8_t r,g,b,a;
- };
+ std::scoped_lock lock{ m_mutActiveConnectors };
- for ( uint32_t& val : *uIconPixels )
+ COpenVRConnector *pConnector = static_cast( GetCurrentConnector() );
+ if ( pConnector )
{
- rgba_t rgb = *((rgba_t*)&val);
- std::swap(rgb.r, rgb.b);
- val = *((uint32_t*)&rgb);
+ pConnector->m_bUsingVRMouse = false;
}
-
- vr::VROverlay()->SetOverlayRaw( GetPrimaryPlane()->GetOverlayThumbnail(), &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) );
- }
- else if ( m_pchOverlayIcon )
- {
- vr::VROverlay()->SetOverlayFromFile( GetPrimaryPlane()->GetOverlayThumbnail(), m_pchOverlayIcon );
- }
- else
- {
- vr::VROverlay()->ClearOverlayTexture( GetPrimaryPlane()->GetOverlayThumbnail() );
}
}
- virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override
- {
- // Do nothing.
- }
- virtual std::shared_ptr GetHostCursor() override
- {
- return nullptr;
- }
vr::IVRIPCResourceManagerClient *GetIPCResourceManager()
{
@@ -913,6 +882,7 @@ namespace gamescope
}
const char *GetOverlayKey() const { return m_szOverlayKey.c_str(); }
+ const char *GetAppOverlayKey() const { return m_szAppOverlayKey.c_str(); }
const char *GetOverlayName() const { return m_pchOverlayName; }
const char *GetOverlayIcon() const { return m_pchOverlayIcon; }
bool ShouldEnableControlBar() const { return m_bEnableControlBar; }
@@ -923,10 +893,11 @@ namespace gamescope
float GetPhysicalCurvature() const { return m_flPhysicalCurvature; }
float GetPhysicalPreCurvePitch() const { return m_flPhysicalPreCurvePitch; }
float GetScrollSpeed() const { return m_flScrollSpeed; }
- bool IsRelativeMouse() const { return m_bRelativeMouse; }
- bool ShouldNudgeToVisible() const { return m_bNudgeToVisible; }
bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); }
+ bool ShouldNudgeToVisible() const { return m_bNudgeToVisible; }
+
+ CVulkanTexture *GetBlackTexture() { return m_pBlackTexture.get(); }
protected:
@@ -936,200 +907,320 @@ namespace gamescope
private:
- COpenVRPlane *GetPrimaryPlane()
- {
- return &m_Planes[0];
- }
-
void WaitUntilVisible()
{
if ( ShouldNudgeToVisible() )
return;
- m_bOverlayVisible.wait( false );
+ m_nOverlaysVisible.wait( 0 );
+ }
+
+ void SetFocus( COpenVRConnector *pFocus )
+ {
+ COpenVRConnector *pPreviousFocus = m_pFocusConnector.exchange( pFocus );
+ if ( pPreviousFocus != pFocus )
+ {
+ MakeFocusDirty();
+ update_connector_display_info_wl( NULL );
+ }
}
void VRInputThread()
{
pthread_setname_np( pthread_self(), "gamescope-vrinp" );
+ m_bInitted.wait( false );
+
// Josh: PollNextOverlayEvent sucks.
// I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep.
- while (true)
+ while ( m_bRunning )
{
- for ( COpenVRPlane &plane : m_Planes )
{
- vr::VREvent_t vrEvent;
- while( vr::VROverlay()->PollNextOverlayEvent( plane.GetOverlay(), &vrEvent, sizeof( vrEvent ) ) )
+ std::scoped_lock lock{ m_mutActiveConnectors };
+
+ for ( COpenVRConnector *pConnector : m_pActiveConnectors )
{
- switch( vrEvent.eventType )
+ bool bIsSteam = VirtualConnectorKeyIsSteam( pConnector->GetVirtualConnectorKey() );
+
+ for ( COpenVRPlane &plane : pConnector->GetPlanes() )
{
- case vr::VREvent_OverlayClosed:
- case vr::VREvent_Quit:
+ vr::VREvent_t vrEvent;
+ while( vr::VROverlay()->PollNextOverlayEvent( plane.GetOverlay(), &vrEvent, sizeof( vrEvent ) ) )
{
- if ( !plane.IsSubview() )
+ switch( vrEvent.eventType )
{
- raise( SIGTERM );
- }
- break;
- }
+ case vr::VREvent_Quit:
+ {
+ raise( SIGTERM );
+ }
+ break;
- case vr::VREvent_KeyboardCharInput:
- {
- if (m_pIME)
- {
- type_text(m_pIME, vrEvent.data.keyboard.cNewInput);
- }
- break;
- }
+ case vr::VREvent_OverlayClosed:
+ {
+ if ( !steamMode || bIsSteam )
+ {
+ if ( !plane.IsSubview() )
+ {
+ raise( SIGTERM );
+ }
+ }
+ else
+ {
+ // How do we quit a game?
+ // Do we?
+ }
+ break;
+ }
- case vr::VREvent_MouseMove:
- {
- float flX = vrEvent.data.mouse.x / float( g_nOutputWidth );
- float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight );
+ case vr::VREvent_SceneApplicationChanged:
+ {
+ if ( m_uCurrentScenePid != vrEvent.data.process.pid )
+ {
+ m_uCurrentScenePid = vrEvent.data.process.pid;
+ m_uCurrentSceneAppId = get_appid_from_pid( m_uCurrentScenePid );
+
+ openvr_log.debugf( "SceneApplicationChanged -> pid: %u appid: %u", m_uCurrentScenePid, m_uCurrentSceneAppId );
+
+ std::optional oulNewSceneAppVirtualConnectorKey;
+ if ( cv_backend_virtual_connector_strategy == VirtualConnectorStrategies::PerAppId )
+ {
+ oulNewSceneAppVirtualConnectorKey = m_uCurrentSceneAppId;
+ }
+
+ if ( ( oulNewSceneAppVirtualConnectorKey || m_oulCurrentSceneVirtualConnectorKey ) &&
+ ( oulNewSceneAppVirtualConnectorKey != m_oulCurrentSceneVirtualConnectorKey ) )
+ {
+ for ( COpenVRConnector *pOtherConnector : m_pActiveConnectors )
+ {
+ if ( oulNewSceneAppVirtualConnectorKey )
+ {
+ if ( pOtherConnector->GetVirtualConnectorKey() == *oulNewSceneAppVirtualConnectorKey )
+ pOtherConnector->MarkSceneAppShown( true );
+ }
+
+ if ( m_oulCurrentSceneVirtualConnectorKey )
+ {
+ if ( pOtherConnector->GetVirtualConnectorKey() == *m_oulCurrentSceneVirtualConnectorKey )
+ pOtherConnector->MarkSceneAppShown( false );
+ }
+ }
+ }
+
+ m_oulCurrentSceneVirtualConnectorKey = oulNewSceneAppVirtualConnectorKey;
+ }
- TouchClickMode eMode = GetTouchClickMode();
- // Always warp a cursor, even if it's invisible, so we get hover events.
- bool bAlwaysMoveCursor = eMode == TouchClickModes::Passthrough && cv_vr_always_warp_cursor;
+ break;
+ }
- if ( eMode == TouchClickModes::Trackpad )
- {
- glm::vec2 vOldTrackpadPos = m_vScreenTrackpadPos;
- m_vScreenTrackpadPos = glm::vec2{ flX, flY };
+ case vr::VREvent_KeyboardCharInput:
+ {
+ if (m_pIME)
+ {
+ type_text(m_pIME, vrEvent.data.keyboard.cNewInput);
+ }
+ break;
+ }
- if ( m_bMouseDown )
+ case vr::VREvent_MouseMove:
+ {
+ if ( pConnector->m_bUsingVRMouse )
+ {
+ SetFocus( pConnector );
+ float flX = vrEvent.data.mouse.x / float( g_nOutputWidth );
+ float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight );
+
+ TouchClickMode eMode = GetTouchClickMode();
+ // Always warp a cursor, even if it's invisible, so we get hover events.
+ bool bAlwaysMoveCursor = eMode == TouchClickModes::Passthrough && cv_vr_always_warp_cursor;
+
+ if ( eMode == TouchClickModes::Trackpad )
+ {
+ glm::vec2 vOldTrackpadPos = m_vScreenTrackpadPos;
+ m_vScreenTrackpadPos = glm::vec2{ flX, flY };
+
+ if ( m_bMouseDown )
+ {
+ glm::vec2 vDelta = ( m_vScreenTrackpadPos - vOldTrackpadPos );
+ // We are based off normalized coords, so we need to fix the aspect ratio
+ // or we get different sensitivities on X and Y.
+ vDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth );
+
+ vDelta *= float( cv_vr_trackpad_sensitivity );
+
+ wlserver_lock();
+ wlserver_mousemotion( vDelta.x, vDelta.y, ++m_uFakeTimestamp );
+ wlserver_unlock();
+ }
+ }
+ else
+ {
+ wlserver_lock();
+ wlserver_touchmotion( flX, flY , 0, ++m_uFakeTimestamp, bAlwaysMoveCursor );
+ wlserver_unlock();
+ }
+ }
+ break;
+ }
+ case vr::VREvent_FocusEnter:
+ {
+ pConnector->m_bUsingVRMouse = true;
+ SetFocus( pConnector );
+ break;
+ }
+ case vr::VREvent_MouseButtonUp:
+ case vr::VREvent_MouseButtonDown:
{
- glm::vec2 vDelta = ( m_vScreenTrackpadPos - vOldTrackpadPos );
- // We are based off normalized coords, so we need to fix the aspect ratio
- // or we get different sensitivities on X and Y.
- vDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth );
+ SetFocus( pConnector );
- vDelta *= float( cv_vr_trackpad_sensitivity );
+ if ( !pConnector->m_bUsingVRMouse )
+ {
+ pConnector->m_bUsingVRMouse = true;
+ }
+ else
+ {
+ float flX = vrEvent.data.mouse.x / float( g_nOutputWidth );
+ float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight );
+
+ uint64_t ulNow = get_time_in_nanos();
+
+ if ( vrEvent.eventType == vr::VREvent_MouseButtonDown )
+ {
+ m_ulMouseDownTime = ulNow;
+ m_bMouseDown = true;
+ }
+ else
+ {
+ m_bMouseDown = false;
+ }
+
+ TouchClickMode eMode = GetTouchClickMode();
+ if ( eMode == TouchClickModes::Trackpad )
+ {
+ m_vScreenTrackpadPos = glm::vec2{ flX, flY };
+
+ if ( vrEvent.eventType == vr::VREvent_MouseButtonUp )
+ {
+ glm::vec2 vTotalDelta = ( m_vScreenTrackpadPos - m_vScreenStartTrackpadPos );
+ vTotalDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth );
+ float flMaxAbsTotalDelta = std::max( std::abs( vTotalDelta.x ), std::abs( vTotalDelta.y ) );
+
+ uint64_t ulClickTime = ulNow - m_ulMouseDownTime;
+ if ( ulClickTime <= cv_vr_trackpad_click_time && flMaxAbsTotalDelta <= cv_vr_trackpad_click_max_delta )
+ {
+ wlserver_lock();
+ wlserver_mousebutton( BTN_LEFT, true, ++m_uFakeTimestamp );
+ wlserver_unlock();
+
+ sleep_for_nanos( g_SteamCompMgrLimitedAppRefreshCycle + 1'000'000 );
+
+ wlserver_lock();
+ wlserver_mousebutton( BTN_LEFT, false, ++m_uFakeTimestamp );
+ wlserver_unlock();
+ }
+ else
+ {
+ m_vScreenStartTrackpadPos = m_vScreenTrackpadPos;
+ }
+ }
+ }
+ else
+ {
+ wlserver_lock();
+ if ( vrEvent.eventType == vr::VREvent_MouseButtonDown )
+ wlserver_touchdown( flX, flY, 0, ++m_uFakeTimestamp );
+ else
+ wlserver_touchup( 0, ++m_uFakeTimestamp );
+ wlserver_unlock();
+ }
+ }
+ break;
+ }
+
+ case vr::VREvent_ScrollSmooth:
+ {
+ SetFocus( pConnector );
+ float flX = -vrEvent.data.scroll.xdelta * m_flScrollSpeed;
+ float flY = -vrEvent.data.scroll.ydelta * m_flScrollSpeed;
wlserver_lock();
- wlserver_mousemotion( vDelta.x, vDelta.y, ++m_uFakeTimestamp );
+ wlserver_mousewheel( flX, flY, ++m_uFakeTimestamp );
wlserver_unlock();
+ break;
}
- }
- else
- {
- wlserver_lock();
- wlserver_touchmotion( flX, flY , 0, ++m_uFakeTimestamp, bAlwaysMoveCursor );
- wlserver_unlock();
- }
- break;
- }
- case vr::VREvent_MouseButtonUp:
- case vr::VREvent_MouseButtonDown:
- {
- float flX = vrEvent.data.mouse.x / float( g_nOutputWidth );
- float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight );
-
- uint64_t ulNow = get_time_in_nanos();
-
- if ( vrEvent.eventType == vr::VREvent_MouseButtonDown )
- {
- m_ulMouseDownTime = ulNow;
- m_bMouseDown = true;
- }
- else
- {
- m_bMouseDown = false;
- }
-
- TouchClickMode eMode = GetTouchClickMode();
- if ( eMode == TouchClickModes::Trackpad )
- {
- m_vScreenTrackpadPos = glm::vec2{ flX, flY };
- if ( vrEvent.eventType == vr::VREvent_MouseButtonUp )
+ case vr::VREvent_ButtonPress:
{
- glm::vec2 vTotalDelta = ( m_vScreenTrackpadPos - m_vScreenStartTrackpadPos );
- vTotalDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth );
- float flMaxAbsTotalDelta = std::max( std::abs( vTotalDelta.x ), std::abs( vTotalDelta.y ) );
-
- uint64_t ulClickTime = ulNow - m_ulMouseDownTime;
- if ( ulClickTime <= cv_vr_trackpad_click_time && flMaxAbsTotalDelta <= cv_vr_trackpad_click_max_delta )
- {
- wlserver_lock();
- wlserver_mousebutton( BTN_LEFT, true, ++m_uFakeTimestamp );
- wlserver_unlock();
+ SetFocus( pConnector );
+ vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button;
- sleep_for_nanos( g_SteamCompMgrLimitedAppRefreshCycle + 1'000'000 );
+ if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM)
+ break;
- wlserver_lock();
- wlserver_mousebutton( BTN_LEFT, false, ++m_uFakeTimestamp );
- wlserver_unlock();
- }
+ if (button == vr::k_EButton_Steam)
+ openvr_log.infof("STEAM button pressed.");
else
+ openvr_log.infof("QAM button pressed.");
+
+ wlserver_open_steam_menu( button == vr::k_EButton_QAM );
+ break;
+ }
+
+ case vr::VREvent_OverlayShown:
+ case vr::VREvent_OverlayHidden:
+ {
+ // Only handle this for the base plane.
+ // Subviews can be hidden if we hide them ourselves,
+ // or for other reasons.
+ if ( !plane.IsSubview() )
{
- m_vScreenStartTrackpadPos = m_vScreenTrackpadPos;
+ pConnector->MarkOverlayShown( vrEvent.eventType == vr::VREvent_OverlayShown );
}
+ break;
}
- }
- else
- {
- wlserver_lock();
- if ( vrEvent.eventType == vr::VREvent_MouseButtonDown )
- wlserver_touchdown( flX, flY, 0, ++m_uFakeTimestamp );
- else
- wlserver_touchup( 0, ++m_uFakeTimestamp );
- wlserver_unlock();
- }
- break;
- }
- case vr::VREvent_ScrollSmooth:
- {
- float flX = -vrEvent.data.scroll.xdelta * m_flScrollSpeed;
- float flY = -vrEvent.data.scroll.ydelta * m_flScrollSpeed;
- wlserver_lock();
- wlserver_mousewheel( flX, flY, ++m_uFakeTimestamp );
- wlserver_unlock();
- break;
+ default:
+ break;
+ }
}
+ }
+ }
- case vr::VREvent_ButtonPress:
- {
- vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button;
+ // Process mouse input state.
+ for ( COpenVRConnector *pConnector : m_pActiveConnectors )
+ {
+ bool bUsingPhysicalMouse = GetCurrentConnector() == pConnector && !pConnector->m_bUsingVRMouse;
- if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM)
- break;
+ bool bShowCursor = !pConnector->IsRelativeMouse();
- if (button == vr::k_EButton_Steam)
- openvr_log.infof("STEAM button pressed.");
- else
- openvr_log.infof("QAM button pressed.");
+ if ( bUsingPhysicalMouse && bShowCursor )
+ {
+ vr::HmdVector2_t vMousePos =
+ {
+ static_cast( wlserver.mouse_surface_cursorx ),
+ static_cast( static_cast( g_nOutputHeight ) - wlserver.mouse_surface_cursory ),
+ };
- wlserver_open_steam_menu( button == vr::k_EButton_QAM );
- break;
- }
+ vr::VROverlay()->SetOverlayCursorPositionOverride( pConnector->GetPrimaryPlane()->GetOverlay(), &vMousePos );
+ pConnector->m_bCurrentlyOverridingPosition = true;
+ }
+ else
+ {
+ if ( !pConnector->m_bCurrentlyOverridingPosition )
+ continue;
- case vr::VREvent_OverlayShown:
- case vr::VREvent_OverlayHidden:
- {
- // Only handle this for the base plane.
- // Subviews can be hidden if we hide them ourselves,
- // or for other reasons.
- if ( !plane.IsSubview() )
- {
- m_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown;
- m_bOverlayVisible.notify_all();
- }
- break;
- }
+ vr::VROverlay()->ClearOverlayCursorPositionOverride( pConnector->GetPrimaryPlane()->GetOverlay() );
- default:
- break;
+ pConnector->m_bCurrentlyOverridingPosition = false;
}
}
}
+
sleep_for_nanos( cv_vr_poll_rate );
}
}
- CVROverlayConnector m_Connector;
std::string m_szOverlayKey;
+ std::string m_szAppOverlayKey;
const char *m_pchOverlayName = nullptr;
const char *m_pchOverlayIcon = nullptr;
bool m_bExplicitOverlayName = false;
@@ -1138,19 +1229,18 @@ namespace gamescope
bool m_bEnableControlBarKeyboard = false;
bool m_bEnableControlBarClose = false;
bool m_bModal = false;
- std::atomic m_bRelativeMouse = false;
float m_flPhysicalWidth = 2.0f;
float m_flPhysicalCurvature = 0.0f;
float m_flPhysicalPreCurvePitch = 0.0f;
float m_flScrollSpeed = 1.0f;
- COpenVRPlane m_Planes[8];
+ // TODO: Restructure and remove the need for this.
wlserver_input_method *m_pIME = nullptr;
OwningRc m_pBlackTexture;
- std::atomic m_bOverlayVisible = { false };
+ std::atomic m_nOverlaysVisible = { 0 };
vr::IVRIPCResourceManagerClient *m_pIPCResourceManager = nullptr;
std::unordered_map> m_FormatModifiers;
@@ -1162,8 +1252,341 @@ namespace gamescope
// Fake "trackpad" tracking for the whole overlay panel.
glm::vec2 m_vScreenTrackpadPos{};
glm::vec2 m_vScreenStartTrackpadPos{};
+
+ uint32_t m_uCurrentScenePid = -1;
+ uint32_t m_uCurrentSceneAppId = 0;
+ std::optional m_oulCurrentSceneVirtualConnectorKey;
+
+ friend COpenVRConnector;
+ std::vector m_pActiveConnectors;
+ std::mutex m_mutActiveConnectors;
+ std::atomic m_pFocusConnector;
+
+ std::thread m_Thread;
+ std::thread m_FlipHandlerThread;
+ std::atomic m_bInitted = { false };
+ std::atomic m_bRunning = { false };
+
+ std::shared_ptr m_pLibInput;
+ CAsyncWaiter, 16> m_LibInputWaiter;
};
+ ////////////////////
+ // COpenVRConnector
+ ////////////////////
+
+ COpenVRConnector::COpenVRConnector( COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey )
+ : CBaseBackendConnector{ ulVirtualConnectorKey }
+ , m_pBackend{ pBackend }
+ , m_Planes{ this, this, this, this, this, this, this, this }
+ {
+ }
+
+ COpenVRConnector::~COpenVRConnector()
+ {
+ std::scoped_lock lock{ m_pBackend->m_mutActiveConnectors };
+
+ MarkSceneAppShown( false );
+ MarkOverlayShown( false );
+
+ auto iter = m_pBackend->m_pActiveConnectors.begin();
+ for ( ; iter != m_pBackend->m_pActiveConnectors.end(); iter++ )
+ {
+ if ( *iter == this )
+ break;
+ }
+ if ( iter != m_pBackend->m_pActiveConnectors.end() )
+ m_pBackend->m_pActiveConnectors.erase( iter );
+
+ COpenVRConnector *pThis = this;
+ m_pBackend->m_pFocusConnector.compare_exchange_strong( pThis, nullptr );
+ }
+
+ GamescopeScreenType COpenVRConnector::GetScreenType() const
+ {
+ return GAMESCOPE_SCREEN_TYPE_INTERNAL;
+ }
+ GamescopePanelOrientation COpenVRConnector::GetCurrentOrientation() const
+ {
+ return GAMESCOPE_PANEL_ORIENTATION_0;
+ }
+ bool COpenVRConnector::SupportsHDR() const
+ {
+ return false;
+ }
+ bool COpenVRConnector::IsHDRActive() const
+ {
+ return false;
+ }
+ const BackendConnectorHDRInfo &COpenVRConnector::GetHDRInfo() const
+ {
+ return m_HDRInfo;
+ }
+ bool COpenVRConnector::IsVRRActive() const
+ {
+ return false;
+ }
+ std::span COpenVRConnector::GetModes() const
+ {
+ return std::span{};
+ }
+
+ bool COpenVRConnector::SupportsVRR() const
+ {
+ return false;
+ }
+
+ std::span COpenVRConnector::GetRawEDID() const
+ {
+ return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() };
+ }
+ std::span COpenVRConnector::GetValidDynamicRefreshRates() const
+ {
+ return std::span{};
+ }
+
+ void COpenVRConnector::GetNativeColorimetry(
+ bool bHDR10,
+ displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF,
+ displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const
+ {
+ *displayColorimetry = displaycolorimetry_709;
+ *displayEOTF = EOTF_Gamma22;
+ *outputEncodingColorimetry = displaycolorimetry_709;
+ *outputEncodingEOTF = EOTF_Gamma22;
+ }
+
+ const char *COpenVRConnector::GetName() const
+ {
+ return "OpenVR";
+ }
+ const char *COpenVRConnector::GetMake() const
+ {
+ return "Gamescope";
+ }
+ const char *COpenVRConnector::GetModel() const
+ {
+ return "Virtual Display";
+ }
+
+ int COpenVRConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync )
+ {
+ bool bNeedsFullComposite = false;
+
+ // TODO: Dedupe some of this composite check code between us and drm.cpp
+ bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f);
+
+ bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize;
+
+ bNeedsFullComposite |= cv_composite_force;
+ bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
+ bNeedsFullComposite |= pFrameInfo->useNISLayer0;
+ bNeedsFullComposite |= pFrameInfo->blurLayer0;
+ bNeedsFullComposite |= bNeedsCompositeFromFilter;
+ bNeedsFullComposite |= g_bColorSliderInUse;
+ bNeedsFullComposite |= pFrameInfo->bFadingOut;
+ bNeedsFullComposite |= !g_reshade_effect.empty();
+ bNeedsFullComposite |= !m_pBackend->UsesModifiers();
+
+ if ( g_bOutputHDREnabled )
+ bNeedsFullComposite |= g_bHDRItmEnable;
+
+ if ( !m_pBackend->SupportsColorManagement() )
+ bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace );
+
+ bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap);
+
+ if ( !bNeedsFullComposite )
+ {
+ bool bNeedsBacking = true;
+ if ( pFrameInfo->layerCount >= 1 )
+ {
+ if ( pFrameInfo->layers[0].isScreenSize() && ( !pFrameInfo->layers[0].hasAlpha() || cv_vr_transparent_backing ) )
+ bNeedsBacking = false;
+ }
+
+ uint32_t uCurrentPlane = 0;
+ if ( bNeedsBacking )
+ {
+ COpenVRPlane *pPlane = &m_Planes[uCurrentPlane++];
+ pPlane->Present(
+ OpenVRPlaneState
+ {
+ .pTexture = m_pBackend->GetBlackTexture(),
+ .flSrcWidth = double( g_nOutputWidth ),
+ .flSrcHeight = double( g_nOutputHeight ),
+ .nDstWidth = int32_t( g_nOutputWidth ),
+ .nDstHeight = int32_t( g_nOutputHeight ),
+ .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU,
+ .bOpaque = !cv_vr_transparent_backing,
+ .flAlpha = cv_vr_transparent_backing ? 0.0f : 1.0f,
+ } );
+ }
+
+ for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ )
+ m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr );
+ }
+ else
+ {
+ std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false );
+ if ( !oCompositeResult )
+ {
+ openvr_log.errorf( "vulkan_composite failed" );
+ return -EINVAL;
+ }
+
+ vulkan_wait( *oCompositeResult, true );
+
+ FrameInfo_t::Layer_t compositeLayer{};
+ compositeLayer.scale.x = 1.0;
+ compositeLayer.scale.y = 1.0;
+ compositeLayer.opacity = 1.0;
+ compositeLayer.zpos = g_zposBase;
+
+ compositeLayer.tex = vulkan_get_last_output_image( false, false );
+ compositeLayer.applyColorMgmt = false;
+
+ compositeLayer.filter = GamescopeUpscaleFilter::NEAREST;
+ compositeLayer.ctm = nullptr;
+ compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB;
+
+ GetPrimaryPlane()->Present( &compositeLayer );
+
+ for ( int i = 1; i < 8; i++ )
+ m_Planes[i].Present( nullptr );
+ }
+
+
+ GetVBlankTimer().UpdateWasCompositing( true );
+ GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime );
+
+ m_pBackend->PollState();
+
+ return 0;
+ }
+
+ ///////////////////
+ // INestedHints
+ ///////////////////
+
+ void COpenVRConnector::SetCursorImage( std::shared_ptr info )
+ {
+ }
+ void COpenVRConnector::SetRelativeMouseMode( bool bRelative )
+ {
+ if ( bRelative != m_bRelativeMouse )
+ {
+ for ( COpenVRPlane &plane : m_Planes )
+ {
+ vr::VROverlay()->SetOverlayFlag( plane.GetOverlay(), vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && bRelative );
+ }
+ m_bRelativeMouse = bRelative;
+ }
+ }
+ void COpenVRConnector::SetVisible( bool bVisible )
+ {
+ vr::VROverlay()->SetOverlayFlag( GetPrimaryPlane()->GetOverlay(), vr::VROverlayFlags_VisibleInDashboard, bVisible );
+ }
+ void COpenVRConnector::SetTitle( std::shared_ptr szTitle )
+ {
+ if ( !m_pBackend->m_bExplicitOverlayName )
+ vr::VROverlay()->SetOverlayName( GetPrimaryPlane()->GetOverlay(), szTitle ? szTitle->c_str() : m_pBackend->GetOverlayName() );
+ }
+ void COpenVRConnector::SetIcon( std::shared_ptr> uIconPixels )
+ {
+ if ( cv_vr_use_window_icons && uIconPixels && uIconPixels->size() >= 3 )
+ {
+ const uint32_t uWidth = (*uIconPixels)[0];
+ const uint32_t uHeight = (*uIconPixels)[1];
+
+ struct rgba_t
+ {
+ uint8_t r,g,b,a;
+ };
+
+ for ( uint32_t& val : *uIconPixels )
+ {
+ rgba_t rgb = *((rgba_t*)&val);
+ std::swap(rgb.r, rgb.b);
+ val = *((uint32_t*)&rgb);
+ }
+
+ vr::VROverlay()->SetOverlayRaw( GetPrimaryPlane()->GetOverlayThumbnail(), &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) );
+ }
+ else if ( m_pBackend->GetOverlayIcon() )
+ {
+ vr::VROverlay()->SetOverlayFromFile( GetPrimaryPlane()->GetOverlayThumbnail(), m_pBackend->GetOverlayIcon() );
+ }
+ else
+ {
+ vr::VROverlay()->ClearOverlayTexture( GetPrimaryPlane()->GetOverlayThumbnail() );
+ }
+ }
+
+ void COpenVRConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection )
+ {
+ // Do nothing
+ }
+
+ bool COpenVRConnector::UpdateEdid()
+ {
+ m_FakeEdid = GenerateSimpleEdid( g_nNestedWidth, g_nNestedHeight );
+
+ return true;
+ }
+
+
+ bool COpenVRConnector::Init()
+ {
+ openvr_log.debugf( "New connector! -> ulKey: %lu", GetVirtualConnectorKey() );
+
+ m_bNudgeToVisible = m_pBackend->ShouldNudgeToVisible();
+
+ for ( uint32_t i = 0; i < 8; i++ )
+ {
+ bool bSuccess = m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] );
+ if ( !bSuccess )
+ return false;
+ }
+
+ UpdateEdid();
+ m_pBackend->HackUpdatePatchedEdid();
+
+ if ( g_bForceRelativeMouse )
+ this->SetRelativeMouseMode( true );
+
+ if ( m_pBackend->m_oulCurrentSceneVirtualConnectorKey &&
+ GetVirtualConnectorKey() == *m_pBackend->m_oulCurrentSceneVirtualConnectorKey )
+ {
+ MarkSceneAppShown( true );
+ }
+
+ return true;
+ }
+
+ void COpenVRConnector::UpdateVisibility( const char *pszReason )
+ {
+ bool bVisible = IsVisible();
+ if ( m_bWasVisible != bVisible )
+ {
+ int nNewOverlayVisibleCount;
+ if ( bVisible )
+ nNewOverlayVisibleCount = ++m_pBackend->m_nOverlaysVisible;
+ else
+ nNewOverlayVisibleCount = --m_pBackend->m_nOverlaysVisible;
+
+ m_pBackend->m_nOverlaysVisible.notify_all();
+
+ m_bWasVisible = bVisible;
+ openvr_log.debugf( "[%s] ulKey: %lu nNewOverlayVisibleCount: %d -> m_bOverlayShown: %s m_bSceneAppVisible: %s",
+ pszReason,
+ GetVirtualConnectorKey(),
+ nNewOverlayVisibleCount,
+ m_bOverlayShown ? "true" : "false",
+ m_bSceneAppVisible ? "true" : "false" );
+ }
+ }
+
/////////////////////////
// COpenVRFb
/////////////////////////
@@ -1186,12 +1609,18 @@ namespace gamescope
// COpenVRPlane
/////////////////////////
- COpenVRPlane::COpenVRPlane( COpenVRBackend *pBackend )
- : m_pBackend{ pBackend }
+ COpenVRPlane::COpenVRPlane( COpenVRConnector *pConnector )
+ : m_pConnector{ pConnector }
+ , m_pBackend{ pConnector->GetBackend() }
{
}
COpenVRPlane::~COpenVRPlane()
{
+ if ( m_hOverlayThumbnail != vr::k_ulOverlayHandleInvalid )
+ vr::VROverlay()->DestroyOverlay( m_hOverlayThumbnail );
+
+ if ( m_hOverlay != vr::k_ulOverlayHandleInvalid )
+ vr::VROverlay()->DestroyOverlay( m_hOverlay );
}
bool COpenVRPlane::Init( COpenVRPlane *pParent, COpenVRPlane *pSiblingBelow )
@@ -1203,10 +1632,36 @@ namespace gamescope
m_uSortOrder = pSiblingBelow->GetSortOrder() + 1;
}
+ std::string sOverlayKey = m_pBackend->GetOverlayKey();
+
+ VirtualConnectorStrategy eStrategy = cv_backend_virtual_connector_strategy;
+ if ( !VirtualConnectorStrategyIsSingleOutput( eStrategy ) )
+ {
+ uint64_t ulKey = m_pConnector->GetVirtualConnectorKey();
+ bool bIsSteam = VirtualConnectorKeyIsSteam( ulKey );
+ if ( !bIsSteam )
+ {
+ const char *pszAppOverlayKey = m_pBackend->GetAppOverlayKey();
+ if ( pszAppOverlayKey && *pszAppOverlayKey )
+ {
+ sOverlayKey = pszAppOverlayKey;
+ sOverlayKey += ".";
+ }
+ else
+ {
+ sOverlayKey += ".app.";
+ }
+ sOverlayKey += std::to_string( m_pConnector->GetVirtualConnectorKey() );
+ }
+ }
+
if ( !m_bIsSubview )
{
+ m_sDashboardOverlayKey = sOverlayKey;
+ openvr_log.debugf( "Creating new dashboard overlay: %s", m_sDashboardOverlayKey.c_str() );
+
vr::VROverlay()->CreateDashboardOverlay(
- m_pBackend->GetOverlayKey(),
+ sOverlayKey.c_str(),
m_pBackend->GetOverlayName(),
&m_hOverlay, &m_hOverlayThumbnail );
@@ -1216,7 +1671,7 @@ namespace gamescope
vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_WantsModalBehavior, m_pBackend->IsModal() );
vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true );
vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, false );
- vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && m_pBackend->IsRelativeMouse() );
+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && m_pConnector->IsRelativeMouse() );
vr::VROverlay()->SetOverlayWidthInMeters( m_hOverlay, m_pBackend->GetPhysicalWidth() );
vr::VROverlay()->SetOverlayCurvature ( m_hOverlay, m_pBackend->GetPhysicalCurvature() );
@@ -1233,7 +1688,7 @@ namespace gamescope
}
else
{
- std::string szSubviewName = m_pBackend->GetOverlayKey() + std::string(".layer") + std::to_string( (uintptr_t)this );
+ std::string szSubviewName = sOverlayKey + std::string(".layer") + std::to_string( m_uSortOrder );
vr::VROverlay()->CreateSubviewOverlay( pParent->GetOverlay(), szSubviewName.c_str(), "Gamescope Layer", &m_hOverlay );
}
@@ -1251,13 +1706,15 @@ namespace gamescope
vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode );
}
+ COpenVRFb *pFb = nullptr;
+
if ( oState )
{
vr::VROverlay()->SetOverlayAlpha( m_hOverlay, oState->flAlpha );
if ( m_pBackend->UsesModifiers() )
{
- vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, oState->bOpaque || !DRMFormatHasAlpha( oState->pTexture->drmFormat() ) );
+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, oState->bOpaque || !DRMFormatHasAlpha( oState->pTexture->drmFormat() ) || cv_vr_debug_force_opaque );
vr::HmdVector2_t vMouseScale =
{
@@ -1279,7 +1736,7 @@ namespace gamescope
vr::VROverlay()->ShowOverlay( m_hOverlay );
}
- COpenVRFb *pFb = static_cast( oState->pTexture->GetBackendFb() );
+ pFb = static_cast( oState->pTexture->GetBackendFb() );
vr::SharedTextureHandle_t ulHandle = pFb->GetSharedTextureHandle();
vr::Texture_t texture = { (void *)&ulHandle, vr::TextureType_SharedTextureHandle, vr::ColorSpace_Gamma };
@@ -1307,9 +1764,21 @@ namespace gamescope
vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture );
}
- if ( !m_bIsSubview && m_pBackend->ConsumeNudgeToVisible() )
+ if ( !m_bIsSubview )
{
- vr::VROverlay()->ShowDashboard( m_pBackend->GetOverlayKey() );
+ bool bNudgeToVisible = cv_vr_nudge_to_visible_per_connector
+ ? m_pConnector->ConsumeNudgeToVisible()
+ : m_pBackend->ConsumeNudgeToVisible();
+
+ if ( bNudgeToVisible )
+ {
+ vr::VROverlay()->ShowDashboard( m_sDashboardOverlayKey.c_str() );
+
+ // Make sure we don't leave any nudges either side.
+ m_pConnector->ConsumeNudgeToVisible();
+ if ( !cv_vr_nudge_to_visible_per_connector )
+ m_pBackend->ConsumeNudgeToVisible();
+ }
}
}
else
@@ -1319,6 +1788,11 @@ namespace gamescope
vr::VROverlay()->HideOverlay( m_hOverlay );
}
}
+
+ {
+ std::scoped_lock lock{ m_mutFbIds };
+ m_pQueuedFbId = pFb;
+ }
}
void COpenVRPlane::Present( const FrameInfo_t::Layer_t *pLayer )
@@ -1348,6 +1822,18 @@ namespace gamescope
}
}
+ void COpenVRPlane::OnPageFlip()
+ {
+ {
+ std::scoped_lock lock{ m_mutFbIds };
+
+ // XXX: We have no guarantee for WHAT the sequence is here. This could be total crap.
+ // but this is probably good enough for now?
+ m_pVisibleFbId = std::move( m_pQueuedFbId );
+ m_pQueuedFbId = nullptr;
+ }
+ }
+
/////////////////////////
// Backend Instantiator
/////////////////////////
diff --git a/src/Backends/SDLBackend.cpp b/src/Backends/SDLBackend.cpp
index 6d50f8d34e..325f91f78d 100644
--- a/src/Backends/SDLBackend.cpp
+++ b/src/Backends/SDLBackend.cpp
@@ -37,8 +37,6 @@ extern int g_nPreferredOutputHeight;
namespace gamescope
{
- extern std::shared_ptr GetX11HostCursor();
-
enum class SDLInitState
{
SDLInit_Waiting,
@@ -57,7 +55,7 @@ namespace gamescope
GAMESCOPE_SDL_EVENT_COUNT,
};
- class CSDLConnector final : public IBackendConnector
+ class CSDLConnector final : public CBaseBackendConnector, public INestedHints
{
public:
CSDLConnector();
@@ -74,6 +72,7 @@ namespace gamescope
virtual bool SupportsHDR() const override;
virtual bool IsHDRActive() const override;
virtual const BackendConnectorHDRInfo &GetHDRInfo() const override;
+ virtual bool IsVRRActive() const override;
virtual std::span GetModes() const override;
virtual bool SupportsVRR() const override;
@@ -99,6 +98,24 @@ namespace gamescope
return "Virtual Display";
}
+ virtual INestedHints *GetNestedHints() override
+ {
+ return this;
+ }
+
+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override;
+
+ ///////////////////
+ // INestedHints
+ ///////////////////
+
+ virtual void SetCursorImage( std::shared_ptr info ) override;
+ virtual void SetRelativeMouseMode( bool bRelative ) override;
+ virtual void SetVisible( bool bVisible ) override;
+ virtual void SetTitle( std::shared_ptr szTitle ) override;
+ virtual void SetIcon( std::shared_ptr> uIconPixels ) override;
+ virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override;
+
//--
SDL_Window *GetSDLWindow() const { return m_pWindow; }
@@ -109,7 +126,7 @@ namespace gamescope
BackendConnectorHDRInfo m_HDRInfo{};
};
- class CSDLBackend : public CBaseBackend, public INestedHints
+ class CSDLBackend : public CBaseBackend
{
public:
CSDLBackend();
@@ -126,7 +143,6 @@ namespace gamescope
virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override;
virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override;
- virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override;
virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override;
virtual bool PollState() override;
@@ -139,7 +155,6 @@ namespace gamescope
virtual IBackendConnector *GetCurrentConnector() override;
virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override;
- virtual bool IsVRRActive() const override;
virtual bool SupportsPlaneHardwareCursor() const override;
virtual bool SupportsTearing() const override;
@@ -152,19 +167,16 @@ namespace gamescope
virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override;
- virtual INestedHints *GetNestedHints() override;
-
- ///////////////////
- // INestedHints
- ///////////////////
+ ////////////////////////
+ // INestedHints Compat
+ ///////////////////////
- virtual void SetCursorImage( std::shared_ptr info ) override;
- virtual void SetRelativeMouseMode( bool bRelative ) override;
- virtual void SetVisible( bool bVisible ) override;
- virtual void SetTitle( std::shared_ptr szTitle ) override;
- virtual void SetIcon( std::shared_ptr> uIconPixels ) override;
- virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override;
- virtual std::shared_ptr GetHostCursor() override;
+ void SetCursorImage( std::shared_ptr info );
+ void SetRelativeMouseMode( bool bRelative );
+ void SetVisible( bool bVisible );
+ void SetTitle( std::shared_ptr szTitle );
+ void SetIcon( std::shared_ptr> uIconPixels );
+ void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection );
protected:
virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override;
private:
@@ -249,7 +261,7 @@ namespace gamescope
if ( !SDL_Vulkan_CreateSurface( m_pWindow, vulkan_get_instance(), &m_pVkSurface ) )
{
- fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() );
+ fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError () );
return false;
}
@@ -277,6 +289,10 @@ namespace gamescope
{
return m_HDRInfo;
}
+ bool CSDLConnector::IsVRRActive() const
+ {
+ return false;
+ }
std::span CSDLConnector::GetModes() const
{
return std::span{};
@@ -317,6 +333,61 @@ namespace gamescope
}
}
+ int CSDLConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync )
+ {
+ // TODO: Resolve const crap
+ std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false );
+ if ( !oCompositeResult )
+ return -EINVAL;
+
+ vulkan_present_to_window();
+
+ // TODO: Hook up PresentationFeedback.
+
+ // Wait for the composite result on our side *after* we
+ // commit the buffer to the compositor to avoid a bubble.
+ vulkan_wait( *oCompositeResult, true );
+
+ GetVBlankTimer().UpdateWasCompositing( true );
+ GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime );
+
+ return 0;
+ }
+
+ void CSDLConnector::SetCursorImage( std::shared_ptr info )
+ {
+ CSDLBackend *pBackend = static_cast( GetBackend() );
+ pBackend->SetCursorImage( std::move( info ) );
+ }
+ void CSDLConnector::SetRelativeMouseMode( bool bRelative )
+ {
+ CSDLBackend *pBackend = static_cast( GetBackend() );
+ pBackend->SetRelativeMouseMode( bRelative );
+ }
+ void CSDLConnector::SetVisible( bool bVisible )
+ {
+ CSDLBackend *pBackend = static_cast( GetBackend() );
+ pBackend->SetVisible( bVisible );
+ }
+ void CSDLConnector::SetTitle( std::shared_ptr szTitle )
+ {
+ CSDLBackend *pBackend = static_cast( GetBackend() );
+ pBackend->SetTitle( std::move( szTitle ) );
+ }
+ void CSDLConnector::SetIcon( std::shared_ptr> uIconPixels )
+ {
+ CSDLBackend *pBackend = static_cast( GetBackend() );
+ pBackend->SetIcon( std::move( uIconPixels ) );
+ }
+
+ void CSDLConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection )
+ {
+ if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD)
+ SDL_SetClipboardText(szContents->c_str());
+ else if (eSelection == GAMESCOPE_SELECTION_PRIMARY)
+ SDL_SetPrimarySelectionText(szContents->c_str());
+ }
+
////////////////
// CSDLBackend
////////////////
@@ -364,26 +435,6 @@ namespace gamescope
return true;
}
- int CSDLBackend::Present( const FrameInfo_t *pFrameInfo, bool bAsync )
- {
- // TODO: Resolve const crap
- std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false );
- if ( !oCompositeResult )
- return -EINVAL;
-
- vulkan_present_to_window();
-
- // TODO: Hook up PresentationFeedback.
-
- // Wait for the composite result on our side *after* we
- // commit the buffer to the compositor to avoid a bubble.
- vulkan_wait( *oCompositeResult, true );
-
- GetVBlankTimer().UpdateWasCompositing( true );
- GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime );
-
- return 0;
- }
void CSDLBackend::DirtyState( bool bForce, bool bForceModeset )
{
}
@@ -422,10 +473,6 @@ namespace gamescope
return nullptr;
}
- bool CSDLBackend::IsVRRActive() const
- {
- return false;
- }
bool CSDLBackend::SupportsPlaneHardwareCursor() const
{
@@ -464,11 +511,6 @@ namespace gamescope
return uvecSize;
}
- INestedHints *CSDLBackend::GetNestedHints()
- {
- return this;
- }
-
///////////////////
// INestedHints
///////////////////
@@ -498,6 +540,7 @@ namespace gamescope
m_pApplicationIcon = uIconPixels;
PushUserEvent( GAMESCOPE_SDL_EVENT_ICON );
}
+
void CSDLBackend::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection )
{
if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD)
@@ -505,10 +548,6 @@ namespace gamescope
else if (eSelection == GAMESCOPE_SELECTION_PRIMARY)
SDL_SetPrimarySelectionText(szContents->c_str());
}
- std::shared_ptr CSDLBackend::GetHostCursor()
- {
- return GetX11HostCursor();
- }
void CSDLBackend::OnBackendBlobDestroyed( BackendBlob *pBlob )
{
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index 08af8bca1b..ec28d28153 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -99,58 +99,6 @@ namespace gamescope
class CWaylandBackend;
class CWaylandFb;
- class CWaylandConnector final : public IBackendConnector
- {
- public:
- CWaylandConnector( CWaylandBackend *pBackend );
- bool UpdateEdid();
-
- virtual ~CWaylandConnector();
-
- /////////////////////
- // IBackendConnector
- /////////////////////
-
- virtual gamescope::GamescopeScreenType GetScreenType() const override;
- virtual GamescopePanelOrientation GetCurrentOrientation() const override;
- virtual bool SupportsHDR() const override;
- virtual bool IsHDRActive() const override;
- virtual const BackendConnectorHDRInfo &GetHDRInfo() const override;
- virtual std::span GetModes() const override;
-
- virtual bool SupportsVRR() const override;
-
- virtual std::span GetRawEDID() const override;
- virtual std::span GetValidDynamicRefreshRates() const override;
-
- virtual void GetNativeColorimetry(
- bool bHDR10,
- displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF,
- displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override;
-
- virtual const char *GetName() const override
- {
- return "Wayland";
- }
- virtual const char *GetMake() const override
- {
- return "Gamescope";
- }
- virtual const char *GetModel() const override
- {
- return "Virtual Display";
- }
- private:
-
- friend CWaylandPlane;
-
- BackendConnectorHDRInfo m_HDRInfo{};
- displaycolorimetry_t m_DisplayColorimetry = displaycolorimetry_709;
- std::vector m_FakeEdid;
-
- CWaylandBackend *m_pBackend = nullptr;
- };
-
struct WaylandPlaneState
{
wl_buffer *pBuffer;
@@ -185,7 +133,7 @@ namespace gamescope
class CWaylandPlane
{
public:
- CWaylandPlane( CWaylandBackend *pBackend );
+ CWaylandPlane( CWaylandConnector *pBackend );
~CWaylandPlane();
bool Init( CWaylandPlane *pParent, CWaylandPlane *pSiblingBelow );
@@ -258,16 +206,17 @@ namespace gamescope
void Wayland_FractionalScale_PreferredScale( wp_fractional_scale_v1 *pFractionalScale, uint32_t uScale );
static const wp_fractional_scale_v1_listener s_FractionalScaleListener;
+ CWaylandConnector *m_pConnector = nullptr;
CWaylandBackend *m_pBackend = nullptr;
CWaylandPlane *m_pParent = nullptr;
wl_surface *m_pSurface = nullptr;
wp_viewport *m_pViewport = nullptr;
- libdecor_frame *m_pFrame = nullptr;
- wl_subsurface *m_pSubsurface = nullptr;
frog_color_managed_surface *m_pFrogColorManagedSurface = nullptr;
xx_color_management_surface_v3 *m_pXXColorManagedSurface = nullptr;
wp_fractional_scale_v1 *m_pFractionalScale = nullptr;
+ wl_subsurface *m_pSubsurface = nullptr;
+ libdecor_frame *m_pFrame = nullptr;
libdecor_window_state m_eWindowState = LIBDECOR_WINDOW_STATE_NONE;
std::vector m_pOutputs;
bool m_bNeedsDecorCommit = false;
@@ -349,6 +298,93 @@ namespace gamescope
int32_t nScale = 1;
};
+
+ class CWaylandConnector final : public CBaseBackendConnector, public INestedHints
+ {
+ public:
+ CWaylandConnector( CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey );
+ virtual ~CWaylandConnector();
+
+ bool UpdateEdid();
+ bool Init();
+ void SetFullscreen( bool bFullscreen ); // Thread safe, can be called from the input thread.
+ void UpdateFullscreenState();
+
+
+ bool HostCompositorIsCurrentlyVRR() const { return m_bHostCompositorIsCurrentlyVRR; }
+ void SetHostCompositorIsCurrentlyVRR( bool bActive ) { m_bHostCompositorIsCurrentlyVRR = bActive; }
+ bool CurrentDisplaySupportsVRR() const { return HostCompositorIsCurrentlyVRR(); }
+ CWaylandBackend *GetBackend() const { return m_pBackend; }
+
+ /////////////////////
+ // IBackendConnector
+ /////////////////////
+
+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override;
+
+ virtual gamescope::GamescopeScreenType GetScreenType() const override;
+ virtual GamescopePanelOrientation GetCurrentOrientation() const override;
+ virtual bool SupportsHDR() const override;
+ virtual bool IsHDRActive() const override;
+ virtual const BackendConnectorHDRInfo &GetHDRInfo() const override;
+ virtual bool IsVRRActive() const override;
+ virtual std::span GetModes() const override;
+
+ virtual bool SupportsVRR() const override;
+
+ virtual std::span GetRawEDID() const override;
+ virtual std::span GetValidDynamicRefreshRates() const override;
+
+ virtual void GetNativeColorimetry(
+ bool bHDR10,
+ displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF,
+ displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override;
+
+ virtual const char *GetName() const override
+ {
+ return "Wayland";
+ }
+ virtual const char *GetMake() const override
+ {
+ return "Gamescope";
+ }
+ virtual const char *GetModel() const override
+ {
+ return "Virtual Display";
+ }
+
+ virtual INestedHints *GetNestedHints() override
+ {
+ return this;
+ }
+
+ ///////////////////
+ // INestedHints
+ ///////////////////
+
+ virtual void SetCursorImage( std::shared_ptr info ) override;
+ virtual void SetRelativeMouseMode( bool bRelative ) override;
+ virtual void SetVisible( bool bVisible ) override;
+ virtual void SetTitle( std::shared_ptr szTitle ) override;
+ virtual void SetIcon( std::shared_ptr> uIconPixels ) override;
+ virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override;
+ private:
+
+ friend CWaylandPlane;
+
+ BackendConnectorHDRInfo m_HDRInfo{};
+ displaycolorimetry_t m_DisplayColorimetry = displaycolorimetry_709;
+ std::vector m_FakeEdid;
+
+ CWaylandBackend *m_pBackend = nullptr;
+
+ CWaylandPlane m_Planes[8];
+ bool m_bVisible = true;
+ std::atomic m_bDesiredFullscreenState = { false };
+
+ bool m_bHostCompositorIsCurrentlyVRR = false;
+ };
+
class CWaylandFb final : public CBaseBackendFb
{
public:
@@ -505,7 +541,7 @@ namespace gamescope
.relative_motion = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_RelativePointer_RelativeMotion ),
};
- class CWaylandBackend : public CBaseBackend, public INestedHints
+ class CWaylandBackend : public CBaseBackend
{
public:
CWaylandBackend();
@@ -522,7 +558,6 @@ namespace gamescope
virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override;
virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override;
- virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override;
virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override;
virtual bool PollState() override;
@@ -535,7 +570,6 @@ namespace gamescope
virtual IBackendConnector *GetCurrentConnector() override;
virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override;
- virtual bool IsVRRActive() const override;
virtual bool SupportsPlaneHardwareCursor() const override;
virtual bool SupportsTearing() const override;
@@ -549,19 +583,8 @@ namespace gamescope
virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override;
virtual void HackUpdatePatchedEdid() override;
- virtual INestedHints *GetNestedHints() override;
-
- ///////////////////
- // INestedHints
- ///////////////////
-
- virtual void SetCursorImage( std::shared_ptr info ) override;
- virtual void SetRelativeMouseMode( bool bRelative ) override;
- virtual void SetVisible( bool bVisible ) override;
- virtual void SetTitle( std::shared_ptr szTitle ) override;
- virtual void SetIcon( std::shared_ptr> uIconPixels ) override;
- virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override;
- virtual std::shared_ptr GetHostCursor() override;
+ virtual bool UsesVirtualConnectors() override;
+ virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override;
protected:
virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override;
@@ -590,12 +613,8 @@ namespace gamescope
xdg_toplevel_icon_manager_v1 *GetToplevelIconManager() const { return m_pToplevelIconManager; }
libdecor *GetLibDecor() const { return m_pLibDecor; }
- void SetFullscreen( bool bFullscreen ); // Thread safe, can be called from the input thread.
void UpdateFullscreenState();
- bool HostCompositorIsCurrentlyVRR() const { return m_bHostCompositorIsCurrentlyVRR; }
- void SetHostCompositorIsCurrentlyVRR( bool bActive ) { m_bHostCompositorIsCurrentlyVRR = bActive; }
-
WaylandOutputInfo *GetOutputInfo( wl_output *pOutput )
{
auto iter = m_pOutputs.find( pOutput );
@@ -605,8 +624,8 @@ namespace gamescope
return &iter->second;
}
- bool CurrentDisplaySupportsVRR() const { return HostCompositorIsCurrentlyVRR(); }
wl_region *GetFullRegion() const { return m_pFullRegion; }
+ CWaylandFb *GetBlackFb() const { return m_BlackFb.get(); }
private:
@@ -642,9 +661,6 @@ namespace gamescope
CWaylandInputThread m_InputThread;
- CWaylandConnector m_Connector;
- CWaylandPlane m_Planes[8];
-
wl_display *m_pDisplay = nullptr;
wl_shm *m_pShm = nullptr;
wl_compositor *m_pCompositor = nullptr;
@@ -665,6 +681,9 @@ namespace gamescope
wp_fractional_scale_manager_v1 *m_pFractionalScaleManager = nullptr;
xdg_toplevel_icon_manager_v1 *m_pToplevelIconManager = nullptr;
+ // TODO: Restructure and remove the need for this.
+ std::shared_ptr m_pFocusConnector;
+
struct
{
std::vector ePrimaries;
@@ -698,11 +717,6 @@ namespace gamescope
wl_surface *m_pCursorSurface = nullptr;
std::shared_ptr m_pDefaultCursorInfo;
wl_surface *m_pDefaultCursorSurface = nullptr;
-
- bool m_bVisible = true;
- std::atomic m_bDesiredFullscreenState = { false };
-
- bool m_bHostCompositorIsCurrentlyVRR = false;
};
const wl_registry_listener CWaylandBackend::s_RegistryListener =
{
@@ -813,8 +827,10 @@ namespace gamescope
// CWaylandConnector
//////////////////
- CWaylandConnector::CWaylandConnector( CWaylandBackend *pBackend )
- : m_pBackend( pBackend )
+ CWaylandConnector::CWaylandConnector( CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey )
+ : CBaseBackendConnector{ ulVirtualConnectorKey }
+ , m_pBackend( pBackend )
+ , m_Planes{ this, this, this, this, this, this, this, this }
{
m_HDRInfo.bAlwaysPatchEdid = true;
}
@@ -830,80 +846,385 @@ namespace gamescope
return true;
}
- GamescopeScreenType CWaylandConnector::GetScreenType() const
- {
- return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL;
- }
- GamescopePanelOrientation CWaylandConnector::GetCurrentOrientation() const
- {
- return GAMESCOPE_PANEL_ORIENTATION_0;
- }
- bool CWaylandConnector::SupportsHDR() const
- {
- return GetHDRInfo().IsHDR10();
- }
- bool CWaylandConnector::IsHDRActive() const
- {
- // XXX: blah
- return false;
- }
- const BackendConnectorHDRInfo &CWaylandConnector::GetHDRInfo() const
- {
- return m_HDRInfo;
- }
- std::span CWaylandConnector::GetModes() const
+ bool CWaylandConnector::Init()
{
- return std::span{};
- }
+ for ( uint32_t i = 0; i < 8; i++ )
+ {
+ bool bSuccess = m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] );
+ if ( !bSuccess )
+ return false;
+ }
- bool CWaylandConnector::SupportsVRR() const
- {
- return m_pBackend->CurrentDisplaySupportsVRR();
+ if ( g_bFullscreen )
+ {
+ m_bDesiredFullscreenState = true;
+ g_bFullscreen = false;
+ UpdateFullscreenState();
+ }
+
+ UpdateEdid();
+ m_pBackend->HackUpdatePatchedEdid();
+
+ if ( g_bForceRelativeMouse )
+ this->SetRelativeMouseMode( true );
+
+ return true;
}
- std::span CWaylandConnector::GetRawEDID() const
+ void CWaylandConnector::SetFullscreen( bool bFullscreen )
{
- return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() };
+ m_bDesiredFullscreenState = bFullscreen;
}
- std::span CWaylandConnector::GetValidDynamicRefreshRates() const
+
+ void CWaylandConnector::UpdateFullscreenState()
{
- return std::span{};
+ if ( !m_bVisible )
+ g_bFullscreen = false;
+
+ if ( m_bDesiredFullscreenState != g_bFullscreen && m_bVisible )
+ {
+ if ( m_bDesiredFullscreenState )
+ libdecor_frame_set_fullscreen( m_Planes[0].GetFrame(), nullptr );
+ else
+ libdecor_frame_unset_fullscreen( m_Planes[0].GetFrame() );
+
+ g_bFullscreen = m_bDesiredFullscreenState;
+ }
}
- void CWaylandConnector::GetNativeColorimetry(
- bool bHDR10,
- displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF,
- displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const
+ int CWaylandConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync )
{
- *displayColorimetry = m_DisplayColorimetry;
- *displayEOTF = EOTF_Gamma22;
+ UpdateFullscreenState();
- if ( bHDR10 && GetHDRInfo().IsHDR10() )
+ bool bNeedsFullComposite = false;
+
+ if ( !m_bVisible )
{
- // For HDR10 output, expected content colorspace != native colorspace.
- *outputEncodingColorimetry = displaycolorimetry_2020;
- *outputEncodingEOTF = GetHDRInfo().eOutputEncodingEOTF;
+ uint32_t uCurrentPlane = 0;
+ for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ )
+ m_Planes[uCurrentPlane++].Present( nullptr );
}
else
{
- // We always use default 'perceptual' intent, so
- // this should be correct for SDR content.
- *outputEncodingColorimetry = m_DisplayColorimetry;
- *outputEncodingEOTF = EOTF_Gamma22;
- }
- }
-
- //////////////////
- // CWaylandPlane
- //////////////////
+ // TODO: Dedupe some of this composite check code between us and drm.cpp
+ bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f);
- CWaylandPlane::CWaylandPlane( CWaylandBackend *pBackend )
- : m_pBackend( pBackend )
- {
- }
+ bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize;
- CWaylandPlane::~CWaylandPlane()
- {
+ bNeedsFullComposite |= cv_composite_force;
+ bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
+ bNeedsFullComposite |= pFrameInfo->useNISLayer0;
+ bNeedsFullComposite |= pFrameInfo->blurLayer0;
+ bNeedsFullComposite |= bNeedsCompositeFromFilter;
+ bNeedsFullComposite |= g_bColorSliderInUse;
+ bNeedsFullComposite |= pFrameInfo->bFadingOut;
+ bNeedsFullComposite |= !g_reshade_effect.empty();
+
+ if ( g_bOutputHDREnabled )
+ bNeedsFullComposite |= g_bHDRItmEnable;
+
+ if ( !m_pBackend->SupportsColorManagement() )
+ bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace );
+
+ bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap);
+
+ if ( !bNeedsFullComposite )
+ {
+ bool bNeedsBacking = true;
+ if ( pFrameInfo->layerCount >= 1 )
+ {
+ if ( pFrameInfo->layers[0].isScreenSize() && !pFrameInfo->layers[0].hasAlpha() )
+ bNeedsBacking = false;
+ }
+
+ uint32_t uCurrentPlane = 0;
+ if ( bNeedsBacking )
+ {
+ m_pBackend->GetBlackFb()->OnCompositorAcquire();
+
+ CWaylandPlane *pPlane = &m_Planes[uCurrentPlane++];
+ pPlane->Present(
+ WaylandPlaneState
+ {
+ .pBuffer = m_pBackend->GetBlackFb()->GetHostBuffer(),
+ .flSrcWidth = 1.0,
+ .flSrcHeight = 1.0,
+ .nDstWidth = int32_t( g_nOutputWidth ),
+ .nDstHeight = int32_t( g_nOutputHeight ),
+ .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU,
+ .bOpaque = true,
+ .uFractionalScale = pPlane->GetScale(),
+ } );
+ }
+
+ for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ )
+ m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr );
+ }
+ else
+ {
+ std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false );
+
+ if ( !oCompositeResult )
+ {
+ xdg_log.errorf( "vulkan_composite failed" );
+ return -EINVAL;
+ }
+
+ vulkan_wait( *oCompositeResult, true );
+
+ FrameInfo_t::Layer_t compositeLayer{};
+ compositeLayer.scale.x = 1.0;
+ compositeLayer.scale.y = 1.0;
+ compositeLayer.opacity = 1.0;
+ compositeLayer.zpos = g_zposBase;
+
+ compositeLayer.tex = vulkan_get_last_output_image( false, false );
+ compositeLayer.applyColorMgmt = false;
+
+ compositeLayer.filter = GamescopeUpscaleFilter::NEAREST;
+ compositeLayer.ctm = nullptr;
+ compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB;
+
+ m_Planes[0].Present( &compositeLayer );
+
+ for ( int i = 1; i < 8; i++ )
+ m_Planes[i].Present( nullptr );
+ }
+ }
+
+ for ( int i = 7; i >= 0; i-- )
+ m_Planes[i].Commit();
+
+ wl_display_flush( m_pBackend->GetDisplay() );
+
+ GetVBlankTimer().UpdateWasCompositing( bNeedsFullComposite );
+ GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime );
+
+ m_pBackend->PollState();
+
+ return 0;
+ }
+
+ GamescopeScreenType CWaylandConnector::GetScreenType() const
+ {
+ return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL;
+ }
+ GamescopePanelOrientation CWaylandConnector::GetCurrentOrientation() const
+ {
+ return GAMESCOPE_PANEL_ORIENTATION_0;
+ }
+ bool CWaylandConnector::SupportsHDR() const
+ {
+ return GetHDRInfo().IsHDR10();
+ }
+ bool CWaylandConnector::IsHDRActive() const
+ {
+ // XXX: blah
+ return false;
+ }
+ const BackendConnectorHDRInfo &CWaylandConnector::GetHDRInfo() const
+ {
+ return m_HDRInfo;
+ }
+ bool CWaylandConnector::IsVRRActive() const
+ {
+ return cv_adaptive_sync && m_bHostCompositorIsCurrentlyVRR;
+ }
+ std::span CWaylandConnector::GetModes() const
+ {
+ return std::span{};
+ }
+
+ bool CWaylandConnector::SupportsVRR() const
+ {
+ return CurrentDisplaySupportsVRR();
+ }
+
+ std::span CWaylandConnector::GetRawEDID() const
+ {
+ return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() };
+ }
+ std::span CWaylandConnector::GetValidDynamicRefreshRates() const
+ {
+ return std::span{};
+ }
+
+ void CWaylandConnector::GetNativeColorimetry(
+ bool bHDR10,
+ displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF,
+ displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const
+ {
+ *displayColorimetry = m_DisplayColorimetry;
+ *displayEOTF = EOTF_Gamma22;
+
+ if ( bHDR10 && GetHDRInfo().IsHDR10() )
+ {
+ // For HDR10 output, expected content colorspace != native colorspace.
+ *outputEncodingColorimetry = displaycolorimetry_2020;
+ *outputEncodingEOTF = GetHDRInfo().eOutputEncodingEOTF;
+ }
+ else
+ {
+ // We always use default 'perceptual' intent, so
+ // this should be correct for SDR content.
+ *outputEncodingColorimetry = m_DisplayColorimetry;
+ *outputEncodingEOTF = EOTF_Gamma22;
+ }
+ }
+
+
+ void CWaylandConnector::SetCursorImage( std::shared_ptr info )
+ {
+ // XXX(strategy): FIXME FIXME FIXME
+#if 0
+ m_pCursorInfo = info;
+
+ if ( m_pCursorSurface )
+ {
+ wl_surface_destroy( m_pCursorSurface );
+ m_pCursorSurface = nullptr;
+ }
+
+ m_pCursorSurface = CursorInfoToSurface( info );
+
+ UpdateCursor();
+#endif
+ }
+ void CWaylandConnector::SetRelativeMouseMode( bool bRelative )
+ {
+ // XXX(strategy): FIXME FIXME FIXME
+#if 0
+ if ( !m_pPointer )
+ return;
+
+ if ( !!bRelative != !!m_pLockedPointer )
+ {
+ if ( m_pLockedPointer )
+ {
+ assert( m_pRelativePointer );
+
+ zwp_locked_pointer_v1_destroy( m_pLockedPointer );
+ m_pLockedPointer = nullptr;
+
+ zwp_relative_pointer_v1_destroy( m_pRelativePointer );
+ m_pRelativePointer = nullptr;
+ }
+ else
+ {
+ assert( !m_pRelativePointer );
+
+ m_pLockedPointer = zwp_pointer_constraints_v1_lock_pointer( m_pPointerConstraints, m_Planes[0].GetSurface(), m_pPointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT );
+ m_pRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( m_pRelativePointerManager, m_pPointer );
+ }
+
+ m_InputThread.SetRelativePointer( bRelative );
+
+ UpdateCursor();
+ }
+#endif
+ }
+ void CWaylandConnector::SetVisible( bool bVisible )
+ {
+ if ( m_bVisible == bVisible )
+ return;
+
+ m_bVisible = bVisible;
+ force_repaint();
+ }
+ void CWaylandConnector::SetTitle( std::shared_ptr pAppTitle )
+ {
+ std::string szTitle = pAppTitle ? *pAppTitle : "gamescope";
+ if ( g_bGrabbed )
+ szTitle += " (grabbed)";
+ libdecor_frame_set_title( m_Planes[0].GetFrame(), szTitle.c_str() );
+ }
+ void CWaylandConnector::SetIcon( std::shared_ptr> uIconPixels )
+ {
+ // XXX(strategy): FIXME FIXME FIXME
+#if 0
+ if ( !m_pToplevelIconManager )
+ return;
+
+ if ( uIconPixels && uIconPixels->size() >= 3 )
+ {
+ xdg_toplevel_icon_v1 *pIcon = xdg_toplevel_icon_manager_v1_create_icon( m_pToplevelIconManager );
+ if ( !pIcon )
+ {
+ xdg_log.errorf( "Failed to create xdg_toplevel_icon_v1" );
+ return;
+ }
+ defer( xdg_toplevel_icon_v1_destroy( pIcon ) );
+
+ const uint32_t uWidth = ( *uIconPixels )[0];
+ const uint32_t uHeight = ( *uIconPixels )[1];
+
+ const uint32_t uStride = uWidth * 4;
+ const uint32_t uSize = uStride * uHeight;
+ int32_t nFd = CreateShmBuffer( uSize, &( *uIconPixels )[2] );
+ if ( nFd < 0 )
+ {
+ xdg_log.errorf( "Failed to create/map shm buffer" );
+ return;
+ }
+ defer( close( nFd ) );
+
+ wl_shm_pool *pPool = wl_shm_create_pool( m_pShm, nFd, uSize );
+ defer( wl_shm_pool_destroy( pPool ) );
+
+ wl_buffer *pBuffer = wl_shm_pool_create_buffer( pPool, 0, uWidth, uHeight, uStride, WL_SHM_FORMAT_ARGB8888 );
+ defer( wl_buffer_destroy( pBuffer ) );
+
+ xdg_toplevel_icon_v1_add_buffer( pIcon, pBuffer, 1 );
+
+ xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), pIcon );
+ }
+ else
+ {
+ xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), nullptr );
+ }
+#endif
+ }
+
+ void CWaylandConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection )
+ {
+ // Do nothing
+ }
+
+ //////////////////
+ // CWaylandPlane
+ //////////////////
+
+ CWaylandPlane::CWaylandPlane( CWaylandConnector *pConnector )
+ : m_pConnector{ pConnector }
+ , m_pBackend{ pConnector->GetBackend() }
+ {
+ }
+
+ CWaylandPlane::~CWaylandPlane()
+ {
+ std::scoped_lock lock{ m_PlaneStateLock };
+
+ m_eWindowState = LIBDECOR_WINDOW_STATE_NONE;
+ m_pOutputs.clear();
+ m_bNeedsDecorCommit = false;
+
+ m_oCurrentPlaneState = std::nullopt;
+
+ if ( m_pFrame )
+ libdecor_frame_unref( m_pFrame ); // Ew.
+
+ if ( m_pSubsurface )
+ wl_subsurface_destroy( m_pSubsurface );
+ if ( m_pFractionalScale )
+ wp_fractional_scale_v1_destroy( m_pFractionalScale );
+ if ( m_pXXColorManagedSurface )
+ xx_color_management_surface_v3_destroy( m_pXXColorManagedSurface );
+ if ( m_pFrogColorManagedSurface )
+ frog_color_managed_surface_destroy( m_pFrogColorManagedSurface );
+ if ( m_pViewport )
+ wp_viewport_destroy( m_pViewport );
+ if ( m_pSurface )
+ wl_surface_destroy( m_pSurface );
}
bool CWaylandPlane::Init( CWaylandPlane *pParent, CWaylandPlane *pSiblingBelow )
@@ -1122,7 +1443,7 @@ namespace gamescope
if ( m_pParent )
return;
- if ( !m_pBackend->HostCompositorIsCurrentlyVRR() )
+ if ( !m_pConnector->HostCompositorIsCurrentlyVRR() )
return;
if ( m_pOutputs.empty() )
@@ -1140,6 +1461,7 @@ namespace gamescope
if ( nLargestRefreshRateMhz && nLargestRefreshRateMhz != g_nOutputRefresh )
{
+ // TODO(strategy): We should pick the largest refresh rate.
xdg_log.infof( "Changed refresh to: %.3fhz", ConvertmHzToHz( (float) nLargestRefreshRateMhz ) );
g_nOutputRefresh = nLargestRefreshRateMhz;
}
@@ -1174,6 +1496,10 @@ namespace gamescope
int nWidth, nHeight;
if ( !libdecor_configuration_get_content_size( pConfiguration, m_pFrame, &nWidth, &nHeight ) )
{
+ // XXX(virtual connector): Move g_nOutputWidth etc to connector.
+ // Right now we are doubling this up when we should not be.
+ //
+ // Which is causing problems.
nWidth = WaylandScaleToLogical( g_nOutputWidth, uScale );
nHeight = WaylandScaleToLogical( g_nOutputHeight, uScale );
}
@@ -1214,11 +1540,11 @@ namespace gamescope
g_nOutputRefresh = nRefresh;
}
- m_pBackend->SetHostCompositorIsCurrentlyVRR( false );
+ m_pConnector->SetHostCompositorIsCurrentlyVRR( false );
}
else
{
- m_pBackend->SetHostCompositorIsCurrentlyVRR( true );
+ m_pConnector->SetHostCompositorIsCurrentlyVRR( true );
UpdateVRRRefreshRate();
}
@@ -1252,14 +1578,14 @@ namespace gamescope
uint32_t uMinLuminance,
uint32_t uMaxFullFrameLuminance )
{
- auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo;
+ auto *pHDRInfo = &m_pConnector->m_HDRInfo;
pHDRInfo->bExposeHDRSupport = ( cv_hdr_enabled && uTransferFunction == FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ );
pHDRInfo->eOutputEncodingEOTF = ( cv_hdr_enabled && uTransferFunction == FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ) ? EOTF_PQ : EOTF_Gamma22;
pHDRInfo->uMaxContentLightLevel = uMaxLuminance;
pHDRInfo->uMaxFrameAverageLuminance = uMaxFullFrameLuminance;
pHDRInfo->uMinContentLightLevel = uMinLuminance;
- auto *pDisplayColorimetry = &m_pBackend->m_Connector.m_DisplayColorimetry;
+ auto *pDisplayColorimetry = &m_pConnector->m_DisplayColorimetry;
pDisplayColorimetry->primaries.r = glm::vec2{ uOutputDisplayPrimaryRedX * 0.00002f, uOutputDisplayPrimaryRedY * 0.00002f };
pDisplayColorimetry->primaries.g = glm::vec2{ uOutputDisplayPrimaryGreenX * 0.00002f, uOutputDisplayPrimaryGreenY * 0.00002f };
pDisplayColorimetry->primaries.b = glm::vec2{ uOutputDisplayPrimaryBlueX * 0.00002f, uOutputDisplayPrimaryBlueY * 0.00002f };
@@ -1323,7 +1649,7 @@ namespace gamescope
}
void CWaylandPlane::Wayland_XXImageDescriptionInfo_TFNamed( xx_image_description_info_v3 *pImageDescInfo, uint32_t uTF)
{
- auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo;
+ auto *pHDRInfo = &m_pConnector->m_HDRInfo;
pHDRInfo->bExposeHDRSupport = ( cv_hdr_enabled && uTF == XX_COLOR_MANAGER_V3_TRANSFER_FUNCTION_ST2084_PQ );
pHDRInfo->eOutputEncodingEOTF = ( cv_hdr_enabled && uTF == XX_COLOR_MANAGER_V3_TRANSFER_FUNCTION_ST2084_PQ ) ? EOTF_PQ : EOTF_Gamma22;
}
@@ -1333,7 +1659,7 @@ namespace gamescope
}
void CWaylandPlane::Wayland_XXImageDescriptionInfo_TargetPrimaries( xx_image_description_info_v3 *pImageDescInfo, int32_t nRedX, int32_t nRedY, int32_t nGreenX, int32_t nGreenY, int32_t nBlueX, int32_t nBlueY, int32_t nWhiteX, int32_t nWhiteY )
{
- auto *pDisplayColorimetry = &m_pBackend->m_Connector.m_DisplayColorimetry;
+ auto *pDisplayColorimetry = &m_pConnector->m_DisplayColorimetry;
pDisplayColorimetry->primaries.r = glm::vec2{ nRedX / 10000.0f, nRedY / 10000.0f };
pDisplayColorimetry->primaries.g = glm::vec2{ nGreenX / 10000.0f, nGreenY / 10000.0f };
pDisplayColorimetry->primaries.b = glm::vec2{ nBlueX / 10000.0f, nBlueY / 10000.0f };
@@ -1345,12 +1671,12 @@ namespace gamescope
}
void CWaylandPlane::Wayland_XXImageDescriptionInfo_Target_MaxCLL( xx_image_description_info_v3 *pImageDescInfo, uint32_t uMaxCLL )
{
- auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo;
+ auto *pHDRInfo = &m_pConnector->m_HDRInfo;
pHDRInfo->uMaxContentLightLevel = uMaxCLL;
}
void CWaylandPlane::Wayland_XXImageDescriptionInfo_Target_MaxFALL( xx_image_description_info_v3 *pImageDescInfo, uint32_t uMaxFALL )
{
- auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo;
+ auto *pHDRInfo = &m_pConnector->m_HDRInfo;
pHDRInfo->uMaxFrameAverageLuminance = uMaxFALL;
}
@@ -1382,8 +1708,6 @@ namespace gamescope
};
CWaylandBackend::CWaylandBackend()
- : m_Connector( this )
- , m_Planes{ this, this, this, this, this, this, this, this }
{
}
@@ -1497,29 +1821,11 @@ namespace gamescope
return true;
}
- bool CWaylandBackend::PostInit()
- {
- m_pFullRegion = wl_compositor_create_region( m_pCompositor );
- wl_region_add( m_pFullRegion, 0, 0, INT32_MAX, INT32_MAX );
-
- for ( uint32_t i = 0; i < 8; i++ )
- m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] );
-
- m_Connector.UpdateEdid();
- this->HackUpdatePatchedEdid();
-
- if ( cv_hdr_enabled && m_Connector.GetHDRInfo().bExposeHDRSupport )
- {
- setenv( "DXVK_HDR", "1", false );
- }
-
- if ( g_bFullscreen )
- {
- m_bDesiredFullscreenState = true;
- g_bFullscreen = false;
- UpdateFullscreenState();
- }
-
+ bool CWaylandBackend::PostInit()
+ {
+ m_pFullRegion = wl_compositor_create_region( m_pCompositor );
+ wl_region_add( m_pFullRegion, 0, 0, INT32_MAX, INT32_MAX );
+
if ( m_pSinglePixelBufferManager )
{
wl_buffer *pBlackBuffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( m_pSinglePixelBufferManager, 0, 0, 0, ~0u );
@@ -1546,9 +1852,6 @@ namespace gamescope
m_pDefaultCursorInfo = GetX11HostCursor();
m_pDefaultCursorSurface = CursorInfoToSurface( m_pDefaultCursorInfo );
- if ( g_bForceRelativeMouse )
- this->SetRelativeMouseMode( true );
-
return true;
}
@@ -1602,118 +1905,6 @@ namespace gamescope
return true;
}
- int CWaylandBackend::Present( const FrameInfo_t *pFrameInfo, bool bAsync )
- {
- UpdateFullscreenState();
-
- bool bNeedsFullComposite = false;
-
- if ( !m_bVisible )
- {
- uint32_t uCurrentPlane = 0;
- for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ )
- m_Planes[uCurrentPlane++].Present( nullptr );
- }
- else
- {
- // TODO: Dedupe some of this composite check code between us and drm.cpp
- bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f);
-
- bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize;
-
- bNeedsFullComposite |= cv_composite_force;
- bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
- bNeedsFullComposite |= pFrameInfo->useNISLayer0;
- bNeedsFullComposite |= pFrameInfo->blurLayer0;
- bNeedsFullComposite |= bNeedsCompositeFromFilter;
- bNeedsFullComposite |= g_bColorSliderInUse;
- bNeedsFullComposite |= pFrameInfo->bFadingOut;
- bNeedsFullComposite |= !g_reshade_effect.empty();
-
- if ( g_bOutputHDREnabled )
- bNeedsFullComposite |= g_bHDRItmEnable;
-
- if ( !SupportsColorManagement() )
- bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace );
-
- bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap);
-
- if ( !bNeedsFullComposite )
- {
- bool bNeedsBacking = true;
- if ( pFrameInfo->layerCount >= 1 )
- {
- if ( pFrameInfo->layers[0].isScreenSize() && !pFrameInfo->layers[0].hasAlpha() )
- bNeedsBacking = false;
- }
-
- uint32_t uCurrentPlane = 0;
- if ( bNeedsBacking )
- {
- m_BlackFb->OnCompositorAcquire();
-
- CWaylandPlane *pPlane = &m_Planes[uCurrentPlane++];
- pPlane->Present(
- WaylandPlaneState
- {
- .pBuffer = m_BlackFb->GetHostBuffer(),
- .flSrcWidth = 1.0,
- .flSrcHeight = 1.0,
- .nDstWidth = int32_t( g_nOutputWidth ),
- .nDstHeight = int32_t( g_nOutputHeight ),
- .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU,
- .bOpaque = true,
- .uFractionalScale = pPlane->GetScale(),
- } );
- }
-
- for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ )
- m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr );
- }
- else
- {
- std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false );
-
- if ( !oCompositeResult )
- {
- xdg_log.errorf( "vulkan_composite failed" );
- return -EINVAL;
- }
-
- vulkan_wait( *oCompositeResult, true );
-
- FrameInfo_t::Layer_t compositeLayer{};
- compositeLayer.scale.x = 1.0;
- compositeLayer.scale.y = 1.0;
- compositeLayer.opacity = 1.0;
- compositeLayer.zpos = g_zposBase;
-
- compositeLayer.tex = vulkan_get_last_output_image( false, false );
- compositeLayer.applyColorMgmt = false;
-
- compositeLayer.filter = GamescopeUpscaleFilter::NEAREST;
- compositeLayer.ctm = nullptr;
- compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB;
-
- m_Planes[0].Present( &compositeLayer );
-
- for ( int i = 1; i < 8; i++ )
- m_Planes[i].Present( nullptr );
- }
- }
-
- for ( int i = 7; i >= 0; i-- )
- m_Planes[i].Commit();
-
- wl_display_flush( m_pDisplay );
-
- GetVBlankTimer().UpdateWasCompositing( bNeedsFullComposite );
- GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime );
-
- this->PollState();
-
- return 0;
- }
void CWaylandBackend::DirtyState( bool bForce, bool bForceModeset )
{
}
@@ -1808,19 +1999,15 @@ namespace gamescope
IBackendConnector *CWaylandBackend::GetCurrentConnector()
{
- return &m_Connector;
+ return m_pFocusConnector.get();
}
IBackendConnector *CWaylandBackend::GetConnector( GamescopeScreenType eScreenType )
{
if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL )
- return &m_Connector;
+ return GetCurrentConnector();
return nullptr;
}
- bool CWaylandBackend::IsVRRActive() const
- {
- return cv_adaptive_sync && m_bHostCompositorIsCurrentlyVRR;
- }
bool CWaylandBackend::SupportsPlaneHardwareCursor() const
{
@@ -1846,7 +2033,7 @@ namespace gamescope
bool CWaylandBackend::SupportsExplicitSync() const
{
return true;
- }
+ }
bool CWaylandBackend::IsVisible() const
{
@@ -1863,12 +2050,36 @@ namespace gamescope
if ( !GetCurrentConnector() )
return;
+ // XXX: We should do this a better way that handles per-window and appid stuff
+ // down the line
+ if ( cv_hdr_enabled && GetCurrentConnector()->GetHDRInfo().bExposeHDRSupport )
+ {
+ setenv( "DXVK_HDR", "1", true );
+ }
+ else
+ {
+ setenv( "DXVK_HDR", "0", true );
+ }
+
WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), false );
}
- INestedHints *CWaylandBackend::GetNestedHints()
+ bool CWaylandBackend::UsesVirtualConnectors()
+ {
+ return true;
+ }
+ std::shared_ptr CWaylandBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey )
{
- return this;
+ std::shared_ptr pConnector = std::make_shared( this, ulVirtualConnectorKey );
+ if ( !m_pFocusConnector )
+ m_pFocusConnector = pConnector;
+
+ if ( !pConnector->Init() )
+ {
+ return nullptr;
+ }
+
+ return pConnector;
}
///////////////////
@@ -1901,118 +2112,6 @@ namespace gamescope
return nFd;
}
- void CWaylandBackend::SetCursorImage( std::shared_ptr info )
- {
- m_pCursorInfo = info;
-
- if ( m_pCursorSurface )
- {
- wl_surface_destroy( m_pCursorSurface );
- m_pCursorSurface = nullptr;
- }
-
- m_pCursorSurface = CursorInfoToSurface( info );
-
- UpdateCursor();
- }
- void CWaylandBackend::SetRelativeMouseMode( bool bRelative )
- {
- if ( !m_pPointer )
- return;
-
- if ( !!bRelative != !!m_pLockedPointer )
- {
- if ( m_pLockedPointer )
- {
- assert( m_pRelativePointer );
-
- zwp_locked_pointer_v1_destroy( m_pLockedPointer );
- m_pLockedPointer = nullptr;
-
- zwp_relative_pointer_v1_destroy( m_pRelativePointer );
- m_pRelativePointer = nullptr;
- }
- else
- {
- assert( !m_pRelativePointer );
-
- m_pLockedPointer = zwp_pointer_constraints_v1_lock_pointer( m_pPointerConstraints, m_Planes[0].GetSurface(), m_pPointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT );
- m_pRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( m_pRelativePointerManager, m_pPointer );
- }
-
- m_InputThread.SetRelativePointer( bRelative );
-
- UpdateCursor();
- }
- }
- void CWaylandBackend::SetVisible( bool bVisible )
- {
- if ( m_bVisible == bVisible )
- return;
-
- m_bVisible = bVisible;
- force_repaint();
- }
- void CWaylandBackend::SetTitle( std::shared_ptr pAppTitle )
- {
- std::string szTitle = pAppTitle ? *pAppTitle : "gamescope";
- if ( g_bGrabbed )
- szTitle += " (grabbed)";
- libdecor_frame_set_title( m_Planes[0].GetFrame(), szTitle.c_str() );
- }
- void CWaylandBackend::SetIcon( std::shared_ptr> uIconPixels )
- {
- if ( !m_pToplevelIconManager )
- return;
-
- if ( uIconPixels && uIconPixels->size() >= 3 )
- {
- xdg_toplevel_icon_v1 *pIcon = xdg_toplevel_icon_manager_v1_create_icon( m_pToplevelIconManager );
- if ( !pIcon )
- {
- xdg_log.errorf( "Failed to create xdg_toplevel_icon_v1" );
- return;
- }
- defer( xdg_toplevel_icon_v1_destroy( pIcon ) );
-
- const uint32_t uWidth = ( *uIconPixels )[0];
- const uint32_t uHeight = ( *uIconPixels )[1];
-
- const uint32_t uStride = uWidth * 4;
- const uint32_t uSize = uStride * uHeight;
- int32_t nFd = CreateShmBuffer( uSize, &( *uIconPixels )[2] );
- if ( nFd < 0 )
- {
- xdg_log.errorf( "Failed to create/map shm buffer" );
- return;
- }
- defer( close( nFd ) );
-
- wl_shm_pool *pPool = wl_shm_create_pool( m_pShm, nFd, uSize );
- defer( wl_shm_pool_destroy( pPool ) );
-
- wl_buffer *pBuffer = wl_shm_pool_create_buffer( pPool, 0, uWidth, uHeight, uStride, WL_SHM_FORMAT_ARGB8888 );
- defer( wl_buffer_destroy( pBuffer ) );
-
- xdg_toplevel_icon_v1_add_buffer( pIcon, pBuffer, 1 );
-
- xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), pIcon );
- }
- else
- {
- xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), nullptr );
- }
- }
- void CWaylandBackend::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection )
- {
- // Do nothing
- }
-
- std::shared_ptr CWaylandBackend::GetHostCursor()
- {
- return m_pDefaultCursorInfo;
- }
-
void CWaylandBackend::OnBackendBlobDestroyed( BackendBlob *pBlob )
{
// Do nothing.
@@ -2074,27 +2173,6 @@ namespace gamescope
}
}
- void CWaylandBackend::SetFullscreen( bool bFullscreen )
- {
- m_bDesiredFullscreenState = bFullscreen;
- }
-
- void CWaylandBackend::UpdateFullscreenState()
- {
- if ( !m_bVisible )
- g_bFullscreen = false;
-
- if ( m_bDesiredFullscreenState != g_bFullscreen && m_bVisible )
- {
- if ( m_bDesiredFullscreenState )
- libdecor_frame_set_fullscreen( m_Planes[0].GetFrame(), nullptr );
- else
- libdecor_frame_unset_fullscreen( m_Planes[0].GetFrame() );
-
- g_bFullscreen = m_bDesiredFullscreenState;
- }
- }
-
/////////////////////
// Wayland Callbacks
/////////////////////
@@ -2474,7 +2552,7 @@ namespace gamescope
{
if ( !bPressed )
{
- m_pBackend->SetFullscreen( !g_bFullscreen );
+ static_cast< CWaylandConnector * >( m_pBackend->GetCurrentConnector() )->SetFullscreen( !g_bFullscreen );
}
return;
}
diff --git a/src/InputEmulation.cpp b/src/InputEmulation.cpp
index 460d0b41a1..5412b01b93 100644
--- a/src/InputEmulation.cpp
+++ b/src/InputEmulation.cpp
@@ -5,6 +5,7 @@
#include
#include
+#include "backend.h"
#include "InputEmulation.h"
#include "wlserver.hpp"
@@ -167,6 +168,8 @@ namespace gamescope
case EIS_EVENT_POINTER_MOTION:
{
+ GetBackend()->NotifyPhysicalInput( InputType::Mouse );
+
wlserver_lock();
wlserver_mousemotion( eis_event_pointer_get_dx( pEisEvent ), eis_event_pointer_get_dy( pEisEvent ), ++s_uSequence );
wlserver_unlock();
@@ -175,6 +178,8 @@ namespace gamescope
case EIS_EVENT_POINTER_MOTION_ABSOLUTE:
{
+ GetBackend()->NotifyPhysicalInput( InputType::Mouse );
+
wlserver_lock();
wlserver_mousewarp( eis_event_pointer_get_absolute_x( pEisEvent ), eis_event_pointer_get_absolute_y( pEisEvent ), ++s_uSequence, true );
wlserver_unlock();
diff --git a/src/LibInputHandler.cpp b/src/LibInputHandler.cpp
new file mode 100644
index 0000000000..145a5be93d
--- /dev/null
+++ b/src/LibInputHandler.cpp
@@ -0,0 +1,204 @@
+#include "LibInputHandler.h"
+
+#include
+#include
+#include
+#include
+
+#include "log.hpp"
+#include "backend.h"
+#include "wlserver.hpp"
+#include "Utils/Defer.h"
+
+// Handles libinput in contexts where we don't have a session
+// and can't use the wlroots libinput stuff.
+//
+// eg. in VR where we want global access to the m + kb
+// without doing any seat dance.
+//
+// That may change in the future...
+// but for now, this solves that problem.
+
+namespace gamescope
+{
+ static LogScope log_input_stealer( "InputStealer" );
+
+ const libinput_interface CLibInputHandler::s_LibInputInterface =
+ {
+ .open_restricted = []( const char *pszPath, int nFlags, void *pUserData ) -> int
+ {
+ return open( pszPath, nFlags );
+ },
+
+ .close_restricted = []( int nFd, void *pUserData ) -> void
+ {
+ close( nFd );
+ },
+ };
+
+ CLibInputHandler::CLibInputHandler()
+ {
+ }
+
+ CLibInputHandler::~CLibInputHandler()
+ {
+ if ( m_pLibInput )
+ {
+ libinput_unref( m_pLibInput );
+ m_pLibInput = nullptr;
+ }
+
+ if ( m_pUdev )
+ {
+ udev_unref( m_pUdev );
+ m_pUdev = nullptr;
+ }
+ }
+
+ bool CLibInputHandler::Init()
+ {
+ m_pUdev = udev_new();
+ if ( !m_pUdev )
+ {
+ log_input_stealer.errorf( "Failed to create udev interface" );
+ return false;
+ }
+
+ m_pLibInput = libinput_udev_create_context( &s_LibInputInterface, nullptr, m_pUdev );
+ if ( !m_pLibInput )
+ {
+ log_input_stealer.errorf( "Failed to create libinput context" );
+ return false;
+ }
+
+ const char *pszSeatName = "seat0";
+ if ( libinput_udev_assign_seat( m_pLibInput, pszSeatName ) == -1 )
+ {
+ log_input_stealer.errorf( "Could not assign seat \"%s\"", pszSeatName );
+ return false;
+ }
+
+ return true;
+ }
+
+ int CLibInputHandler::GetFD()
+ {
+ if ( !m_pLibInput )
+ return -1;
+
+ return libinput_get_fd( m_pLibInput );
+ }
+
+ void CLibInputHandler::OnPollIn()
+ {
+ static uint32_t s_uSequence = 0;
+
+ libinput_dispatch( m_pLibInput );
+
+ while ( libinput_event *pEvent = libinput_get_event( m_pLibInput ) )
+ {
+ defer( libinput_event_destroy( pEvent ) );
+
+ libinput_event_type eEventType = libinput_event_get_type( pEvent );
+
+ switch ( eEventType )
+ {
+ case LIBINPUT_EVENT_POINTER_MOTION:
+ {
+ libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent );
+
+ double flDx = libinput_event_pointer_get_dx( pPointerEvent );
+ double flDy = libinput_event_pointer_get_dy( pPointerEvent );
+
+ GetBackend()->NotifyPhysicalInput( InputType::Mouse );
+
+ wlserver_lock();
+ wlserver_mousemotion( flDx, flDy, ++s_uSequence );
+ wlserver_unlock();
+ }
+ break;
+
+ case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
+ {
+ libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent );
+
+ double flX = libinput_event_pointer_get_absolute_x( pPointerEvent );
+ double flY = libinput_event_pointer_get_absolute_y( pPointerEvent );
+
+ GetBackend()->NotifyPhysicalInput( InputType::Mouse );
+
+ wlserver_lock();
+ wlserver_mousewarp( flX, flY, ++s_uSequence, true );
+ wlserver_unlock();
+ }
+ break;
+
+ case LIBINPUT_EVENT_POINTER_BUTTON:
+ {
+ libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent );
+
+ uint32_t uButton = libinput_event_pointer_get_button( pPointerEvent );
+ libinput_button_state eButtonState = libinput_event_pointer_get_button_state( pPointerEvent );
+
+ wlserver_lock();
+ wlserver_mousebutton( uButton, eButtonState == LIBINPUT_BUTTON_STATE_PRESSED, ++s_uSequence );
+ wlserver_unlock();
+ }
+ break;
+
+ case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL:
+ {
+ libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent );
+
+ static constexpr libinput_pointer_axis eAxes[] =
+ {
+ LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
+ LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+ };
+
+ for ( uint32_t i = 0; i < std::size( eAxes ); i++ )
+ {
+ libinput_pointer_axis eAxis = eAxes[i];
+
+ if ( !libinput_event_pointer_has_axis( pPointerEvent, eAxis ) )
+ continue;
+
+ double flScroll = libinput_event_pointer_get_scroll_value_v120( pPointerEvent, eAxis );
+ m_flScrollAccum[i] += flScroll / 120.0;
+ }
+ }
+ break;
+
+ case LIBINPUT_EVENT_KEYBOARD_KEY:
+ {
+ libinput_event_keyboard *pKeyboardEvent = libinput_event_get_keyboard_event( pEvent );
+ uint32_t uKey = libinput_event_keyboard_get_key( pKeyboardEvent );
+ libinput_key_state eState = libinput_event_keyboard_get_key_state( pKeyboardEvent );
+
+ wlserver_lock();
+ wlserver_key( uKey, eState == LIBINPUT_KEY_STATE_PRESSED, ++ s_uSequence );
+ wlserver_unlock();
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ // Handle scrolling
+ {
+ double flScrollX = m_flScrollAccum[0];
+ double flScrollY = m_flScrollAccum[1];
+ m_flScrollAccum[0] = 0.0;
+ m_flScrollAccum[1] = 0.0;
+
+ if ( flScrollX != 0.0 || flScrollY != 0.0 )
+ {
+ wlserver_lock();
+ wlserver_mousewheel( flScrollX, flScrollY, ++s_uSequence );
+ wlserver_unlock();
+ }
+ }
+ }
+}
diff --git a/src/LibInputHandler.h b/src/LibInputHandler.h
new file mode 100644
index 0000000000..d9853f34c7
--- /dev/null
+++ b/src/LibInputHandler.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "waitable.h"
+
+struct libinput_interface;
+struct udev;
+struct libinput;
+
+namespace gamescope
+{
+ class CLibInputHandler final : public IWaitable
+ {
+ public:
+ CLibInputHandler();
+ ~CLibInputHandler();
+
+ bool Init();
+
+ virtual int GetFD() override;
+ virtual void OnPollIn() override;
+ private:
+ udev *m_pUdev = nullptr;
+ libinput *m_pLibInput = nullptr;
+
+ double m_flScrollAccum[2]{};
+
+ static const libinput_interface s_LibInputInterface;
+ };
+}
\ No newline at end of file
diff --git a/src/WaylandServer/WaylandServerLegacy.h b/src/WaylandServer/WaylandServerLegacy.h
index 0facb7dc8b..63ee2ca17e 100644
--- a/src/WaylandServer/WaylandServerLegacy.h
+++ b/src/WaylandServer/WaylandServerLegacy.h
@@ -29,6 +29,7 @@ struct wlserver_vk_swapchain_feedback
VkCompositeAlphaFlagBitsKHR vk_composite_alpha;
VkSurfaceTransformFlagBitsKHR vk_pre_transform;
VkBool32 vk_clipped;
+ std::shared_ptr vk_engine_name;
std::shared_ptr hdr_metadata_blob;
};
diff --git a/src/backend.cpp b/src/backend.cpp
index 8a6bbe8ed9..7ac9b3e49a 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -12,6 +12,8 @@ extern bool env_to_bool(const char *env);
namespace gamescope
{
+ ConVar cv_backend_virtual_connector_strategy( "backend_virtual_connector_strategy", VirtualConnectorStrategies::SingleApplication );
+
/////////////
// IBackend
/////////////
@@ -56,7 +58,7 @@ namespace gamescope
CBaseBackendFb::~CBaseBackendFb()
{
// I do not own the client buffer, but I released that in DecRef.
- assert( !HasLiveReferences() );
+ //assert( !HasLiveReferences() );
}
uint32_t CBaseBackendFb::IncRef()
@@ -122,6 +124,17 @@ namespace gamescope
}
}
+ /////////////////////////
+ // CBaseBackendConnector
+ /////////////////////////
+
+ VBlankScheduleTime CBaseBackendConnector::FrameSync()
+ {
+ VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false );
+ sleep_until_nanos( schedule.ulScheduledWakeupPoint );
+ return schedule;
+ }
+
/////////////////
// CBaseBackend
/////////////////
@@ -132,18 +145,6 @@ namespace gamescope
return bForceTimerFd;
}
- INestedHints *CBaseBackend::GetNestedHints()
- {
- return nullptr;
- }
-
- VBlankScheduleTime CBaseBackend::FrameSync()
- {
- VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false );
- sleep_until_nanos( schedule.ulScheduledWakeupPoint );
- return schedule;
- }
-
ConVar cv_touch_external_display_trackpad( "touch_external_display_trackpad", false, "If we are using an external display, should we treat the internal display's touch as a trackpad insteaad?" );
ConVar cv_touch_click_mode( "touch_click_mode", TouchClickModes::Left, "The default action to perform on touch." );
TouchClickMode CBaseBackend::GetTouchClickMode()
@@ -161,7 +162,6 @@ namespace gamescope
void CBaseBackend::DumpDebugInfo()
{
console_log.infof( "Uses Modifiers: %s", this->UsesModifiers() ? "true" : "false" );
- console_log.infof( "VRR Active: %s", this->IsVRRActive() ? "true" : "false" );
console_log.infof( "Supports Plane Hardware Cursor: %s (not relevant for nested backends)", this->SupportsPlaneHardwareCursor() ? "true" : "false" );
console_log.infof( "Supports Tearing: %s", this->SupportsTearing() ? "true" : "false" );
console_log.infof( "Uses Vulkan Swapchain: %s", this->UsesVulkanSwapchain() ? "true" : "false" );
@@ -169,11 +169,21 @@ namespace gamescope
console_log.infof( "Supports Explicit Sync: %s", this->SupportsExplicitSync() ? "true" : "false" );
console_log.infof( "Current Screen Type: %s", this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ? "Internal" : "External" );
console_log.infof( "Is Visible: %s", this->IsVisible() ? "true" : "false" );
- console_log.infof( "Is Nested: %s", this->GetNestedHints() != nullptr ? "true" : "false" );
console_log.infof( "Needs Frame Sync: %s", this->NeedsFrameSync() ? "true" : "false" );
- console_log.infof( "Total Presents Queued: %lu", this->PresentationFeedback().TotalPresentsQueued() );
- console_log.infof( "Total Presents Completed: %lu", this->PresentationFeedback().TotalPresentsCompleted() );
- console_log.infof( "Current Presents In Flight: %lu", this->PresentationFeedback().CurrentPresentsInFlight() );
+ console_log.infof( "VRR Active: %s", this->GetCurrentConnector()->IsVRRActive() ? "true" : "false" );
+ console_log.infof( "Total Presents Queued: %lu", this->GetCurrentConnector()->PresentationFeedback().TotalPresentsQueued() );
+ console_log.infof( "Total Presents Completed: %lu", this->GetCurrentConnector()->PresentationFeedback().TotalPresentsCompleted() );
+ console_log.infof( "Current Presents In Flight: %lu", this->GetCurrentConnector()->PresentationFeedback().CurrentPresentsInFlight() );
+ }
+
+ bool CBaseBackend::UsesVirtualConnectors()
+ {
+ return false;
+ }
+ std::shared_ptr CBaseBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey )
+ {
+ assert( false );
+ return nullptr;
}
ConCommand cc_backend_info( "backend_info", "Dump debug info about the backend state",
diff --git a/src/backend.h b/src/backend.h
index dd295f4ac0..82db19c285 100644
--- a/src/backend.h
+++ b/src/backend.h
@@ -15,16 +15,74 @@
#include
#include
#include
+#include
struct wlr_buffer;
struct wlr_dmabuf_attributes;
struct FrameInfo_t;
+extern bool steamMode;
+
namespace gamescope
{
struct VBlankScheduleTime;
class BackendBlob;
+ class INestedHints;
+
+ namespace VirtualConnectorStrategies
+ {
+ enum VirtualConnectorStrategy : uint32_t
+ {
+ SingleApplication,
+ SteamControlled,
+ PerAppId,
+ PerWindow,
+
+ Count,
+ };
+ }
+ using VirtualConnectorStrategy = VirtualConnectorStrategies::VirtualConnectorStrategy;
+ using VirtualConnectorKey_t = uint64_t;
+ extern ConVar cv_backend_virtual_connector_strategy;
+
+ static constexpr bool VirtualConnectorStrategyIsSingleOutput( VirtualConnectorStrategy eStrategy )
+ {
+ return eStrategy == VirtualConnectorStrategies::SingleApplication || eStrategy == VirtualConnectorStrategies::SteamControlled;
+ }
+
+ static inline bool VirtualConnectorIsSingleOutput()
+ {
+ return VirtualConnectorStrategyIsSingleOutput( cv_backend_virtual_connector_strategy );
+ }
+
+ static inline bool VirtualConnectorInSteamPerAppState()
+ {
+ return steamMode && cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::PerAppId;
+ }
+
+ static inline bool VirtualConnectorKeyIsSteam( VirtualConnectorKey_t ulKey )
+ {
+ return VirtualConnectorInSteamPerAppState() && ulKey == 769;
+ }
+
+ static inline std::string_view VirtualConnectorStrategyToString( VirtualConnectorStrategy eStrategy )
+ {
+ switch ( eStrategy )
+ {
+ case VirtualConnectorStrategies::SingleApplication: return "SingleApplication";
+ case VirtualConnectorStrategies::SteamControlled: return "SteamControlled";
+ case VirtualConnectorStrategies::PerAppId: return "PerAppId";
+ case VirtualConnectorStrategies::PerWindow: return "PerWindow";
+ default:
+ return "Unknown";
+ }
+ }
+
+ enum class InputType
+ {
+ Mouse,
+ };
namespace TouchClickModes
{
@@ -84,6 +142,19 @@ namespace gamescope
uint32_t uRefresh; // Hz
};
+ struct BackendPresentFeedback
+ {
+ public:
+ uint64_t CurrentPresentsInFlight() const { return TotalPresentsQueued() - TotalPresentsCompleted(); }
+
+ // Across the lifetime of the backend.
+ uint64_t TotalPresentsQueued() const { return m_uQueuedPresents.load(); }
+ uint64_t TotalPresentsCompleted() const { return m_uCompletedPresents.load(); }
+
+ std::atomic m_uQueuedPresents = { 0u };
+ std::atomic m_uCompletedPresents = { 0u };
+ };
+
class IBackendConnector
{
public:
@@ -94,6 +165,7 @@ namespace gamescope
virtual bool SupportsHDR() const = 0;
virtual bool IsHDRActive() const = 0;
virtual const BackendConnectorHDRInfo &GetHDRInfo() const = 0;
+ virtual bool IsVRRActive() const = 0;
virtual std::span GetModes() const = 0;
virtual bool SupportsVRR() const = 0;
@@ -109,6 +181,39 @@ namespace gamescope
virtual const char *GetName() const = 0;
virtual const char *GetMake() const = 0;
virtual const char *GetModel() const = 0;
+
+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) = 0;
+ virtual VBlankScheduleTime FrameSync() = 0;
+ virtual BackendPresentFeedback& PresentationFeedback() = 0;
+
+ virtual uint64_t GetVirtualConnectorKey() const = 0;
+
+ virtual INestedHints *GetNestedHints() = 0;
+ };
+
+ class CBaseBackendConnector : public IBackendConnector
+ {
+ public:
+ CBaseBackendConnector()
+ {
+ }
+ CBaseBackendConnector( uint64_t ulVirtualConnectorKey )
+ : m_ulVirtualConnectorKey{ ulVirtualConnectorKey }
+ {
+ }
+
+ virtual ~CBaseBackendConnector()
+ {
+
+ }
+
+ virtual VBlankScheduleTime FrameSync() override;
+ virtual BackendPresentFeedback& PresentationFeedback() override { return m_PresentFeedback; }
+ virtual uint64_t GetVirtualConnectorKey() const override { return m_ulVirtualConnectorKey; }
+ virtual INestedHints *GetNestedHints() override { return nullptr; }
+ protected:
+ uint64_t m_ulVirtualConnectorKey = 0;
+ BackendPresentFeedback m_PresentFeedback{};
};
class INestedHints
@@ -131,20 +236,6 @@ namespace gamescope
virtual void SetTitle( std::shared_ptr szTitle ) = 0;
virtual void SetIcon( std::shared_ptr> uIconPixels ) = 0;
virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) = 0;
- virtual std::shared_ptr GetHostCursor() = 0;
- };
-
- struct BackendPresentFeedback
- {
- public:
- uint64_t CurrentPresentsInFlight() const { return TotalPresentsQueued() - TotalPresentsCompleted(); }
-
- // Across the lifetime of the backend.
- uint64_t TotalPresentsQueued() const { return m_uQueuedPresents.load(); }
- uint64_t TotalPresentsCompleted() const { return m_uCompletedPresents.load(); }
-
- std::atomic m_uQueuedPresents = { 0u };
- std::atomic m_uCompletedPresents = { 0u };
};
class IBackendFb : public IRcObject
@@ -184,7 +275,6 @@ namespace gamescope
virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const = 0;
virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const = 0;
- virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) = 0;
virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) = 0;
virtual bool PollState() = 0;
@@ -214,9 +304,6 @@ namespace gamescope
virtual IBackendConnector *GetCurrentConnector() = 0;
virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) = 0;
- // Might want to move this to connector someday, but it lives in CRTC.
- virtual bool IsVRRActive() const = 0;
-
virtual bool SupportsPlaneHardwareCursor() const = 0;
virtual bool SupportsTearing() const = 0;
@@ -237,22 +324,21 @@ namespace gamescope
virtual bool IsVisible() const = 0;
virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const = 0;
- virtual INestedHints *GetNestedHints() = 0;
-
// This will move to the connector and be deprecated soon.
virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) = 0;
virtual void HackUpdatePatchedEdid() = 0;
virtual bool NeedsFrameSync() const = 0;
- virtual VBlankScheduleTime FrameSync() = 0;
-
- // TODO: Make me const someday.
- virtual BackendPresentFeedback& PresentationFeedback() = 0;
virtual TouchClickMode GetTouchClickMode() = 0;
virtual void DumpDebugInfo() = 0;
+ virtual bool UsesVirtualConnectors() = 0;
+ virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) = 0;
+
+ virtual void NotifyPhysicalInput( InputType eInputType ) = 0;
+
static IBackend *Get();
template
static bool Set();
@@ -269,21 +355,19 @@ namespace gamescope
class CBaseBackend : public IBackend
{
public:
- virtual INestedHints *GetNestedHints() override;
-
virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override { return false; }
virtual void HackUpdatePatchedEdid() override {}
virtual bool NeedsFrameSync() const override;
- virtual VBlankScheduleTime FrameSync() override;
-
- virtual BackendPresentFeedback& PresentationFeedback() override { return m_PresentFeedback; }
virtual TouchClickMode GetTouchClickMode() override;
virtual void DumpDebugInfo() override;
- protected:
- BackendPresentFeedback m_PresentFeedback{};
+
+ virtual bool UsesVirtualConnectors() override;
+ virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override;
+
+ virtual void NotifyPhysicalInput( InputType eInputType ) override {}
};
// This is a blob of data that may be associated with
diff --git a/src/main.cpp b/src/main.cpp
index 9dff5c4d85..7bd4f6f2ef 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -94,6 +94,7 @@ const struct option *gamescope_options = (struct option[]){
// openvr options
#if HAVE_OPENVR
{ "vr-overlay-key", required_argument, nullptr, 0 },
+ { "vr-app-overlay-key", required_argument, nullptr, 0 },
{ "vr-overlay-explicit-name", required_argument, nullptr, 0 },
{ "vr-overlay-default-name", required_argument, nullptr, 0 },
{ "vr-overlay-icon", required_argument, nullptr, 0 },
@@ -106,6 +107,7 @@ const struct option *gamescope_options = (struct option[]){
{ "vr-overlay-physical-curvature", required_argument, nullptr, 0 },
{ "vr-overlay-physical-pre-curve-pitch", required_argument, nullptr, 0 },
{ "vr-scroll-speed", required_argument, nullptr, 0 },
+ { "vr-session-manager", no_argument, nullptr, 0 },
#endif
// wlserver options
@@ -115,6 +117,7 @@ const struct option *gamescope_options = (struct option[]){
{ "cursor", required_argument, nullptr, 0 },
{ "cursor-hotspot", required_argument, nullptr, 0 },
{ "cursor-scale-height", required_argument, nullptr, 0 },
+ { "virtual-connector-strategy", required_argument, nullptr, 0 },
{ "ready-fd", required_argument, nullptr, 'R' },
{ "stats-path", required_argument, nullptr, 'T' },
{ "hide-cursor-delay", required_argument, nullptr, 'C' },
@@ -192,6 +195,7 @@ const char usage[] =
" --force-orientation rotate the internal display (left, right, normal, upsidedown)\n"
" --force-windows-fullscreen force windows inside of gamescope to be the size of the nested display (fullscreen)\n"
" --cursor-scale-height if specified, sets a base output height to linearly scale the cursor against.\n"
+ " --virtual-connector-strategy Specifies how we should make virtual connectors.\n"
" --hdr-enabled enable HDR output (needs Gamescope WSI layer enabled for support from clients)\n"
" If this is not set, and there is a HDR client, it will be tonemapped SDR.\n"
" --sdr-gamut-wideness Set the 'wideness' of the gamut for SDR comment. 0 - 1.\n"
@@ -222,6 +226,7 @@ const char usage[] =
#if HAVE_OPENVR
"VR mode options:\n"
" --vr-overlay-key Sets the SteamVR overlay key to this string\n"
+ " --vr-app-overlay-key Sets the SteamVR overlay key to use for child apps\n"
" --vr-overlay-explicit-name Force the SteamVR overlay name to always be this string\n"
" --vr-overlay-default-name Sets the fallback SteamVR overlay name when there is no window title\n"
" --vr-overlay-icon Sets the SteamVR overlay icon to this file\n"
@@ -720,6 +725,8 @@ int main(int argc, char **argv)
break;
case 'e':
steamMode = true;
+ if ( gamescope::cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::SingleApplication )
+ gamescope::cv_backend_virtual_connector_strategy = gamescope::VirtualConnectorStrategies::SteamControlled;
break;
case 0: // long options without a short option
opt_name = gamescope_options[opt_index].name;
@@ -762,7 +769,7 @@ int main(int argc, char **argv)
} else if (strcmp(opt_name, "force-grab-cursor") == 0) {
g_bForceRelativeMouse = true;
} else if (strcmp(opt_name, "display-index") == 0) {
- g_nNestedDisplayIndex = atoi( optarg );
+ g_nNestedDisplayIndex = atoi( optarg );
} else if (strcmp(opt_name, "adaptive-sync") == 0) {
cv_adaptive_sync = true;
} else if (strcmp(opt_name, "expose-wayland") == 0) {
@@ -773,6 +780,17 @@ int main(int argc, char **argv)
g_nCursorScaleHeight = atoi(optarg);
} else if (strcmp(opt_name, "mangoapp") == 0) {
g_bLaunchMangoapp = true;
+ } else if (strcmp(opt_name, "virtual-connector-strategy") == 0) {
+ for ( uint32_t i = 0; i < gamescope::VirtualConnectorStrategies::Count; i++ )
+ {
+ gamescope::VirtualConnectorStrategy eStrategy =
+ static_cast( i );
+ if ( optarg == gamescope::VirtualConnectorStrategyToString( eStrategy ) )
+ {
+ gamescope::cv_backend_virtual_connector_strategy = eStrategy;
+
+ }
+ }
}
break;
case '?':
diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp
index 91e01bc275..d8e1ce7eda 100644
--- a/src/mangoapp.cpp
+++ b/src/mangoapp.cpp
@@ -31,6 +31,7 @@ struct mangoapp_msg_v1 {
uint16_t displayRefresh;
bool bAppWantsHDR : 1;
bool bSteamFocused : 1;
+ char engineName[40];
// WARNING: Always ADD fields, never remove or repurpose fields
} __attribute__((packed)) mangoapp_msg_v1;
@@ -60,6 +61,11 @@ void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uin
mangoapp_msg_v1.displayRefresh = (uint16_t) gamescope::ConvertmHzToHz( g_nOutputRefresh );
mangoapp_msg_v1.bAppWantsHDR = g_bAppWantsHDRCached;
mangoapp_msg_v1.bSteamFocused = g_focusedBaseAppId == 769;
+ memset(mangoapp_msg_v1.engineName, 0, sizeof(mangoapp_msg_v1.engineName));
+ if (focusWindow_engine)
+ focusWindow_engine->copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char));
+ else
+ std::string("gamescope").copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char));
msgsnd(msgid, &mangoapp_msg_v1, sizeof(mangoapp_msg_v1) - sizeof(mangoapp_msg_v1.hdr.msg_type), IPC_NOWAIT);
}
diff --git a/src/meson.build b/src/meson.build
index 74fc0334d4..63897dd2a4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -119,9 +119,11 @@ src = [
'backend.cpp',
'x11cursor.cpp',
'InputEmulation.cpp',
+ 'LibInputHandler.cpp',
]
luajit_dep = dependency( 'luajit' )
+libinput_dep = dependency('libinput', required: true)
gamescope_cpp_args = []
if drm_dep.found()
@@ -194,7 +196,7 @@ gamescope_version = configure_file(
xkbcommon, thread_dep, sdl2_dep, wlroots_dep,
vulkan_dep, liftoff_dep, dep_xtst, dep_xmu, cap_dep, epoll_dep, pipewire_dep, librt_dep,
stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi,
- libdecor_dep, eis_dep, luajit_dep
+ libdecor_dep, eis_dep, luajit_dep, libinput_dep,
],
install: true,
cpp_args: gamescope_cpp_args,
diff --git a/src/messagey.h b/src/messagey.h
index 87a84d310c..640fb4d72f 100644
--- a/src/messagey.h
+++ b/src/messagey.h
@@ -112,6 +112,10 @@ namespace messagey {
int fd_pipe[2]; /* fd_pipe[0]: read end of pipe, fd_pipe[1]: write end of pipe */
pid_t pid1;
+ const char *zenityDisableEnv = getenv("GAMESCOPE_ZENITY_DISABLE");
+ if (zenityDisableEnv && *zenityDisableEnv && atoi(zenityDisableEnv) != 0)
+ return 0;
+
if (messageboxdata->numbuttons > MaxButtons) {
return SetError("Too many buttons (%d max allowed)", MaxButtons);
}
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index a8f44d1ef2..3b26f3919e 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -166,9 +166,21 @@ uint32_t g_reshade_technique_idx = 0;
bool g_bSteamIsActiveWindow = false;
bool g_bForceInternal = false;
+namespace gamescope
+{
+ extern std::shared_ptr GetX11HostCursor();
+}
+
static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows();
static bool
-pick_primary_focus_and_override(focus_t *out, Window focusControlWindow, const std::vector& vecPossibleFocusWindows, bool globalFocus, const std::vector& ctxFocusControlAppIDs);
+pick_primary_focus_and_override(
+ focus_t *out,
+ Window focusControlWindow,
+ const std::vector& vecPossibleFocusWindows,
+ bool globalFocus,
+ const std::vector& ctxFocusControlAppIDs,
+ uint64_t ulVirtualFocusKey = 0,
+ gamescope::VirtualConnectorStrategy eStrategy = gamescope::VirtualConnectorStrategies::PerWindow);
bool env_to_bool(const char *env)
{
@@ -715,7 +727,7 @@ Window x11_win(steamcompmgr_win_t *w) {
}
static uint64_t s_ulFocusSerial = 0ul;
-static inline void MakeFocusDirty()
+void MakeFocusDirty()
{
s_ulFocusSerial++;
}
@@ -733,10 +745,44 @@ bool focus_t::IsDirty()
struct global_focus_t : public focus_t
{
steamcompmgr_win_t *keyboardFocusWindow;
- steamcompmgr_win_t *fadeWindow;
+ steamcompmgr_win_t *fadeWindow;
MouseCursor *cursor;
-} global_focus;
+ gamescope::VirtualConnectorKey_t ulVirtualFocusKey = 0;
+ std::shared_ptr pVirtualConnector;
+
+ gamescope::INestedHints *GetNestedHints()
+ {
+ gamescope::IBackendConnector *pConnector = this->pVirtualConnector.get();
+ if ( !pConnector )
+ pConnector = GetBackend()->GetCurrentConnector();
+
+ if ( pConnector )
+ {
+ return pConnector->GetNestedHints();
+ }
+
+ return nullptr;
+ }
+};
+
+std::unordered_map g_VirtualConnectorFocuses;
+global_focus_t *GetCurrentFocus()
+{
+ if ( GetBackend()->GetCurrentConnector() )
+ {
+ uint64_t ulKey = GetBackend()->GetCurrentConnector()->GetVirtualConnectorKey();
+
+ auto iter = g_VirtualConnectorFocuses.find( ulKey );
+ if ( iter != g_VirtualConnectorFocuses.end() )
+ return &iter->second;
+ }
+
+ if ( g_VirtualConnectorFocuses.size() > 0 )
+ return &g_VirtualConnectorFocuses.begin()->second;
+
+ return nullptr;
+}
uint32_t currentOutputWidth, currentOutputHeight;
bool currentHDROutput = false;
@@ -884,6 +930,7 @@ int g_BlurRadius = 5;
unsigned int g_BlurFadeStartTime = 0;
pid_t focusWindow_pid;
+std::shared_ptr focusWindow_engine = nullptr;
focus_t g_steamcompmgr_xdg_focus;
std::vector> g_steamcompmgr_xdg_wins;
@@ -899,7 +946,7 @@ bool g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = false;
bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w )
{
// VRR + FPS Limit needs another approach.
- if ( GetBackend()->IsVRRActive() )
+ if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() )
return false;
return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay;
@@ -923,7 +970,7 @@ steamcompmgr_user_has_any_game_open()
bool steamcompmgr_window_should_refresh_switch( steamcompmgr_win_t *w )
{
- if ( GetBackend()->IsVRRActive() )
+ if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() )
return false;
if ( g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive )
@@ -1678,8 +1725,10 @@ bool MouseCursor::getTexture()
updateCursorFeedback();
if (m_imageEmpty) {
+#if 0 // XXX(strategy) FIXME
if ( GetBackend()->GetNestedHints() )
GetBackend()->GetNestedHints()->SetCursorImage( nullptr );
+#endif
return false;
}
@@ -1692,6 +1741,8 @@ bool MouseCursor::getTexture()
}
m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer.data());
+
+#if 0 // XXX(strategy) FIXME
if ( GetBackend()->GetNestedHints() )
{
auto info = std::make_shared(
@@ -1705,6 +1756,7 @@ bool MouseCursor::getTexture()
});
GetBackend()->GetNestedHints()->SetCursorImage( std::move( info ) );
}
+#endif
assert(m_texture);
XFree(image);
@@ -1908,12 +1960,6 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win
// Base plane will stay as tex=0 if we don't have contents yet, which will
// make us fall back to compositing and use the Vulkan null texture
- steamcompmgr_win_t *mainOverlayWindow = global_focus.overlayWindow;
-
- const bool notificationMode = flags & PaintWindowFlag::NotificationMode;
- if (notificationMode && !mainOverlayWindow)
- return nullptr;
-
int curLayer = frameInfo->layerCount++;
FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ];
@@ -1922,12 +1968,7 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win
layer->tex = lastCommit->GetTexture( layer->filter, g_upscaleScaler );
- if (notificationMode)
- {
- sourceWidth = mainOverlayWindow->GetGeometry().nWidth;
- sourceHeight = mainOverlayWindow->GetGeometry().nHeight;
- }
- else if ( flags & PaintWindowFlag::NoScale )
+ if ( flags & PaintWindowFlag::NoScale )
{
sourceWidth = currentOutputWidth;
sourceHeight = currentOutputHeight;
@@ -1974,7 +2015,7 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win
drawYOffset += w->GetGeometry().nY * currentScaleRatio_y;
}
- if ( cursor && zoomScaleRatio != 1.0 )
+ if ( zoomScaleRatio != 1.0 )
{
drawXOffset += (((int)sourceWidth / 2) - cursor->x()) * currentScaleRatio_x;
drawYOffset += (((int)sourceHeight / 2) - cursor->y()) * currentScaleRatio_y;
@@ -1991,22 +2032,6 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win
layer->offset.x = -drawXOffset;
layer->offset.y = -drawYOffset;
}
- else if (notificationMode)
- {
- int xOffset = 0, yOffset = 0;
-
- int width = w->GetGeometry().nWidth * currentScaleRatio_x;
- int height = w->GetGeometry().nHeight * currentScaleRatio_y;
-
- if (globalScaleRatio != 1.0f)
- {
- xOffset = (currentOutputWidth - currentOutputWidth * globalScaleRatio) / 2.0;
- yOffset = (currentOutputHeight - currentOutputHeight * globalScaleRatio) / 2.0;
- }
-
- layer->offset.x = (currentOutputWidth - xOffset - width) * -1.0f;
- layer->offset.y = (currentOutputHeight - yOffset - height) * -1.0f;
- }
else
{
layer->offset.x = -drawXOffset;
@@ -2175,7 +2200,7 @@ static void paint_pipewire()
}
else
{
- pFocus = &global_focus;
+ pFocus = GetCurrentFocus();
}
if ( !pFocus->focusWindow )
@@ -2200,10 +2225,10 @@ static void paint_pipewire()
s_ulLastOverrideCommitId = ulOverrideCommitId;
// Paint the windows we have onto the Pipewire stream.
- paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, global_focus.cursor, 0, 1.0f, pFocus->overrideWindow );
+ paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, 0, 1.0f, pFocus->overrideWindow );
if ( pFocus->overrideWindow && !pFocus->focusWindow->isSteamStreamingClient )
- paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, global_focus.cursor, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow );
+ paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow );
gamescope::Rc pRGBTexture = s_pPipewireBuffer->texture->isYcbcr()
? vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, DRM_FORMAT_XRGB2101010 )
@@ -2239,12 +2264,19 @@ bool ShouldDrawCursor()
if ( cv_cursor_composite == 0 )
return false;
- return g_bForceRelativeMouse || !GetBackend()->GetNestedHints();
+ return g_bForceRelativeMouse || !GetCurrentFocus()->GetNestedHints();
}
static void
-paint_all(bool async)
+paint_all( global_focus_t *pFocus, bool async )
{
+ if ( !pFocus )
+ return;
+
+ gamescope::IBackendConnector *pConnector = pFocus->pVirtualConnector.get();
+ if ( !pConnector )
+ pConnector = GetBackend()->GetCurrentConnector();
+
gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0);
xwayland_ctx_t *root_ctx = root_server->ctx.get();
@@ -2264,12 +2296,12 @@ paint_all(bool async)
unsigned int currentTime = get_time_in_milliseconds();
bool fadingOut = ( currentTime - fadeOutStartTime < g_FadeOutDuration || g_bPendingFade ) && g_HeldCommits[HELD_COMMIT_FADE] != nullptr;
- w = global_focus.focusWindow;
- overlay = global_focus.overlayWindow;
- externalOverlay = global_focus.externalOverlayWindow;
- notification = global_focus.notificationWindow;
- override = global_focus.overrideWindow;
- input = global_focus.inputFocusWindow;
+ w = pFocus->focusWindow;
+ overlay = pFocus->overlayWindow;
+ externalOverlay = pFocus->externalOverlayWindow;
+ notification = pFocus->notificationWindow;
+ override = pFocus->overrideWindow;
+ input = pFocus->inputFocusWindow;
if (++frameCounter == 300)
{
@@ -2312,7 +2344,7 @@ paint_all(bool async)
if ( videow->isSteamStreamingClientVideo == true )
{
// TODO: also check matching AppID so we can have several pairs
- paint_window(videow, videow, &frameInfo, global_focus.cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders);
+ paint_window(videow, videow, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders);
bHasVideoUnderlay = true;
break;
}
@@ -2324,8 +2356,9 @@ paint_all(bool async)
uint32_t flags = 0;
if ( !bHasVideoUnderlay )
flags |= PaintWindowFlag::BasePlane;
- paint_window(w, w, &frameInfo, global_focus.cursor, flags);
- update_touch_scaling( &frameInfo );
+ paint_window(w, w, &frameInfo, pFocus->cursor, flags);
+ if ( pFocus == GetCurrentFocus() )
+ update_touch_scaling( &frameInfo );
// paint UI unless it's fully hidden, which it communicates to us through opacity=0
// we paint it to extract scaling coefficients above, then remove the layer if one was added
@@ -2341,7 +2374,7 @@ paint_all(bool async)
: ((currentTime - fadeOutStartTime) / (float)g_FadeOutDuration);
paint_cached_base_layer(g_HeldCommits[HELD_COMMIT_FADE], g_CachedPlanes[HELD_COMMIT_FADE], &frameInfo, 1.0f - opacityScale, false);
- paint_window(w, w, &frameInfo, global_focus.cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::FadeTarget | PaintWindowFlag::DrawBorders, opacityScale, override);
+ paint_window(w, w, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::FadeTarget | PaintWindowFlag::DrawBorders, opacityScale, override);
}
else
{
@@ -2351,17 +2384,18 @@ paint_all(bool async)
g_HeldCommits[HELD_COMMIT_FADE] = nullptr;
g_bPendingFade = false;
fadeOutStartTime = 0;
- global_focus.fadeWindow = None;
+ pFocus->fadeWindow = None;
}
}
// Just draw focused window as normal, be it Steam or the game
- paint_window(w, w, &frameInfo, global_focus.cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders, 1.0f, override);
+ paint_window(w, w, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders, 1.0f, override);
bool needsScaling = frameInfo.layers[0].scale.x < 0.999f && frameInfo.layers[0].scale.y < 0.999f;
frameInfo.useFSRLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::FSR && needsScaling;
frameInfo.useNISLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::NIS && needsScaling;
}
- update_touch_scaling( &frameInfo );
+ if ( pFocus == GetCurrentFocus() )
+ update_touch_scaling( &frameInfo );
}
}
else
@@ -2386,7 +2420,7 @@ paint_all(bool async)
// as we will have too many layers. Better to be safe than sorry.
if ( override && w && !w->isSteamStreamingClient )
{
- paint_window(override, w, &frameInfo, global_focus.cursor, PaintWindowFlag::NoFilter, 1.0f, override);
+ paint_window(override, w, &frameInfo, pFocus->cursor, PaintWindowFlag::NoFilter, 1.0f, override);
// Don't update touch scaling for frameInfo. We don't ever make it our
// wlserver_mousefocus window.
//update_touch_scaling( &frameInfo );
@@ -2399,18 +2433,18 @@ paint_all(bool async)
{
if (externalOverlay->opacity)
{
- paint_window(externalOverlay, externalOverlay, &frameInfo, global_focus.cursor, PaintWindowFlag::NoScale | PaintWindowFlag::NoFilter);
+ paint_window(externalOverlay, externalOverlay, &frameInfo, pFocus->cursor, PaintWindowFlag::NoScale | PaintWindowFlag::NoFilter);
- if ( externalOverlay == global_focus.inputFocusWindow )
+ if ( externalOverlay == pFocus->inputFocusWindow && pFocus == GetCurrentFocus() )
update_touch_scaling( &frameInfo );
}
}
if (overlay && overlay->opacity)
{
- paint_window(overlay, overlay, &frameInfo, global_focus.cursor, PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter);
+ paint_window(overlay, overlay, &frameInfo, pFocus->cursor, PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter);
- if ( overlay == global_focus.inputFocusWindow )
+ if ( overlay == pFocus->inputFocusWindow && pFocus == GetCurrentFocus() )
update_touch_scaling( &frameInfo );
}
else if ( !GetBackend()->UsesVulkanSwapchain() && GetBackend()->IsSessionBased() )
@@ -2446,7 +2480,7 @@ paint_all(bool async)
{
if (notification->opacity)
{
- paint_window(notification, notification, &frameInfo, global_focus.cursor, PaintWindowFlag::NotificationMode | PaintWindowFlag::NoFilter);
+ paint_window(notification, notification, &frameInfo, pFocus->cursor, PaintWindowFlag::NotificationMode | PaintWindowFlag::NoFilter);
}
}
@@ -2454,12 +2488,12 @@ paint_all(bool async)
{
// Make sure to un-dirty the texture before we do any painting logic.
// We determine whether we are grabbed etc this way.
- global_focus.cursor->undirty();
+ pFocus->cursor->undirty();
}
// Draw cursor if we need to
if (input && ShouldDrawCursor()) {
- global_focus.cursor->paint(
+ pFocus->cursor->paint(
input, w == input ? override : nullptr,
&frameInfo);
}
@@ -2499,14 +2533,14 @@ paint_all(bool async)
update_app_target_refresh_cycle();
- const bool bSupportsDynamicRefresh = GetBackend()->GetCurrentConnector() && !GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates().empty();
+ const bool bSupportsDynamicRefresh = pConnector && !pConnector->GetValidDynamicRefreshRates().empty();
if ( bSupportsDynamicRefresh )
{
- auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates();
+ auto rates = pConnector->GetValidDynamicRefreshRates();
int nDynamicRefreshHz = g_nDynamicRefreshRate[GetBackend()->GetScreenType()];
- int nTargetRefreshHz = nDynamicRefreshHz && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow
+ int nTargetRefreshHz = nDynamicRefreshHz && steamcompmgr_window_should_refresh_switch( pFocus->focusWindow )
? nDynamicRefreshHz
: int( rates[ rates.size() - 1 ] );
@@ -2561,7 +2595,7 @@ paint_all(bool async)
}
}
- if ( GetBackend()->Present( &frameInfo, async ) != 0 )
+ if ( pConnector->Present( &frameInfo, async ) != 0 )
{
return;
}
@@ -2682,10 +2716,10 @@ paint_all(bool async)
if ( !maxCLLNits && !maxFALLNits )
{
- if ( GetBackend()->GetCurrentConnector() )
+ if ( pConnector )
{
- maxCLLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel;
- maxFALLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxFrameAverageLuminance;
+ maxCLLNits = pConnector->GetHDRInfo().uMaxContentLightLevel;
+ maxFALLNits = pConnector->GetHDRInfo().uMaxFrameAverageLuminance;
}
}
@@ -3160,35 +3194,57 @@ static bool is_good_override_candidate( steamcompmgr_win_t *override, steamcompm
}
static bool
-pick_primary_focus_and_override(focus_t *out, Window focusControlWindow, const std::vector& vecPossibleFocusWindows, bool globalFocus, const std::vector& ctxFocusControlAppIDs)
+pick_primary_focus_and_override(
+ focus_t *out,
+ Window focusControlWindow,
+ const std::vector& vecPossibleFocusWindows,
+ bool globalFocus,
+ const std::vector& ctxFocusControlAppIDs,
+ uint64_t ulVirtualFocusKey,
+ gamescope::VirtualConnectorStrategy eStrategy )
{
bool localGameFocused = false;
steamcompmgr_win_t *focus = NULL, *override_focus = NULL;
- bool controlledFocus = focusControlWindow != None || !ctxFocusControlAppIDs.empty();
+ bool controlledFocus = eStrategy != gamescope::VirtualConnectorStrategies::SingleApplication || focusControlWindow != None || !ctxFocusControlAppIDs.empty();
if ( controlledFocus )
{
- if ( focusControlWindow != None )
+ if ( eStrategy == gamescope::VirtualConnectorStrategies::SteamControlled )
{
- for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows )
+ if ( focusControlWindow != None )
{
- if ( focusable_window->type != steamcompmgr_win_type_t::XWAYLAND )
- continue;
+ for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows )
+ {
+ if ( focusable_window->type != steamcompmgr_win_type_t::XWAYLAND )
+ continue;
- if ( focusable_window->xwayland().id == focusControlWindow )
+ if ( focusable_window->xwayland().id == focusControlWindow )
+ {
+ focus = focusable_window;
+ localGameFocused = true;
+ goto found;
+ }
+ }
+ }
+
+ for ( auto focusable_appid : ctxFocusControlAppIDs )
+ {
+ for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows )
{
- focus = focusable_window;
- localGameFocused = true;
- goto found;
+ if ( focusable_window->appID == focusable_appid )
+ {
+ focus = focusable_window;
+ localGameFocused = true;
+ goto found;
+ }
}
}
}
-
- for ( auto focusable_appid : ctxFocusControlAppIDs )
+ else
{
for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows )
{
- if ( focusable_window->appID == focusable_appid )
+ if ( focusable_window->GetVirtualConnectorKey( eStrategy ) == ulVirtualFocusKey )
{
focus = focusable_window;
localGameFocused = true;
@@ -3416,9 +3472,12 @@ void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win
}
}
- if ( w->isOverlay && w->inputFocusMode )
+ if ( gamescope::VirtualConnectorIsSingleOutput() )
{
- inputFocus = w;
+ if ( w->isOverlay && w->inputFocusMode )
+ {
+ inputFocus = w;
+ }
}
}
@@ -3456,8 +3515,11 @@ void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win
steamcompmgr_win_t *keyboardFocusWin = inputFocus;
- if ( inputFocus && inputFocus->inputFocusMode == 2 )
- keyboardFocusWin = ctx->focus.focusWindow;
+ if ( gamescope::VirtualConnectorIsSingleOutput() )
+ {
+ if ( inputFocus && inputFocus->inputFocusMode == 2 )
+ keyboardFocusWin = ctx->focus.focusWindow;
+ }
Window keyboardFocusWindow = keyboardFocusWin ? keyboardFocusWin->xwayland().id : None;
@@ -3650,14 +3712,16 @@ steamcompmgr_xdg_determine_and_apply_focus( const std::vector< steamcompmgr_win_
uint32_t g_focusedBaseAppId = 0;
static void
-determine_and_apply_focus()
+determine_and_apply_focus( global_focus_t *pFocus )
{
gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0);
xwayland_ctx_t *root_ctx = root_server->ctx.get();
- global_focus_t previous_focus = global_focus;
- global_focus = global_focus_t{};
- global_focus.focusWindow = previous_focus.focusWindow;
- global_focus.cursor = root_ctx->cursor.get();
+ global_focus_t previousLocalFocus = *pFocus;
+ *pFocus = global_focus_t{};
+ pFocus->focusWindow = previousLocalFocus.focusWindow;
+ pFocus->cursor = root_ctx->cursor.get();
+ pFocus->ulVirtualFocusKey = previousLocalFocus.ulVirtualFocusKey;
+ pFocus->pVirtualConnector = previousLocalFocus.pVirtualConnector;
gameFocused = false;
std::vector< unsigned long > focusable_appids;
@@ -3725,42 +3789,52 @@ determine_and_apply_focus()
XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusableWindowsAtom, XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)focusable_windows.data(), focusable_windows.size() );
- gameFocused = pick_primary_focus_and_override(&global_focus, root_ctx->focusControlWindow, vecPossibleFocusWindows, true, vecFocuscontrolAppIDs);
+ gameFocused = pick_primary_focus_and_override( pFocus, root_ctx->focusControlWindow, vecPossibleFocusWindows, true, vecFocuscontrolAppIDs,
+ pFocus->ulVirtualFocusKey,
+ gamescope::cv_backend_virtual_connector_strategy );
// Pick overlay/notifications from root ctx
- global_focus.overlayWindow = root_ctx->focus.overlayWindow;
- global_focus.externalOverlayWindow = root_ctx->focus.externalOverlayWindow;
- global_focus.notificationWindow = root_ctx->focus.notificationWindow;
+ pFocus->overlayWindow = root_ctx->focus.overlayWindow;
+ pFocus->externalOverlayWindow = root_ctx->focus.externalOverlayWindow;
+ pFocus->notificationWindow = root_ctx->focus.notificationWindow;
- if ( !global_focus.overlayWindow )
+ if ( !pFocus->overlayWindow )
{
- global_focus.overlayWindow = g_steamcompmgr_xdg_focus.overlayWindow;
+ pFocus->overlayWindow = g_steamcompmgr_xdg_focus.overlayWindow;
}
- if ( !global_focus.externalOverlayWindow )
+ if ( !pFocus->externalOverlayWindow )
{
- global_focus.externalOverlayWindow = g_steamcompmgr_xdg_focus.externalOverlayWindow;
+ pFocus->externalOverlayWindow = g_steamcompmgr_xdg_focus.externalOverlayWindow;
}
+ bool bUseOverlay = gamescope::VirtualConnectorIsSingleOutput() || gamescope::VirtualConnectorKeyIsSteam( pFocus->ulVirtualFocusKey );
+ if ( !bUseOverlay )
+ {
+ pFocus->overlayWindow = nullptr;
+ pFocus->notificationWindow = nullptr;
+ }
+
// Pick inputFocusWindow
- if (global_focus.overlayWindow && global_focus.overlayWindow->inputFocusMode)
+ if ( gamescope::VirtualConnectorIsSingleOutput() &&
+ pFocus->overlayWindow && pFocus->overlayWindow->inputFocusMode )
{
- global_focus.inputFocusWindow = global_focus.overlayWindow;
- global_focus.keyboardFocusWindow = global_focus.overlayWindow;
+ pFocus->inputFocusWindow = pFocus->overlayWindow;
+ pFocus->keyboardFocusWindow = pFocus->overlayWindow;
}
else
{
- global_focus.inputFocusWindow = global_focus.focusWindow;
- global_focus.keyboardFocusWindow = global_focus.overrideWindow ? global_focus.overrideWindow : global_focus.focusWindow;
+ pFocus->inputFocusWindow = pFocus->focusWindow;
+ pFocus->keyboardFocusWindow = pFocus->overrideWindow ? pFocus->overrideWindow : pFocus->focusWindow;
}
// Pick cursor from our input focus window
// Initially pick cursor from the ctx of our input focus.
- if (global_focus.inputFocusWindow)
+ if (pFocus->inputFocusWindow)
{
- if (global_focus.inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND)
- global_focus.cursor = global_focus.inputFocusWindow->xwayland().ctx->cursor.get();
+ if (pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND)
+ pFocus->cursor = pFocus->inputFocusWindow->xwayland().ctx->cursor.get();
else
{
// TODO XDG:
@@ -3777,79 +3851,89 @@ determine_and_apply_focus()
}
}
- if (global_focus.inputFocusWindow)
- global_focus.inputFocusMode = global_focus.inputFocusWindow->inputFocusMode;
+ if (pFocus->inputFocusWindow)
+ pFocus->inputFocusMode = pFocus->inputFocusWindow->inputFocusMode;
- if ( global_focus.inputFocusMode == 2 )
+ if ( gamescope::VirtualConnectorIsSingleOutput() &&
+ pFocus->inputFocusMode == 2 )
{
- global_focus.keyboardFocusWindow = global_focus.overrideWindow
- ? global_focus.overrideWindow
- : global_focus.focusWindow;
+ pFocus->keyboardFocusWindow = pFocus->overrideWindow
+ ? pFocus->overrideWindow
+ : pFocus->focusWindow;
}
- // Tell wlserver about our keyboard/mouse focus.
- if ( global_focus.inputFocusWindow != previous_focus.inputFocusWindow ||
- global_focus.keyboardFocusWindow != previous_focus.keyboardFocusWindow ||
- global_focus.overrideWindow != previous_focus.overrideWindow )
+ // TODO(strategy): multi-seat on Wayland side
+ if ( pFocus == GetCurrentFocus() )
{
- if ( win_surface(global_focus.inputFocusWindow) != nullptr ||
- win_surface(global_focus.keyboardFocusWindow) != nullptr )
- {
- wlserver_lock();
+ static global_focus_t s_lastFocus;
- wlserver_clear_dropdowns();
- if ( win_surface( global_focus.overrideWindow ) != nullptr )
- wlserver_notify_dropdown( global_focus.overrideWindow->main_surface(), global_focus.overrideWindow->xwayland().a.x, global_focus.overrideWindow->xwayland().a.y );
+ // Tell wlserver about our keyboard/mouse focus.
+ if ( pFocus->inputFocusWindow != s_lastFocus.inputFocusWindow ||
+ pFocus->keyboardFocusWindow != s_lastFocus.keyboardFocusWindow ||
+ pFocus->overrideWindow != s_lastFocus.overrideWindow )
+ {
+ if ( win_surface(pFocus->inputFocusWindow) != nullptr ||
+ win_surface(pFocus->keyboardFocusWindow) != nullptr )
+ {
+ wlserver_lock();
- if ( win_surface(global_focus.inputFocusWindow) != nullptr && global_focus.cursor )
- wlserver_mousefocus( global_focus.inputFocusWindow->main_surface(), global_focus.cursor->x(), global_focus.cursor->y() );
+ wlserver_clear_dropdowns();
+ if ( win_surface( pFocus->overrideWindow ) != nullptr )
+ wlserver_notify_dropdown( pFocus->overrideWindow->main_surface(), pFocus->overrideWindow->xwayland().a.x, pFocus->overrideWindow->xwayland().a.y );
- if ( win_surface(global_focus.keyboardFocusWindow) != nullptr )
- wlserver_keyboardfocus( global_focus.keyboardFocusWindow->main_surface() );
- wlserver_unlock();
- }
+ if ( win_surface(pFocus->inputFocusWindow) != nullptr && pFocus->cursor )
+ wlserver_mousefocus( pFocus->inputFocusWindow->main_surface(), pFocus->cursor->x(), pFocus->cursor->y() );
- // Hide cursor on transitioning between xwaylands
- // We already do this when transitioning input focus inside of an
- // xwayland ctx.
- // don't care if we change kb focus window due to that happening when
- // going from override -> focus and we don't want to hide then as it's probably a dropdown.
- if ( global_focus.cursor && global_focus.inputFocusWindow != previous_focus.inputFocusWindow )
- global_focus.cursor->hide();
- }
+ if ( win_surface(pFocus->keyboardFocusWindow) != nullptr )
+ wlserver_keyboardfocus( pFocus->keyboardFocusWindow->main_surface() );
+ wlserver_unlock();
+ }
- if ( global_focus.inputFocusWindow )
- {
- // Cannot simply XWarpPointer here as we immediately go on to
- // do wlserver_mousefocus and need to update m_x and m_y of the cursor.
- if ( global_focus.inputFocusWindow->GetFocus()->bResetToCorner )
- {
- wlserver_lock();
- wlserver_mousewarp( global_focus.inputFocusWindow->GetGeometry().nWidth / 2, global_focus.inputFocusWindow->GetGeometry().nHeight / 2, 0, true );
- wlserver_fake_mouse_pos( global_focus.inputFocusWindow->GetGeometry().nWidth - 1, global_focus.inputFocusWindow->GetGeometry().nHeight - 1 );
- wlserver_unlock();
+ // Hide cursor on transitioning between xwaylands
+ // We already do this when transitioning input focus inside of an
+ // xwayland ctx.
+ // don't care if we change kb focus window due to that happening when
+ // going from override -> focus and we don't want to hide then as it's probably a dropdown.
+ if ( pFocus->cursor && pFocus->inputFocusWindow != s_lastFocus.inputFocusWindow )
+ pFocus->cursor->hide();
}
- else if ( global_focus.inputFocusWindow->GetFocus()->bResetToCenter )
+
+ if ( pFocus->inputFocusWindow )
{
- wlserver_lock();
- wlserver_mousewarp( global_focus.inputFocusWindow->GetGeometry().nWidth / 2, global_focus.inputFocusWindow->GetGeometry().nHeight / 2, 0, true );
- wlserver_unlock();
+ // Cannot simply XWarpPointer here as we immediately go on to
+ // do wlserver_mousefocus and need to update m_x and m_y of the cursor.
+ if ( pFocus->inputFocusWindow->GetFocus()->bResetToCorner )
+ {
+ wlserver_lock();
+ wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true );
+ wlserver_fake_mouse_pos( pFocus->inputFocusWindow->GetGeometry().nWidth - 1, pFocus->inputFocusWindow->GetGeometry().nHeight - 1 );
+ wlserver_unlock();
+ }
+ else if ( pFocus->inputFocusWindow->GetFocus()->bResetToCenter )
+ {
+ wlserver_lock();
+ wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true );
+ wlserver_unlock();
+ }
+
+ pFocus->inputFocusWindow->GetFocus()->bResetToCorner = false;
+ pFocus->inputFocusWindow->GetFocus()->bResetToCenter = false;
}
- global_focus.inputFocusWindow->GetFocus()->bResetToCorner = false;
- global_focus.inputFocusWindow->GetFocus()->bResetToCenter = false;
+ s_lastFocus = *pFocus;
+ s_lastFocus.pVirtualConnector = nullptr; // I don't want to keep a ref to this.
}
// Determine if we need to repaints
- if (previous_focus.overlayWindow != global_focus.overlayWindow ||
- previous_focus.externalOverlayWindow != global_focus.externalOverlayWindow ||
- previous_focus.notificationWindow != global_focus.notificationWindow ||
- previous_focus.overrideWindow != global_focus.overrideWindow)
+ if (previousLocalFocus.overlayWindow != pFocus->overlayWindow ||
+ previousLocalFocus.externalOverlayWindow != pFocus->externalOverlayWindow ||
+ previousLocalFocus.notificationWindow != pFocus->notificationWindow ||
+ previousLocalFocus.overrideWindow != pFocus->overrideWindow)
{
hasRepaintNonBasePlane = true;
}
- if (previous_focus.focusWindow != global_focus.focusWindow)
+ if (previousLocalFocus.focusWindow != pFocus->focusWindow)
{
hasRepaint = true;
}
@@ -3862,70 +3946,73 @@ determine_and_apply_focus()
const char *focused_keyboard_display = root_ctx->xwayland_server->get_nested_display_name();
const char *focused_mouse_display = root_ctx->xwayland_server->get_nested_display_name();
- if ( global_focus.focusWindow )
+ if ( pFocus->focusWindow )
{
- focusedWindow = (unsigned long)global_focus.focusWindow->id();
- focusedBaseAppId = global_focus.focusWindow->appID;
- focusedAppId = global_focus.inputFocusWindow->appID;
- focused_display = get_win_display_name(global_focus.focusWindow);
- focusWindow_pid = global_focus.focusWindow->pid;
+ focusedWindow = (unsigned long)pFocus->focusWindow->id();
+ focusedBaseAppId = pFocus->focusWindow->appID;
+ focusedAppId = pFocus->inputFocusWindow->appID;
+ focused_display = get_win_display_name(pFocus->focusWindow);
+ focusWindow_pid = pFocus->focusWindow->pid;
}
g_focusedBaseAppId = (uint32_t)focusedAppId;
- if ( global_focus.inputFocusWindow )
+ if ( pFocus->inputFocusWindow )
{
- focused_mouse_display = get_win_display_name(global_focus.inputFocusWindow);
+ focused_mouse_display = get_win_display_name(pFocus->inputFocusWindow);
}
- if ( global_focus.keyboardFocusWindow )
+ if ( pFocus->keyboardFocusWindow )
{
- focused_keyboard_display = get_win_display_name(global_focus.keyboardFocusWindow);
+ focused_keyboard_display = get_win_display_name(pFocus->keyboardFocusWindow);
}
- if ( steamMode )
+ if ( pFocus == GetCurrentFocus() )
{
- XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppAtom, XA_CARDINAL, 32, PropModeReplace,
- (unsigned char *)&focusedAppId, focusedAppId != 0 ? 1 : 0 );
+ if ( steamMode )
+ {
+ XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppAtom, XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)&focusedAppId, focusedAppId != 0 ? 1 : 0 );
- XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppGfxAtom, XA_CARDINAL, 32, PropModeReplace,
- (unsigned char *)&focusedBaseAppId, focusedBaseAppId != 0 ? 1 : 0 );
- }
+ XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppGfxAtom, XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)&focusedBaseAppId, focusedBaseAppId != 0 ? 1 : 0 );
+ }
- XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedWindowAtom, XA_CARDINAL, 32, PropModeReplace,
- (unsigned char *)&focusedWindow, focusedWindow != 0 ? 1 : 0 );
+ XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedWindowAtom, XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)&focusedWindow, focusedWindow != 0 ? 1 : 0 );
- XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusDisplay, XA_CARDINAL, 32, PropModeReplace,
- (unsigned char *)focused_display, strlen(focused_display) + 1 );
+ XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusDisplay, XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)focused_display, strlen(focused_display) + 1 );
- XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeMouseFocusDisplay, XA_CARDINAL, 32, PropModeReplace,
- (unsigned char *)focused_mouse_display, strlen(focused_mouse_display) + 1 );
+ XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeMouseFocusDisplay, XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)focused_mouse_display, strlen(focused_mouse_display) + 1 );
- XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeKeyboardFocusDisplay, XA_CARDINAL, 32, PropModeReplace,
- (unsigned char *)focused_keyboard_display, strlen(focused_keyboard_display) + 1 );
+ XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeKeyboardFocusDisplay, XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)focused_keyboard_display, strlen(focused_keyboard_display) + 1 );
- XFlush( root_ctx->dpy );
+ XFlush( root_ctx->dpy );
+ }
// Sort out fading.
- if (global_focus.focusWindow && previous_focus.focusWindow != global_focus.focusWindow)
+ if (pFocus->focusWindow && previousLocalFocus.focusWindow != pFocus->focusWindow)
{
if ( g_FadeOutDuration != 0 && !g_bFirstFrame )
{
if ( g_HeldCommits[ HELD_COMMIT_FADE ] == nullptr )
{
- global_focus.fadeWindow = previous_focus.focusWindow;
+ pFocus->fadeWindow = previousLocalFocus.focusWindow;
g_HeldCommits[ HELD_COMMIT_FADE ] = g_HeldCommits[ HELD_COMMIT_BASE ];
g_bPendingFade = true;
}
else
{
// If we end up fading back to what we were going to fade to, cancel the fade.
- if ( global_focus.fadeWindow != nullptr && global_focus.focusWindow == global_focus.fadeWindow )
+ if ( pFocus->fadeWindow != nullptr && pFocus->focusWindow == pFocus->fadeWindow )
{
g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr;
g_bPendingFade = false;
fadeOutStartTime = 0;
- global_focus.fadeWindow = nullptr;
+ pFocus->fadeWindow = nullptr;
}
}
}
@@ -3934,32 +4021,32 @@ determine_and_apply_focus()
if ( !cv_paint_debug_pause_base_plane )
{
// Update last focus commit
- if ( global_focus.focusWindow &&
- previous_focus.focusWindow != global_focus.focusWindow &&
- !global_focus.focusWindow->isSteamStreamingClient )
+ if ( pFocus->focusWindow &&
+ previousLocalFocus.focusWindow != pFocus->focusWindow &&
+ !pFocus->focusWindow->isSteamStreamingClient )
{
- get_window_last_done_commit( global_focus.focusWindow, g_HeldCommits[ HELD_COMMIT_BASE ] );
+ get_window_last_done_commit( pFocus->focusWindow, g_HeldCommits[ HELD_COMMIT_BASE ] );
}
}
// Set SDL window title
- if ( GetBackend()->GetNestedHints() )
+ if ( pFocus->GetNestedHints() )
{
- if ( global_focus.focusWindow )
+ if ( pFocus->focusWindow )
{
- GetBackend()->GetNestedHints()->SetVisible( true );
- GetBackend()->GetNestedHints()->SetTitle( global_focus.focusWindow->title );
- GetBackend()->GetNestedHints()->SetIcon( global_focus.focusWindow->icon );
+ pFocus->GetNestedHints()->SetVisible( true );
+ pFocus->GetNestedHints()->SetTitle( pFocus->focusWindow->title );
+ pFocus->GetNestedHints()->SetIcon( pFocus->focusWindow->icon );
}
else
{
- GetBackend()->GetNestedHints()->SetVisible( false );
+ pFocus->GetNestedHints()->SetVisible( false );
}
}
// Some games such as Disgaea PC (405900) don't take controller input until
// the window is first clicked on despite it having focus.
- if ( global_focus.inputFocusWindow && global_focus.inputFocusWindow->appID == 405900 )
+ if ( pFocus->inputFocusWindow && pFocus->inputFocusWindow->appID == 405900 )
{
auto now = get_time_in_milliseconds();
@@ -3970,7 +4057,7 @@ determine_and_apply_focus()
wlserver_unlock();
}
- global_focus.ulCurrentFocusSerial = GetFocusSerial();
+ pFocus->ulCurrentFocusSerial = GetFocusSerial();
}
static void
@@ -4649,19 +4736,23 @@ destroy_win(xwayland_ctx_t *ctx, Window id, bool gone, bool fade)
if (ctx->currentKeyboardFocusWindow == id && gone)
ctx->currentKeyboardFocusWindow = None;
- // Global Focus
- if (x11_win(global_focus.focusWindow) == id && gone)
- global_focus.focusWindow = nullptr;
- if (x11_win(global_focus.inputFocusWindow) == id && gone)
- global_focus.inputFocusWindow = nullptr;
- if (x11_win(global_focus.overlayWindow) == id && gone)
- global_focus.overlayWindow = nullptr;
- if (x11_win(global_focus.notificationWindow) == id && gone)
- global_focus.notificationWindow = nullptr;
- if (x11_win(global_focus.overrideWindow) == id && gone)
- global_focus.overrideWindow = nullptr;
- if (x11_win(global_focus.fadeWindow) == id && gone)
- global_focus.fadeWindow = nullptr;
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pFocus = &iter.second;
+ // Global Focus
+ if (x11_win(pFocus->focusWindow) == id && gone)
+ pFocus->focusWindow = nullptr;
+ if (x11_win(pFocus->inputFocusWindow) == id && gone)
+ pFocus->inputFocusWindow = nullptr;
+ if (x11_win(pFocus->overlayWindow) == id && gone)
+ pFocus->overlayWindow = nullptr;
+ if (x11_win(pFocus->notificationWindow) == id && gone)
+ pFocus->notificationWindow = nullptr;
+ if (x11_win(pFocus->overrideWindow) == id && gone)
+ pFocus->overrideWindow = nullptr;
+ if (x11_win(pFocus->fadeWindow) == id && gone)
+ pFocus->fadeWindow = nullptr;
+ }
MakeFocusDirty();
@@ -4721,18 +4812,23 @@ handle_wl_surface_id(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, uint32_t surfac
return;
}
- // If we already focused on our side and are handling this late,
- // let wayland know now.
- if ( w == global_focus.inputFocusWindow )
- wlserver_mousefocus( main_surface, INT32_MAX, INT32_MAX );
+ global_focus_t *pCurrentFocus = GetCurrentFocus();
+ if ( pCurrentFocus )
+ {
+ // If we already focused on our side and are handling this late,
+ // let wayland know now.
+ if ( w == pCurrentFocus->inputFocusWindow )
+ wlserver_mousefocus( main_surface, INT32_MAX, INT32_MAX );
- steamcompmgr_win_t *keyboardFocusWindow = global_focus.inputFocusWindow;
+ steamcompmgr_win_t *keyboardFocusWindow = pCurrentFocus->inputFocusWindow;
- if ( keyboardFocusWindow && keyboardFocusWindow->inputFocusMode == 2 )
- keyboardFocusWindow = global_focus.focusWindow;
+ if ( gamescope::VirtualConnectorIsSingleOutput() &&
+ keyboardFocusWindow && keyboardFocusWindow->inputFocusMode == 2 )
+ keyboardFocusWindow = pCurrentFocus->focusWindow;
- if ( w == keyboardFocusWindow )
- wlserver_keyboardfocus( main_surface );
+ if ( w == keyboardFocusWindow )
+ wlserver_keyboardfocus( main_surface );
+ }
// Pull the first buffer out of that window, if needed
xwayland_surface_commit( current_surface );
@@ -4987,22 +5083,22 @@ handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev)
if (ev->selection == ctx->atoms.clipboard)
{
- if ( GetBackend()->GetNestedHints() )
- {
- GetBackend()->GetNestedHints()->SetSelection( szContents, GAMESCOPE_SELECTION_CLIPBOARD );
- }
- else
+ // if ( GetBackend()->GetNestedHints() )
+ // {
+ // //GetBackend()->GetNestedHints()->SetSelection()
+ // }
+ // else
{
gamescope_set_selection( contents, GAMESCOPE_SELECTION_CLIPBOARD );
}
}
else if (ev->selection == ctx->atoms.primarySelection)
{
- if ( GetBackend()->GetNestedHints() )
- {
- GetBackend()->GetNestedHints()->SetSelection( szContents, GAMESCOPE_SELECTION_PRIMARY );
- }
- else
+ // if ( GetBackend()->GetNestedHints() )
+ // {
+ // //GetBackend()->GetNestedHints()->SetSelection()
+ // }
+ // else
{
gamescope_set_selection( contents, GAMESCOPE_SELECTION_PRIMARY );
}
@@ -5078,7 +5174,7 @@ steamcompmgr_flush_frame_done( steamcompmgr_win_t *w )
static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vblank_idx )
{
- if ( GetBackend()->IsVRRActive() )
+ if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() )
return true;
bool bSendCallback = true;
@@ -5309,9 +5405,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
globalScaleRatio = overscanScaleRatio * zoomScaleRatio;
- if (global_focus.focusWindow)
+ for ( auto &iter : g_VirtualConnectorFocuses )
{
- hasRepaint = true;
+ global_focus_t *pFocus = &iter.second;
+ if (pFocus->focusWindow)
+ {
+ hasRepaint = true;
+ }
}
MakeFocusDirty();
@@ -5322,9 +5422,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
globalScaleRatio = overscanScaleRatio * zoomScaleRatio;
- if (global_focus.focusWindow)
+ for ( auto &iter : g_VirtualConnectorFocuses )
{
- hasRepaint = true;
+ global_focus_t *pFocus = &iter.second;
+ if (pFocus->focusWindow)
+ {
+ hasRepaint = true;
+ }
}
MakeFocusDirty();
@@ -5356,10 +5460,14 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
{
get_win_title(ctx, w, ev->atom);
- if (ev->window == x11_win(global_focus.focusWindow))
+ for ( auto &iter : g_VirtualConnectorFocuses )
{
- if ( GetBackend()->GetNestedHints() )
- GetBackend()->GetNestedHints()->SetTitle( w->title );
+ global_focus_t *pFocus = &iter.second;
+ if (ev->window == x11_win(pFocus->focusWindow))
+ {
+ if ( pFocus->GetNestedHints() )
+ pFocus->GetNestedHints()->SetTitle( w->title );
+ }
}
}
}
@@ -5371,10 +5479,14 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
{
get_win_icon(ctx, w);
- if (ev->window == x11_win(global_focus.focusWindow))
+ for ( auto &iter : g_VirtualConnectorFocuses )
{
- if ( GetBackend()->GetNestedHints() )
- GetBackend()->GetNestedHints()->SetIcon( w->icon );
+ global_focus_t *pFocus = &iter.second;
+ if (ev->window == x11_win(pFocus->focusWindow))
+ {
+ if ( pFocus->GetNestedHints() )
+ pFocus->GetNestedHints()->SetIcon( w->icon );
+ }
}
}
}
@@ -5802,49 +5914,53 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(server_id);
if (server)
{
- if (global_focus.focusWindow &&
- global_focus.focusWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.focusWindow->xwayland().ctx == server->ctx.get())
- global_focus.focusWindow = nullptr;
-
- if (global_focus.inputFocusWindow &&
- global_focus.inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.inputFocusWindow->xwayland().ctx == server->ctx.get())
- global_focus.inputFocusWindow = nullptr;
-
- if (global_focus.overlayWindow &&
- global_focus.overlayWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.overlayWindow->xwayland().ctx == server->ctx.get())
- global_focus.overlayWindow = nullptr;
-
- if (global_focus.externalOverlayWindow &&
- global_focus.externalOverlayWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.externalOverlayWindow->xwayland().ctx == server->ctx.get())
- global_focus.externalOverlayWindow = nullptr;
-
- if (global_focus.notificationWindow &&
- global_focus.notificationWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.notificationWindow->xwayland().ctx == server->ctx.get())
- global_focus.notificationWindow = nullptr;
-
- if (global_focus.overrideWindow &&
- global_focus.overrideWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.overrideWindow->xwayland().ctx == server->ctx.get())
- global_focus.overrideWindow = nullptr;
-
- if (global_focus.keyboardFocusWindow &&
- global_focus.keyboardFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.keyboardFocusWindow->xwayland().ctx == server->ctx.get())
- global_focus.keyboardFocusWindow = nullptr;
-
- if (global_focus.fadeWindow &&
- global_focus.fadeWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
- global_focus.fadeWindow->xwayland().ctx == server->ctx.get())
- global_focus.fadeWindow = nullptr;
-
- if (global_focus.cursor &&
- global_focus.cursor->getCtx() == server->ctx.get())
- global_focus.cursor = nullptr;
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pFocus = &iter.second;
+ if (pFocus->focusWindow &&
+ pFocus->focusWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->focusWindow->xwayland().ctx == server->ctx.get())
+ pFocus->focusWindow = nullptr;
+
+ if (pFocus->inputFocusWindow &&
+ pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->inputFocusWindow->xwayland().ctx == server->ctx.get())
+ pFocus->inputFocusWindow = nullptr;
+
+ if (pFocus->overlayWindow &&
+ pFocus->overlayWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->overlayWindow->xwayland().ctx == server->ctx.get())
+ pFocus->overlayWindow = nullptr;
+
+ if (pFocus->externalOverlayWindow &&
+ pFocus->externalOverlayWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->externalOverlayWindow->xwayland().ctx == server->ctx.get())
+ pFocus->externalOverlayWindow = nullptr;
+
+ if (pFocus->notificationWindow &&
+ pFocus->notificationWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->notificationWindow->xwayland().ctx == server->ctx.get())
+ pFocus->notificationWindow = nullptr;
+
+ if (pFocus->overrideWindow &&
+ pFocus->overrideWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->overrideWindow->xwayland().ctx == server->ctx.get())
+ pFocus->overrideWindow = nullptr;
+
+ if (pFocus->keyboardFocusWindow &&
+ pFocus->keyboardFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->keyboardFocusWindow->xwayland().ctx == server->ctx.get())
+ pFocus->keyboardFocusWindow = nullptr;
+
+ if (pFocus->fadeWindow &&
+ pFocus->fadeWindow->type == steamcompmgr_win_type_t::XWAYLAND &&
+ pFocus->fadeWindow->xwayland().ctx == server->ctx.get())
+ pFocus->fadeWindow = nullptr;
+
+ if (pFocus->cursor &&
+ pFocus->cursor->getCtx() == server->ctx.get())
+ pFocus->cursor = nullptr;
+ }
wlserver_lock();
g_SteamCompMgrWaiter.RemoveWaitable( server->ctx.get() );
@@ -5938,6 +6054,8 @@ steamcompmgr_exit(void)
}
}
+ g_VirtualConnectorFocuses.clear();
+
gamescope::IBackend::Set( nullptr );
wlserver_lock();
@@ -6041,6 +6159,9 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
uint32_t j;
for ( j = 0; j < w->commit_queue.size(); j++ )
{
+ if (w->commit_queue[ j ]->feedback.has_value())
+ w->engineName = w->commit_queue[ j ]->feedback->vk_engine_name;
+
if ( w->commit_queue[ j ]->commitID == commitID )
{
gpuvis_trace_printf( "commit %lu done", w->commit_queue[ j ]->commitID );
@@ -6051,47 +6172,54 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
// Window just got a new available commit, determine if that's worth a repaint
- // If this is an overlay that we're presenting, repaint
- if ( w == global_focus.overlayWindow && w->opacity != TRANSLUCENT )
+ for ( auto &iter : g_VirtualConnectorFocuses )
{
- hasRepaintNonBasePlane = true;
- }
+ global_focus_t *pFocus = &iter.second;
- if ( w == global_focus.notificationWindow && w->opacity != TRANSLUCENT )
- {
- hasRepaintNonBasePlane = true;
- }
+ // If this is an overlay that we're presenting, repaint
+ if ( w == pFocus->overlayWindow && w->opacity != TRANSLUCENT )
+ {
+ hasRepaintNonBasePlane = true;
+ }
- // If this is an external overlay, repaint
- if ( w == global_focus.externalOverlayWindow && w->opacity != TRANSLUCENT )
- {
- hasRepaintNonBasePlane = true;
- }
+ if ( w == pFocus->notificationWindow && w->opacity != TRANSLUCENT )
+ {
+ hasRepaintNonBasePlane = true;
+ }
- if ( w->outdatedInteractiveFocus )
- {
- MakeFocusDirty();
- w->outdatedInteractiveFocus = false;
- }
+ // If this is an external overlay, repaint
+ if ( w == pFocus->externalOverlayWindow && w->opacity != TRANSLUCENT )
+ {
+ hasRepaintNonBasePlane = true;
+ }
- // If this is the main plane, repaint
- if ( w == global_focus.focusWindow && !w->isSteamStreamingClient )
- {
- if ( !cv_paint_debug_pause_base_plane )
- g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ];
- hasRepaint = true;
- }
+ // If this is the main plane, repaint
+ if ( w == pFocus->focusWindow && !w->isSteamStreamingClient )
+ {
+ if ( !cv_paint_debug_pause_base_plane )
+ g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ];
+ hasRepaint = true;
- if ( w == global_focus.overrideWindow )
- {
- hasRepaintNonBasePlane = true;
+ focusWindow_engine = w->engineName;
+ }
+
+ if ( w == pFocus->overrideWindow )
+ {
+ hasRepaintNonBasePlane = true;
+ }
+
+ if ( w->isSteamStreamingClientVideo && pFocus->focusWindow && pFocus->focusWindow->isSteamStreamingClient )
+ {
+ if ( !cv_paint_debug_pause_base_plane )
+ g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ];
+ hasRepaint = true;
+ }
}
- if ( w->isSteamStreamingClientVideo && global_focus.focusWindow && global_focus.focusWindow->isSteamStreamingClient )
+ if ( w->outdatedInteractiveFocus )
{
- if ( !cv_paint_debug_pause_base_plane )
- g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ];
- hasRepaint = true;
+ MakeFocusDirty();
+ w->outdatedInteractiveFocus = false;
}
break;
@@ -6456,11 +6584,16 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
int fence = -1;
if ( newCommit != nullptr )
{
+ global_focus_t *pCurrentFocus = GetCurrentFocus();
+
+ static bool bMangoappSocketDisable = env_to_bool( getenv( "GAMESCOPE_MANGOAPP_SOCKET_DISABLE" ));
+
// Whether or not to nudge mango app when this commit is done.
- const bool mango_nudge = ( w == global_focus.focusWindow && !w->isSteamStreamingClient ) ||
- ( global_focus.focusWindow && global_focus.focusWindow->isSteamStreamingClient && w->isSteamStreamingClientVideo );
+ const bool mango_nudge = pCurrentFocus && ( ( w == pCurrentFocus->focusWindow && !w->isSteamStreamingClient ) ||
+ ( pCurrentFocus->focusWindow && pCurrentFocus->focusWindow->isSteamStreamingClient && w->isSteamStreamingClientVideo ) )
+ && !bMangoappSocketDisable;
- bool bValidPreemptiveScale = reslistentry.pAcquirePoint && w == global_focus.focusWindow;
+ bool bValidPreemptiveScale = reslistentry.pAcquirePoint && pCurrentFocus && w == pCurrentFocus->focusWindow;
bool bPreemptiveUpscale = bValidPreemptiveScale && newCommit->ShouldPreemptivelyUpscale();
bool bKnownReady = false;
@@ -7101,9 +7234,8 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_
xwm_log.errorf("Failed to load mouse cursor: %s", g_customCursorPath);
}
else
- {
- std::shared_ptr pHostCursor;
- if ( GetBackend()->GetNestedHints() && ( pHostCursor = GetBackend()->GetNestedHints()->GetHostCursor() ) )
+ {
+ if ( std::shared_ptr pHostCursor = gamescope::GetX11HostCursor() )
{
ctx->cursor->setCursorImage(
reinterpret_cast( pHostCursor->pPixels.data() ),
@@ -7149,7 +7281,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush =
*needs_flush = true;
}
- bool in_use = GetBackend()->IsVRRActive();
+ bool in_use = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive();
if ( in_use != g_bVRRInUse_CachedValue || force )
{
uint32_t in_use_value = in_use ? 1 : 0;
@@ -7234,18 +7366,22 @@ void steamcompmgr_check_xdg(bool vblank, uint64_t vblank_idx)
{
if (wlserver_xdg_dirty())
{
- if (global_focus.focusWindow && global_focus.focusWindow->type == steamcompmgr_win_type_t::XDG)
- global_focus.focusWindow = nullptr;
- if (global_focus.inputFocusWindow && global_focus.inputFocusWindow->type == steamcompmgr_win_type_t::XDG)
- global_focus.inputFocusWindow = nullptr;
- if (global_focus.overlayWindow && global_focus.overlayWindow->type == steamcompmgr_win_type_t::XDG)
- global_focus.overlayWindow = nullptr;
- if (global_focus.notificationWindow && global_focus.notificationWindow->type == steamcompmgr_win_type_t::XDG)
- global_focus.notificationWindow = nullptr;
- if (global_focus.overrideWindow && global_focus.overrideWindow->type == steamcompmgr_win_type_t::XDG)
- global_focus.overrideWindow = nullptr;
- if (global_focus.fadeWindow && global_focus.fadeWindow->type == steamcompmgr_win_type_t::XDG)
- global_focus.fadeWindow = nullptr;
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pFocus = &iter.second;
+ if (pFocus->focusWindow && pFocus->focusWindow->type == steamcompmgr_win_type_t::XDG)
+ pFocus->focusWindow = nullptr;
+ if (pFocus->inputFocusWindow && pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XDG)
+ pFocus->inputFocusWindow = nullptr;
+ if (pFocus->overlayWindow && pFocus->overlayWindow->type == steamcompmgr_win_type_t::XDG)
+ pFocus->overlayWindow = nullptr;
+ if (pFocus->notificationWindow && pFocus->notificationWindow->type == steamcompmgr_win_type_t::XDG)
+ pFocus->notificationWindow = nullptr;
+ if (pFocus->overrideWindow && pFocus->overrideWindow->type == steamcompmgr_win_type_t::XDG)
+ pFocus->overrideWindow = nullptr;
+ if (pFocus->fadeWindow && pFocus->fadeWindow->type == steamcompmgr_win_type_t::XDG)
+ pFocus->fadeWindow = nullptr;
+ }
g_steamcompmgr_xdg_wins = wlserver_get_xdg_shell_windows();
MakeFocusDirty();
}
@@ -7520,7 +7656,12 @@ steamcompmgr_main(int argc, char **argv)
globalScaleRatio = overscanScaleRatio * zoomScaleRatio;
- determine_and_apply_focus();
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pFocus = &iter.second;
+ if ( pFocus->IsDirty() )
+ determine_and_apply_focus( pFocus );
+ }
if ( readyPipeFD != -1 )
{
@@ -7593,7 +7734,7 @@ steamcompmgr_main(int argc, char **argv)
const bool bIsVBlankFromTimer = vblank;
// We can always vblank if VRR.
- const bool bVRR = GetBackend()->IsVRRActive();
+ const bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive();
if ( bVRR )
vblank = true;
@@ -7618,8 +7759,85 @@ steamcompmgr_main(int argc, char **argv)
flush_root = true;
}
- if (global_focus.IsDirty())
- determine_and_apply_focus();
+ static gamescope::VirtualConnectorStrategy s_eLastVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy;
+ gamescope::VirtualConnectorStrategy eVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy;
+
+ if ( eVirtualConnectorStrategy != s_eLastVirtualConnectorStrategy )
+ {
+ // If our virtual connector strategy changes, clear out our virtual connector
+ // global focuses.
+ g_VirtualConnectorFocuses.clear();
+ s_eLastVirtualConnectorStrategy = eVirtualConnectorStrategy;
+ }
+
+#if 0
+ bool bDirtyFocuses = false;
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pFocus = &iter.second;
+ if ( pFocus->IsDirty() )
+ {
+ bDirtyFocuses = true;
+ break;
+ }
+ }
+#endif
+
+ // XXX: Need to look into why this doesn't work.
+ // if ( bDirtyFocuses )
+ {
+ // TODO(misyl): Improve this situation, it's kind of a mess.
+ // We could/should make this event driven rather than solving
+ // per-frame.
+
+ std::vector newKeys;
+
+ auto focusWindows = GetGlobalPossibleFocusWindows();
+ for ( steamcompmgr_win_t *pWindow : focusWindows )
+ {
+ gamescope::VirtualConnectorKey_t ulKey = pWindow->GetVirtualConnectorKey( eVirtualConnectorStrategy );
+ if ( !gamescope::Algorithm::Contains( newKeys, ulKey ) )
+ newKeys.emplace_back( ulKey );
+ }
+ std::sort( newKeys.begin(), newKeys.end() );
+
+ std::vector oldKeys;
+ for ( const auto &iter : g_VirtualConnectorFocuses )
+ oldKeys.emplace_back( iter.first );
+ std::sort( oldKeys.begin(), oldKeys.end() );
+
+ std::vector diffKeys;
+
+ std::set_symmetric_difference(oldKeys.begin(), oldKeys.end(),
+ newKeys.begin(), newKeys.end(),
+ std::back_inserter(diffKeys),
+ [](auto& a, auto& b) { return a < b; });
+
+ for ( gamescope::VirtualConnectorKey_t ulKey : diffKeys )
+ {
+ bool bIsSteam = gamescope::VirtualConnectorKeyIsSteam( ulKey );
+
+ if ( gamescope::Algorithm::Contains( newKeys, ulKey ) )
+ {
+ g_VirtualConnectorFocuses[ulKey] = global_focus_t
+ {
+ .ulVirtualFocusKey = ulKey,
+ .pVirtualConnector = GetBackend()->UsesVirtualConnectors() ? GetBackend()->CreateVirtualConnector( ulKey ) : nullptr,
+ };
+ }
+ else if ( !bIsSteam ) // Never remove Steam's virtual conn ector.
+ {
+ g_VirtualConnectorFocuses.erase( ulKey );
+ }
+ }
+
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pFocus = &iter.second;
+ if ( pFocus->IsDirty() )
+ determine_and_apply_focus( pFocus );
+ }
+ }
// If our DRM state is out-of-date, refresh it. This might update
// the output size.
@@ -7832,7 +8050,7 @@ steamcompmgr_main(int argc, char **argv)
XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback);
}
- g_ColorMgmt.pending.appHDRMetadata = app_hdr_metadata;
+ g_ColorMgmt.pending.appHDRMetadata = app_hdr_metadata;
flush_root = true;
}
}
@@ -7840,10 +8058,16 @@ steamcompmgr_main(int argc, char **argv)
// Handles if we got a commit for the window we want to focus
// to switch to it for painting (outdatedInteractiveFocus)
// Doesn't realllly matter but avoids an extra frame of being on the wrong window.
- if (global_focus.IsDirty())
- determine_and_apply_focus();
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pFocus = &iter.second;
+ if ( pFocus->IsDirty() )
+ determine_and_apply_focus( pFocus );
+ }
- if ( window_is_steam( global_focus.focusWindow ) )
+ // XXX(misyl): This is bad! We shouldnt change the upscaler like this at all!!!
+ // We should move this to business logic in paint_window or something!
+ if ( GetCurrentFocus() && window_is_steam( GetCurrentFocus()->focusWindow ) )
{
g_bSteamIsActiveWindow = true;
g_upscaleScaler = GamescopeUpscaleScaler::FIT;
@@ -7861,157 +8085,160 @@ steamcompmgr_main(int argc, char **argv)
if ( is_fading_out() )
hasRepaint = true;
- if ( vblank )
- {
- if ( global_focus.cursor )
- global_focus.cursor->UpdatePosition();
- }
+ bool bPainted = false;
- if ( GetBackend()->GetNestedHints() && !g_bForceRelativeMouse )
- {
- const bool bImageEmpty =
- ( global_focus.cursor && global_focus.cursor->imageEmpty() ) &&
- ( !window_is_steam( global_focus.inputFocusWindow ) );
+ static int nIgnoredOverlayRepaints = 0;
- const bool bHasPointerConstraint = global_focus.cursor->IsConstrained();
+ if ( !hasRepaintNonBasePlane )
+ nIgnoredOverlayRepaints = 0;
- uint32_t uAppId = global_focus.inputFocusWindow
- ? global_focus.inputFocusWindow->appID
- : 0;
+ if ( cv_adaptive_sync_ignore_overlay )
+ nIgnoredOverlayRepaints = 0;
- const bool bExcludedAppId = uAppId && gamescope::Algorithm::Contains( s_uRelativeMouseFilteredAppids, uAppId );
+ for ( auto &iter : g_VirtualConnectorFocuses )
+ {
+ global_focus_t *pPaintFocus = &iter.second;
- const bool bRelativeMouseMode = bImageEmpty && bHasPointerConstraint && !bExcludedAppId;
+ if ( vblank )
+ {
+ if ( pPaintFocus->cursor )
+ pPaintFocus->cursor->UpdatePosition();
+ }
- GetBackend()->GetNestedHints()->SetRelativeMouseMode( bRelativeMouseMode );
- }
+ if ( pPaintFocus->GetNestedHints() && !g_bForceRelativeMouse )
+ {
+ const bool bImageEmpty =
+ ( pPaintFocus->cursor && pPaintFocus->cursor->imageEmpty() ) &&
+ ( !window_is_steam( pPaintFocus->inputFocusWindow ) );
- static int nIgnoredOverlayRepaints = 0;
+ const bool bHasPointerConstraint = pPaintFocus->cursor && pPaintFocus->cursor->IsConstrained();
- if ( !hasRepaintNonBasePlane )
- nIgnoredOverlayRepaints = 0;
+ uint32_t uAppId = pPaintFocus->inputFocusWindow
+ ? pPaintFocus->inputFocusWindow->appID
+ : 0;
- if ( cv_adaptive_sync_ignore_overlay )
- nIgnoredOverlayRepaints = 0;
+ const bool bExcludedAppId = uAppId && gamescope::Algorithm::Contains( s_uRelativeMouseFilteredAppids, uAppId );
- // HACK: Disable tearing if we have an overlay to avoid stutters right now
- // TODO: Fix properly.
- const bool bHasOverlay = ( global_focus.overlayWindow && global_focus.overlayWindow->opacity ) ||
- ( global_focus.externalOverlayWindow && global_focus.externalOverlayWindow->opacity ) ||
- ( global_focus.overrideWindow && global_focus.focusWindow && !global_focus.focusWindow->isSteamStreamingClient && global_focus.overrideWindow->opacity );
+ const bool bRelativeMouseMode = bImageEmpty && bHasPointerConstraint && !bExcludedAppId;
- // If we are running behind, allow tearing.
+ pPaintFocus->GetNestedHints()->SetRelativeMouseMode( bRelativeMouseMode );
+ }
- const bool bForceRepaint = vblank && g_bForceRepaint.exchange(false);
- const bool bForceSyncFlip = bForceRepaint || is_fading_out();
+ // HACK: Disable tearing if we have an overlay to avoid stutters right now
+ // TODO: Fix properly.
+ const bool bHasOverlay = ( pPaintFocus->overlayWindow && pPaintFocus->overlayWindow->opacity ) ||
+ ( pPaintFocus->externalOverlayWindow && pPaintFocus->externalOverlayWindow->opacity ) ||
+ ( pPaintFocus->overrideWindow && pPaintFocus->focusWindow && !pPaintFocus->focusWindow->isSteamStreamingClient && pPaintFocus->overrideWindow->opacity );
- // If we are compositing, always force sync flips because we currently wait
- // for composition to finish before submitting.
- // If we want to do async + composite, we should set up syncfile stuff and have DRM wait on it.
- const bool bSurfaceWantsAsync = (g_HeldCommits[HELD_COMMIT_BASE] != nullptr && g_HeldCommits[HELD_COMMIT_BASE]->async);
- const bool bTearing = cv_tearing_enabled && GetBackend()->SupportsTearing() && bSurfaceWantsAsync;
+ // If we are running behind, allow tearing.
- enum class FlipType
- {
- Normal,
- Async,
- VRR,
- };
+ const bool bForceRepaint = g_bForceRepaint.exchange(false);
+ const bool bForceSyncFlip = bForceRepaint || is_fading_out();
- FlipType eFlipType = FlipType::Normal;
+ // If we are compositing, always force sync flips because we currently wait
+ // for composition to finish before submitting.
+ // If we want to do async + composite, we should set up syncfile stuff and have DRM wait on it.
+ const bool bSurfaceWantsAsync = (g_HeldCommits[HELD_COMMIT_BASE] != nullptr && g_HeldCommits[HELD_COMMIT_BASE]->async);
+ const bool bTearing = cv_tearing_enabled && GetBackend()->SupportsTearing() && bSurfaceWantsAsync;
- if ( bForceSyncFlip )
- eFlipType = FlipType::Normal;
- else if ( bVRR )
- eFlipType = FlipType::VRR;
- else if ( bTearing )
- {
- eFlipType = FlipType::Async;
+ enum class FlipType
+ {
+ Normal,
+ Async,
+ VRR,
+ };
- if ( nIgnoredOverlayRepaints )
- eFlipType = FlipType::Normal;
- if ( bHasOverlay ) // Don't tear if the Steam or perf overlay is up atm.
+ FlipType eFlipType = FlipType::Normal;
+
+ if ( bForceSyncFlip )
eFlipType = FlipType::Normal;
- if ( GetVBlankTimer().WasCompositing() )
+ else if ( bVRR )
+ eFlipType = FlipType::VRR;
+ else if ( bTearing )
+ {
+ eFlipType = FlipType::Async;
+
+ if ( nIgnoredOverlayRepaints )
+ eFlipType = FlipType::Normal;
+ if ( bHasOverlay ) // Don't tear if the Steam or perf overlay is up atm.
+ eFlipType = FlipType::Normal;
+ if ( GetVBlankTimer().WasCompositing() )
+ eFlipType = FlipType::Normal;
+ }
+ else
eFlipType = FlipType::Normal;
- }
- else
- eFlipType = FlipType::Normal;
- if ( g_pLastReshadeEffect && ( g_pLastReshadeEffect->flags() & ReshadeEffectFlag::AlwaysScanout ) )
- {
- eFlipType = FlipType::Normal;
- }
+ bool bShouldPaint = false;
- bool bShouldPaint = false;
-
- if ( GetBackend()->IsVisible() )
- {
- switch ( eFlipType )
+ //if ( GetBackend()->IsVisible() )
+ if ( true )
{
- case FlipType::Normal:
- {
- bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || bForceSyncFlip );
- break;
- }
-
- case FlipType::Async:
+ switch ( eFlipType )
{
- bShouldPaint = hasRepaint;
+ case FlipType::Normal:
+ {
+ bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || bForceSyncFlip );
+ break;
+ }
- if ( vblank && !bShouldPaint && hasRepaintNonBasePlane )
- nIgnoredOverlayRepaints++;
+ case FlipType::Async:
+ {
+ bShouldPaint = hasRepaint;
- break;
- }
+ if ( vblank && !bShouldPaint && hasRepaintNonBasePlane )
+ nIgnoredOverlayRepaints++;
- case FlipType::VRR:
- {
- bShouldPaint = hasRepaint;
+ break;
+ }
- if ( bIsVBlankFromTimer )
+ case FlipType::VRR:
{
- if ( hasRepaintNonBasePlane )
+ bShouldPaint = hasRepaint;
+
+ if ( bIsVBlankFromTimer )
{
- if ( nIgnoredOverlayRepaints >= cv_adaptive_sync_overlay_cycles )
- {
- // If we hit vblank and we previously punted on drawing an overlay
- // we should go ahead and draw now.
- bShouldPaint = true;
- }
- else if ( !bShouldPaint )
+ if ( hasRepaintNonBasePlane )
{
- // If we hit vblank (ie. fastest refresh cycle since last commit),
- // and we aren't painting and we have a pending overlay, then:
- // defer it until the next game update or next true vblank.
- if ( !cv_adaptive_sync_ignore_overlay )
- nIgnoredOverlayRepaints++;
+ if ( nIgnoredOverlayRepaints >= cv_adaptive_sync_overlay_cycles )
+ {
+ // If we hit vblank and we previously punted on drawing an overlay
+ // we should go ahead and draw now.
+ bShouldPaint = true;
+ }
+ else if ( !bShouldPaint )
+ {
+ // If we hit vblank (ie. fastest refresh cycle since last commit),
+ // and we aren't painting and we have a pending overlay, then:
+ // defer it until the next game update or next true vblank.
+ if ( !cv_adaptive_sync_ignore_overlay )
+ nIgnoredOverlayRepaints++;
+ }
}
}
- }
- // If we have a pending page flip and doing VRR, lets not do another...
- if ( GetBackend()->PresentationFeedback().CurrentPresentsInFlight() != 0 )
- bShouldPaint = false;
+ // If we have a pending page flip and doing VRR, lets not do another...
+ if ( GetBackend()->GetCurrentConnector()->PresentationFeedback().CurrentPresentsInFlight() != 0 )
+ bShouldPaint = false;
- break;
+ break;
+ }
}
}
- }
- else
- {
- bShouldPaint = false;
- }
+ else
+ {
+ bShouldPaint = false;
+ }
- if ( g_pLastReshadeEffect && ( g_pLastReshadeEffect->flags() & ReshadeEffectFlag::AlwaysScanout ) )
- {
- bShouldPaint = true;
+ if ( bShouldPaint )
+ {
+ paint_all( pPaintFocus, eFlipType == FlipType::Async );
+
+ bPainted = true;
+ }
}
- if ( bShouldPaint )
+ if ( bPainted )
{
- paint_all( eFlipType == FlipType::Async );
-
hasRepaint = false;
hasRepaintNonBasePlane = false;
nIgnoredOverlayRepaints = 0;
@@ -8039,14 +8266,14 @@ steamcompmgr_main(int argc, char **argv)
update_vrr_atoms(root_ctx, false, &flush_root);
- if (global_focus.cursor)
+ if (GetCurrentFocus() && GetCurrentFocus()->cursor)
{
- global_focus.cursor->checkSuspension();
+ GetCurrentFocus()->cursor->checkSuspension();
- if (global_focus.cursor->needs_server_flush())
+ if (GetCurrentFocus()->cursor->needs_server_flush())
{
flush_root = true;
- global_focus.cursor->inform_flush();
+ GetCurrentFocus()->cursor->inform_flush();
}
}
@@ -8063,34 +8290,6 @@ steamcompmgr_main(int argc, char **argv)
steamcompmgr_exit();
}
-void steamcompmgr_send_frame_done_to_focus_window()
-{
- struct timespec now;
- clock_gettime(CLOCK_MONOTONIC, &now);
-
- if ( global_focus.focusWindow && global_focus.focusWindow->xwayland().surface.main_surface )
- {
- wlserver_lock();
- wlserver_send_frame_done( global_focus.focusWindow->xwayland().surface.main_surface , &now );
- wlserver_unlock();
- }
-}
-
-gamescope_xwayland_server_t *steamcompmgr_get_focused_server()
-{
- if (global_focus.inputFocusWindow != nullptr)
- {
- gamescope_xwayland_server_t *server = NULL;
- for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++)
- {
- if (server->ctx->focus.inputFocusWindow == global_focus.inputFocusWindow)
- return server;
- }
- }
-
- return wlserver_get_xwayland_server(0);
-}
-
struct wlr_surface *steamcompmgr_get_server_input_surface( size_t idx )
{
gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx );
@@ -8121,7 +8320,7 @@ struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xw
MouseCursor *steamcompmgr_get_current_cursor()
{
- return global_focus.cursor;
+ return GetCurrentFocus()->cursor;
}
MouseCursor *steamcompmgr_get_server_cursor(uint32_t idx)
diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp
index 9f384c461c..4aa12f76a0 100644
--- a/src/steamcompmgr.hpp
+++ b/src/steamcompmgr.hpp
@@ -136,7 +136,6 @@ void nudge_steamcompmgr( void );
void force_repaint( void );
extern void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns );
-gamescope_xwayland_server_t *steamcompmgr_get_focused_server();
struct wlr_surface *steamcompmgr_get_server_input_surface( size_t idx );
wlserver_vk_swapchain_feedback* steamcompmgr_get_base_layer_swapchain_feedback();
@@ -144,6 +143,7 @@ struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xw
extern gamescope::VBlankTime g_SteamCompMgrVBlankTime;
extern pid_t focusWindow_pid;
+extern std::shared_ptr focusWindow_engine;
void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server);
void gamescope_set_selection(std::string contents, GamescopeSelection eSelection);
diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp
index 095694e493..c4b616d65d 100644
--- a/src/steamcompmgr_shared.hpp
+++ b/src/steamcompmgr_shared.hpp
@@ -140,6 +140,8 @@ struct steamcompmgr_win_t {
bool unlockedForFrameCallback = false;
bool receivedDoneCommit = false;
+ std::shared_ptr engineName;
+
std::vector< gamescope::Rc > commit_queue;
std::shared_ptr> icon;
@@ -209,6 +211,21 @@ struct steamcompmgr_win_t {
else
return nullptr;
}
+
+ gamescope::VirtualConnectorKey_t GetVirtualConnectorKey( gamescope::VirtualConnectorStrategy eStrategy )
+ {
+ switch ( eStrategy )
+ {
+ default:
+ case gamescope::VirtualConnectorStrategies::SingleApplication:
+ case gamescope::VirtualConnectorStrategies::SteamControlled:
+ return 0;
+ case gamescope::VirtualConnectorStrategies::PerAppId:
+ return static_cast( this->appID );
+ case gamescope::VirtualConnectorStrategies::PerWindow:
+ return static_cast( this->seq );
+ }
+ }
};
namespace gamescope
diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp
index 2fd0ec45ef..f036d000a8 100644
--- a/src/vblankmanager.cpp
+++ b/src/vblankmanager.cpp
@@ -100,7 +100,7 @@ namespace gamescope
const int nRefreshRate = GetRefresh();
const uint64_t ulRefreshInterval = mHzToRefreshCycle( nRefreshRate );
- bool bVRR = GetBackend()->IsVRRActive();
+ bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive();
uint64_t ulOffset = 0;
if ( !bVRR )
{
@@ -358,7 +358,22 @@ namespace gamescope
if ( !m_bRunning )
return;
- VBlankScheduleTime schedule = GetBackend()->FrameSync();
+ VBlankScheduleTime schedule;
+ if ( GetBackend()->GetCurrentConnector() )
+ {
+ schedule = GetBackend()->GetCurrentConnector()->FrameSync();
+ }
+ else
+ {
+ // If we don't currently have a connector, make up some dummy refresh cycle.
+ sleep_for_nanos( mHzToRefreshCycle( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh ) );
+ uint64_t ulNow = get_time_in_nanos();
+ schedule = VBlankScheduleTime
+ {
+ .ulTargetVBlank = ulNow + 3'000'000,
+ .ulScheduledWakeupPoint = ulNow,
+ };
+ }
const uint64_t ulWakeupTime = get_time_in_nanos();
{
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 78a86ee0e2..32554849a3 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -840,7 +840,8 @@ static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, st
uint32_t vk_colorspace,
uint32_t vk_composite_alpha,
uint32_t vk_pre_transform,
- uint32_t vk_clipped)
+ uint32_t vk_clipped,
+ const char *vk_engine_name)
{
wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource );
if ( wl_info )
@@ -852,6 +853,7 @@ static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, st
.vk_composite_alpha = VkCompositeAlphaFlagBitsKHR(vk_composite_alpha),
.vk_pre_transform = VkSurfaceTransformFlagBitsKHR(vk_pre_transform),
.vk_clipped = VkBool32(vk_clipped),
+ .vk_engine_name = std::make_shared(vk_engine_name),
.hdr_metadata_blob = nullptr,
});
}
@@ -1844,9 +1846,11 @@ bool wlserver_init( void ) {
char szEISocket[ 64 ];
snprintf( szEISocket, sizeof( szEISocket ), "%s-ei", wlserver.wl_display_name );
- g_InputServer = std::make_unique();
- if ( g_InputServer->Init( szEISocket ) )
+ std::unique_ptr pInputServer = std::make_unique();
+ if ( pInputServer->Init( szEISocket ) )
{
+ g_InputServer = std::move( pInputServer );
+
setenv( "LIBEI_SOCKET", szEISocket, 1 );
g_LibEisWaiter.AddWaitable( g_InputServer.get() );
wl_log.infof( "Successfully initialized libei for input emulation!" );