mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
fix(desktop): a game's window follows its own window mode (#2094)
* fix(desktop): a game's window follows its own window mode Desktop games ran in a titled gamescope window whatever mode they asked for. gamescope now fills the desktop for a fullscreen or borderless game and matches the window size of a windowed one. * Use the p30 gamescope test build * fix(desktop): a windowed game too big for the desktop is maximised * fix(desktop): a decorated game window stays a window * Use the published gamescope p21
This commit is contained in:
1 parent
f3d6efbcfa
commit
bba40cd69c
5 files changed
+265
-3
No files matched your search
@@ -260,3 +260,9 @@ the X pointer stays far from the seat position while input moves, gamescope take
|
||||
each frame's motion to the X pointer itself until the game stops warping. Absolute motion also sends relative
|
||||
motion, as a real compositor does, so XInput2 raw motion reaches games that read only that. A touch moves the
|
||||
pointer without relative motion, so tapping the screen does not turn the camera.
|
||||
|
||||
## 0132 sdl, steamcompmgr: follow a game's window mode
|
||||
With `--follow-game-window-mode`, the outer window of a nested SDL gamescope follows the focused game window. A window
|
||||
asking for `_NET_WM_STATE_FULLSCREEN`, or a Steam game covering the X screen without minimise or maximise functions or
|
||||
decorations in `_MOTIF_WM_HINTS`, makes the outer window fullscreen on the desktop. Anything else makes it a normal window of the
|
||||
game window's size, or a maximised one when that size does not fit the desktop with its title bar and panel. A change is applied only when the wanted mode changes, so a manual resize or Super+F stays.
|
||||
@@ -0,0 +1,254 @@
|
||||
diff --git a/src/Backends/SDLBackend.cpp b/src/Backends/SDLBackend.cpp
|
||||
index 58af150..b050183 100644
|
||||
--- a/src/Backends/SDLBackend.cpp
|
||||
+++ b/src/Backends/SDLBackend.cpp
|
||||
@@ -51,6 +51,7 @@ namespace gamescope
|
||||
GAMESCOPE_SDL_EVENT_VISIBLE,
|
||||
GAMESCOPE_SDL_EVENT_GRAB,
|
||||
GAMESCOPE_SDL_EVENT_CURSOR,
|
||||
+ GAMESCOPE_SDL_EVENT_WINDOW_MODE,
|
||||
|
||||
GAMESCOPE_SDL_EVENT_COUNT,
|
||||
};
|
||||
@@ -114,6 +115,7 @@ namespace gamescope
|
||||
virtual void SetCursorImage( std::shared_ptr<INestedHints::CursorInfo> info ) override;
|
||||
virtual void SetRelativeMouseMode( bool bRelative ) override;
|
||||
virtual void SetVisible( bool bVisible ) override;
|
||||
+ virtual void SetWindowMode( bool bFullscreen, int nWidth, int nHeight ) override;
|
||||
virtual void SetTitle( std::shared_ptr<std::string> szTitle ) override;
|
||||
virtual void SetIcon( std::shared_ptr<std::vector<uint32_t>> uIconPixels ) override;
|
||||
virtual void SetSelection( std::shared_ptr<std::string> szContents, GamescopeSelection eSelection ) override;
|
||||
@@ -179,6 +181,7 @@ namespace gamescope
|
||||
void SetCursorImage( std::shared_ptr<INestedHints::CursorInfo> info );
|
||||
void SetRelativeMouseMode( bool bRelative );
|
||||
void SetVisible( bool bVisible );
|
||||
+ void SetWindowMode( bool bFullscreen, int nWidth, int nHeight );
|
||||
void SetTitle( std::shared_ptr<std::string> szTitle );
|
||||
void SetIcon( std::shared_ptr<std::vector<uint32_t>> uIconPixels );
|
||||
void SetSelection( std::shared_ptr<std::string> szContents, GamescopeSelection eSelection );
|
||||
@@ -193,6 +196,12 @@ namespace gamescope
|
||||
bool m_bShown = false;
|
||||
CSDLConnector m_Connector; // Window.
|
||||
uint32_t m_uUserEventIdBase = 0u;
|
||||
+ std::atomic<bool> m_bWantFullscreen = { false };
|
||||
+ std::atomic<int> m_nWantWidth = { 0 };
|
||||
+ std::atomic<int> m_nWantHeight = { 0 };
|
||||
+ bool m_bAppliedFullscreen = false;
|
||||
+ int m_nAppliedWidth = 0;
|
||||
+ int m_nAppliedHeight = 0;
|
||||
std::vector<const char *> m_pszInstanceExtensions;
|
||||
|
||||
std::thread m_SDLThread;
|
||||
@@ -372,6 +381,10 @@ namespace gamescope
|
||||
{
|
||||
m_pBackend->SetVisible( bVisible );
|
||||
}
|
||||
+ void CSDLConnector::SetWindowMode( bool bFullscreen, int nWidth, int nHeight )
|
||||
+ {
|
||||
+ m_pBackend->SetWindowMode( bFullscreen, nWidth, nHeight );
|
||||
+ }
|
||||
void CSDLConnector::SetTitle( std::shared_ptr<std::string> szTitle )
|
||||
{
|
||||
m_pBackend->SetTitle( std::move( szTitle ) );
|
||||
@@ -544,6 +557,13 @@ namespace gamescope
|
||||
m_bApplicationVisible = bVisible;
|
||||
PushUserEvent( GAMESCOPE_SDL_EVENT_VISIBLE );
|
||||
}
|
||||
+ void CSDLBackend::SetWindowMode( bool bFullscreen, int nWidth, int nHeight )
|
||||
+ {
|
||||
+ m_bWantFullscreen = bFullscreen;
|
||||
+ m_nWantWidth = nWidth;
|
||||
+ m_nWantHeight = nHeight;
|
||||
+ PushUserEvent( GAMESCOPE_SDL_EVENT_WINDOW_MODE );
|
||||
+ }
|
||||
void CSDLBackend::SetTitle( std::shared_ptr<std::string> szTitle )
|
||||
{
|
||||
m_pApplicationTitle = szTitle;
|
||||
@@ -894,6 +914,38 @@ namespace gamescope
|
||||
}
|
||||
}
|
||||
}
|
||||
+ else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_WINDOW_MODE ) )
|
||||
+ {
|
||||
+ const bool bFullscreen = m_bWantFullscreen;
|
||||
+ const int nWidth = bFullscreen ? 0 : m_nWantWidth.load();
|
||||
+ const int nHeight = bFullscreen ? 0 : m_nWantHeight.load();
|
||||
+
|
||||
+ if ( bFullscreen != m_bAppliedFullscreen || nWidth != m_nAppliedWidth || nHeight != m_nAppliedHeight )
|
||||
+ {
|
||||
+ m_bAppliedFullscreen = bFullscreen;
|
||||
+ m_nAppliedWidth = nWidth;
|
||||
+ m_nAppliedHeight = nHeight;
|
||||
+ g_bFullscreen = bFullscreen;
|
||||
+ SDL_SetWindowFullscreen( m_Connector.GetSDLWindow(), bFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 );
|
||||
+ if ( !bFullscreen )
|
||||
+ {
|
||||
+ static constexpr int k_nDesktopChromeHeight = 96;
|
||||
+
|
||||
+ SDL_Rect usable = {};
|
||||
+ const int nDisplay = SDL_GetWindowDisplayIndex( m_Connector.GetSDLWindow() );
|
||||
+ const bool bKnown = SDL_GetDisplayUsableBounds( nDisplay, &usable ) == 0;
|
||||
+ if ( bKnown && ( nWidth > usable.w || nHeight + k_nDesktopChromeHeight > usable.h ) )
|
||||
+ {
|
||||
+ SDL_MaximizeWindow( m_Connector.GetSDLWindow() );
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ SDL_RestoreWindow( m_Connector.GetSDLWindow() );
|
||||
+ SDL_SetWindowSize( m_Connector.GetSDLWindow(), nWidth, nHeight );
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ) )
|
||||
{
|
||||
std::shared_ptr<std::string> pAppTitle = m_pApplicationTitle;
|
||||
diff --git a/src/backend.h b/src/backend.h
|
||||
index 37b936e..fbfa7c7 100644
|
||||
--- a/src/backend.h
|
||||
+++ b/src/backend.h
|
||||
@@ -273,6 +273,7 @@ namespace gamescope
|
||||
virtual void SetRelativeMouseMode( bool bRelative ) = 0;
|
||||
virtual void SetCursorPositionHint( double flX, double flY ) {}
|
||||
virtual void SetVisible( bool bVisible ) = 0;
|
||||
+ virtual void SetWindowMode( bool bFullscreen, int nWidth, int nHeight ) {}
|
||||
virtual void SetTitle( std::shared_ptr<std::string> szTitle ) = 0;
|
||||
virtual void SetIcon( std::shared_ptr<std::vector<uint32_t>> uIconPixels ) = 0;
|
||||
virtual void SetSelection( std::shared_ptr<std::string> szContents, GamescopeSelection eSelection ) = 0;
|
||||
diff --git a/src/main.cpp b/src/main.cpp
|
||||
index eef4264..8f0ddd3 100644
|
||||
--- a/src/main.cpp
|
||||
+++ b/src/main.cpp
|
||||
@@ -73,6 +73,7 @@ const struct option *gamescope_options = (struct option[]){
|
||||
{ "rt", no_argument, nullptr, 0 },
|
||||
{ "prefer-vk-device", required_argument, 0 },
|
||||
{ "expose-wayland", no_argument, 0 },
|
||||
+ { "follow-game-window-mode", no_argument, 0 },
|
||||
{ "mouse-sensitivity", required_argument, nullptr, 's' },
|
||||
{ "mangoapp", no_argument, nullptr, 0 },
|
||||
{ "adaptive-sync", no_argument, nullptr, 0 },
|
||||
@@ -700,6 +701,7 @@ static void UpdateCompatEnvVars()
|
||||
int g_nPreferredOutputWidth = 0;
|
||||
int g_nPreferredOutputHeight = 0;
|
||||
bool g_bExposeWayland = false;
|
||||
+bool g_bFollowGameWindowMode = false;
|
||||
const char *g_sOutputName = nullptr;
|
||||
bool g_bDebugLayers = false;
|
||||
bool g_bForceDisableColorMgmt = false;
|
||||
@@ -838,6 +840,8 @@ int main(int argc, char **argv)
|
||||
cv_adaptive_sync = true;
|
||||
} else if (strcmp(opt_name, "expose-wayland") == 0) {
|
||||
g_bExposeWayland = true;
|
||||
+ } else if (strcmp(opt_name, "follow-game-window-mode") == 0) {
|
||||
+ g_bFollowGameWindowMode = true;
|
||||
} else if (strcmp(opt_name, "backend") == 0) {
|
||||
eCurrentBackend = parse_backend_name( optarg );
|
||||
} else if (strcmp(opt_name, "cursor-scale-height") == 0) {
|
||||
diff --git a/src/main.hpp b/src/main.hpp
|
||||
index b2a169f..48929f2 100644
|
||||
--- a/src/main.hpp
|
||||
+++ b/src/main.hpp
|
||||
@@ -134,6 +134,7 @@ extern int g_upscaleFilterSharpness;
|
||||
extern bool g_bBorderlessOutputWindow;
|
||||
|
||||
extern bool g_bExposeWayland;
|
||||
+extern bool g_bFollowGameWindowMode;
|
||||
|
||||
extern bool g_bRt;
|
||||
|
||||
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
|
||||
index c468fb3..3188b74 100644
|
||||
--- a/src/steamcompmgr.cpp
|
||||
+++ b/src/steamcompmgr.cpp
|
||||
@@ -4741,6 +4741,54 @@ static void set_wm_state( xwayland_ctx_t *ctx, Window win, uint32_t state )
|
||||
sizeof(wmState) / sizeof(wmState[0]));
|
||||
}
|
||||
|
||||
+static bool
|
||||
+window_offers_frame_controls( xwayland_ctx_t *ctx, steamcompmgr_win_t *w )
|
||||
+{
|
||||
+ static constexpr unsigned long k_ulMotifFunctions = 1ul << 0;
|
||||
+ static constexpr unsigned long k_ulMotifMinimize = 1ul << 3;
|
||||
+ static constexpr unsigned long k_ulMotifMaximize = 1ul << 4;
|
||||
+ static constexpr unsigned long k_ulMotifDecorations = 1ul << 1;
|
||||
+
|
||||
+ Atom type;
|
||||
+ int format;
|
||||
+ unsigned long nitems;
|
||||
+ unsigned long bytesAfter;
|
||||
+ unsigned char *data = nullptr;
|
||||
+ if ( XGetWindowProperty( ctx->dpy, w->xwayland().id, ctx->atoms.motifWMHintsAtom, 0, 3, false,
|
||||
+ AnyPropertyType, &type, &format, &nitems, &bytesAfter, &data ) != Success )
|
||||
+ return false;
|
||||
+
|
||||
+ bool bControls = false;
|
||||
+ if ( data && format == 32 && nitems >= 3 )
|
||||
+ {
|
||||
+ const long *hints = (const long *)data;
|
||||
+ bControls = ( ( hints[0] & k_ulMotifFunctions ) && ( hints[1] & ( k_ulMotifMinimize | k_ulMotifMaximize ) ) ) ||
|
||||
+ ( ( hints[0] & k_ulMotifDecorations ) && hints[2] );
|
||||
+ }
|
||||
+ if ( data )
|
||||
+ XFree( data );
|
||||
+ return bControls;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+apply_game_window_mode( global_focus_t *pFocus )
|
||||
+{
|
||||
+ static constexpr int k_nSmallestFollowedWindow = 64;
|
||||
+
|
||||
+ steamcompmgr_win_t *w = pFocus->focusWindow;
|
||||
+ if ( !g_bFollowGameWindowMode || !pFocus->GetNestedHints() || !w || w->type != steamcompmgr_win_type_t::XWAYLAND )
|
||||
+ return;
|
||||
+
|
||||
+ xwayland_ctx_t *ctx = w->xwayland().ctx;
|
||||
+ const XWindowAttributes &a = w->xwayland().a;
|
||||
+ if ( a.width < k_nSmallestFollowedWindow || a.height < k_nSmallestFollowedWindow )
|
||||
+ return;
|
||||
+
|
||||
+ const bool bCoversScreen = a.x == 0 && a.y == 0 && a.width >= ctx->root_width && a.height >= ctx->root_height;
|
||||
+ const bool bFullscreen = w->isFullscreen || ( w->appID && bCoversScreen && !window_offers_frame_controls( ctx, w ) );
|
||||
+ pFocus->GetNestedHints()->SetWindowMode( bFullscreen, a.width, a.height );
|
||||
+}
|
||||
+
|
||||
static void
|
||||
fit_root_to_focus_window( xwayland_ctx_t *ctx )
|
||||
{
|
||||
@@ -5739,6 +5787,7 @@ determine_and_apply_focus( global_focus_t *pFocus )
|
||||
if ( pFocus->focusWindow )
|
||||
{
|
||||
pFocus->GetNestedHints()->SetVisible( true );
|
||||
+ apply_game_window_mode( pFocus );
|
||||
if ( previousLocalFocus.focusWindow != pFocus->focusWindow )
|
||||
{
|
||||
pFocus->GetNestedHints()->SetTitle( pFocus->focusWindow->title );
|
||||
@@ -7083,6 +7132,8 @@ static inline float santitize_float( float f )
|
||||
static void
|
||||
handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
|
||||
{
|
||||
+ if (ev->atom == ctx->atoms.motifWMHintsAtom)
|
||||
+ MakeFocusDirty();
|
||||
/* check if Trans property was changed */
|
||||
if (ev->atom == ctx->atoms.opacityAtom)
|
||||
{
|
||||
@@ -9216,6 +9267,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_
|
||||
ctx->atoms.winNormalAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_NORMAL", false);
|
||||
ctx->atoms.sizeHintsAtom = XInternAtom(ctx->dpy, "WM_NORMAL_HINTS", false);
|
||||
ctx->atoms.netWMStateFullscreenAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE_FULLSCREEN", false);
|
||||
+ ctx->atoms.motifWMHintsAtom = XInternAtom(ctx->dpy, "_MOTIF_WM_HINTS", false);
|
||||
ctx->atoms.netActiveWindowAtom = XInternAtom(ctx->dpy, "_NET_ACTIVE_WINDOW", false);
|
||||
ctx->atoms.netWMStateAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE", false);
|
||||
ctx->atoms.WMTransientForAtom = XInternAtom(ctx->dpy, "WM_TRANSIENT_FOR", false);
|
||||
diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp
|
||||
index 15c2e6b..525b155 100644
|
||||
--- a/src/xwayland_ctx.hpp
|
||||
+++ b/src/xwayland_ctx.hpp
|
||||
@@ -128,6 +128,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable
|
||||
Atom winNormalAtom;
|
||||
Atom sizeHintsAtom;
|
||||
Atom netWMStateFullscreenAtom;
|
||||
+ Atom motifWMHintsAtom;
|
||||
Atom netActiveWindowAtom;
|
||||
Atom netWMStateAtom;
|
||||
Atom WMTransientForAtom;
|
||||
@@ -1,3 +1,3 @@
|
||||
GAMESCOPE_REPO=Droid-Deck/DroidDeck-Components
|
||||
GAMESCOPE_TAG=gamescope-3.16.29-p20
|
||||
GAMESCOPE_SHA256=30a94becf21f3de4d1d7239c47b5168aab973f4e95981402a550c228929ba827
|
||||
GAMESCOPE_TAG=gamescope-3.16.29-p21
|
||||
GAMESCOPE_SHA256=c34d2728eb31a5ded35e5cb874e68dd4932a7fba4bcce5f99edf654108f97018
|
||||
@@ -10,7 +10,7 @@ window=0
|
||||
if [ "${BL_DESKTOP_GPU_WINDOW:-1}" != 0 ] && [ -n "${WAYLAND_DISPLAY:-}" ] && [ "$WAYLAND_DISPLAY" != wayland-0 ]; then
|
||||
window=1
|
||||
parent=$WAYLAND_DISPLAY
|
||||
args=(--backend sdl --expose-wayland -W "$width" -H "$height" -w "$width" -h "$height")
|
||||
args=(--backend sdl --expose-wayland --follow-game-window-mode -W "$width" -H "$height" -w "$width" -h "$height")
|
||||
else
|
||||
args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$width" -h "$height")
|
||||
fi
|
||||
|
||||
@@ -32,6 +32,7 @@ class DesktopGpuWindowTest(unittest.TestCase):
|
||||
def test_on_the_desktop_the_game_gets_a_window_of_its_own(self):
|
||||
run = self.run_gpu(WAYLAND_DISPLAY="wayland-1", MESA_VK_WSI_DEBUG="sw,linear")
|
||||
self.assertEqual(run["args"][:2], ["--backend", "sdl"])
|
||||
self.assertIn("--follow-game-window-mode", run["args"])
|
||||
self.assertNotIn("-f", run["args"])
|
||||
self.assertEqual(run["args"][-2:], ["/usr/local/bin/droiddeck-gpu-window", "game.exe"])
|
||||
self.assertEqual(run["env"]["WAYLAND_DISPLAY"], "wayland-1")
|
||||
@@ -44,6 +45,7 @@ class DesktopGpuWindowTest(unittest.TestCase):
|
||||
for env in ({"WAYLAND_DISPLAY": "wayland-1", "BL_DESKTOP_GPU_WINDOW": "0"}, {"WAYLAND_DISPLAY": "wayland-0"}, {}):
|
||||
run = self.run_gpu(**env)
|
||||
self.assertEqual(run["args"][:2], ["--backend", "wayland"])
|
||||
self.assertNotIn("--follow-game-window-mode", run["args"])
|
||||
self.assertIn("-f", run["args"])
|
||||
self.assertEqual(run["env"]["WAYLAND_DISPLAY"], "wayland-0")
|
||||
self.assertIsNone(run["env"]["MESA_VK_WSI_DEBUG"])
|
||||
|
||||
Reference in new issue
Block a user