Эх сурвалжийг харах

Wayland: add fullscreen pause exceptions (active-only + ignore app_id) (#434)

* feat: add Wayland fullscreen pause exceptions

* refactor: extract Wayland fullscreen relevance helper

* refactor: dedupe Wayland fullscreen relevance checks

* fix: ignore empty fullscreen pause appId filters
acters 9 сар өмнө
parent
commit
f79c29f067

+ 2 - 0
README.md

@@ -181,6 +181,8 @@ If you're one of those developers, feel free to open an issue to get your projec
 | `--disable-mouse` | Disable mouse interaction |
 | `--disable-parallax` | Disable parallax effect on backgrounds that support it |
 | `--no-fullscreen-pause` | Prevent pausing while fullscreen apps are running |
+| `--fullscreen-pause-only-active` | Wayland only: pause only when a fullscreen window is active |
+| `--fullscreen-pause-ignore-appid <val>` | Wayland only: ignore fullscreen windows whose app_id contains `<val>` (repeatable) |
 
 ---
 

+ 16 - 0
src/WallpaperEngine/Application/ApplicationContext.cpp

@@ -390,6 +390,22 @@ void ApplicationContext::loadSettingsFromArgv () {
                 this->settings.render.pauseOnFullscreen = false;
             });
 
+        performanceGroup.add_argument ("--fullscreen-pause-only-active")
+            .help ("Wayland only: pause only when a fullscreen window is active (activated)")
+            .flag ()
+            .action ([this](const std::string& value) -> void {
+                this->settings.render.pauseOnFullscreenOnlyWhenActive = true;
+            });
+
+        performanceGroup.add_argument ("--fullscreen-pause-ignore-appid")
+            .help ("Wayland only: ignore fullscreen windows whose app_id contains this value (repeatable)")
+            .action ([this](const std::string& value) -> void {
+                if (!value.empty ()) {
+                    this->settings.render.fullscreenPauseIgnoreAppIds.push_back (value);
+                }
+            })
+            .append ();
+
     auto& audioGroup = program.add_group ("Sound settings");
     auto& audioSettingsGroup = audioGroup.add_mutually_exclusive_group (false);
 

+ 12 - 0
src/WallpaperEngine/Application/ApplicationContext.h

@@ -93,6 +93,16 @@ class ApplicationContext {
             int maximumFPS;
             /** Indicates if pausing should happen when something goes fullscreen */
             bool pauseOnFullscreen;
+            /**
+             * Wayland-only: if true, only consider fullscreen toplevels that are also activated.
+             * Useful for compositors with "virtual" fullscreen windows (e.g. scrollable tiling).
+             */
+            bool pauseOnFullscreenOnlyWhenActive;
+            /**
+             * Wayland-only: list of app_id substrings to ignore for fullscreen pause.
+             * Example: "firefox" will match "org.mozilla.firefox".
+             */
+            std::vector<std::string> fullscreenPauseIgnoreAppIds;
 
             struct {
                 /** The window size used in explicit window */
@@ -154,6 +164,8 @@ class ApplicationContext {
             .mode = NORMAL_WINDOW,
             .maximumFPS = 30,
             .pauseOnFullscreen = true,
+            .pauseOnFullscreenOnlyWhenActive = false,
+            .fullscreenPauseIgnoreAppIds = {},
             .window = {
                 .geometry = {},
                 .clamp = TextureFlags_ClampUVs,

+ 119 - 27
src/WallpaperEngine/Render/Drivers/Detectors/WaylandFullScreenDetector.cpp

@@ -4,20 +4,57 @@
 #include "WallpaperEngine/Render/Drivers/VideoFactories.h"
 #include "wlr-foreign-toplevel-management-unstable-v1-protocol.h"
 #include <cstring>
+#include <string>
+#include <string_view>
 
 namespace WallpaperEngine::Render::Drivers::Detectors {
 
 namespace {
 
+bool icontains (std::string_view haystack, std::string_view needle) {
+    if (needle.empty ())
+        return true;
+    if (haystack.empty ())
+        return false;
+
+    auto toLower = [](unsigned char c) {
+        if (c >= 'A' && c <= 'Z')
+            return static_cast<unsigned char> (c - 'A' + 'a');
+        return c;
+    };
+
+    for (size_t i = 0; i + needle.size () <= haystack.size (); ++i) {
+        bool match = true;
+        for (size_t j = 0; j < needle.size (); ++j) {
+            if (toLower (static_cast<unsigned char> (haystack[i + j])) !=
+                toLower (static_cast<unsigned char> (needle[j]))) {
+                match = false;
+                break;
+            }
+        }
+        if (match)
+            return true;
+    }
+
+    return false;
+}
+
 struct FullscreenState {
-    bool pending;
-    bool current;
-    uint32_t* const count;
+    bool pending = false;
+    bool current = false;
+    bool pendingActivated = false;
+    bool currentActivated = false;
+    std::string appId {};
+    WallpaperEngine::Render::Drivers::Detectors::WaylandFullscreenDetectorCallbackData* const data;
 };
 
 void toplevelHandleTitle (void*, struct zwlr_foreign_toplevel_handle_v1*, const char*) {}
 
-void toplevelHandleAppId (void*, struct zwlr_foreign_toplevel_handle_v1*, const char*) {}
+void toplevelHandleAppId (void* data, struct zwlr_foreign_toplevel_handle_v1*, const char* appId) {
+    const auto state = static_cast<FullscreenState*> (data);
+    if (appId)
+        state->appId = appId;
+}
 
 void toplevelHandleOutputEnter (void*, struct zwlr_foreign_toplevel_handle_v1*, struct wl_output*) {}
 
@@ -25,47 +62,93 @@ void toplevelHandleOutputLeave (void*, struct zwlr_foreign_toplevel_handle_v1*,
 
 void toplevelHandleParent (void*, struct zwlr_foreign_toplevel_handle_v1*, struct zwlr_foreign_toplevel_handle_v1*) {}
 
-void toplevelHandleState (void* data, struct zwlr_foreign_toplevel_handle_v1* handle, struct wl_array* state) {
-    const auto fullscreen = static_cast<FullscreenState*> (data);
+void toplevelHandleState (void* data, struct zwlr_foreign_toplevel_handle_v1*, struct wl_array* state) {
+    const auto toplevel = static_cast<FullscreenState*> (data);
     const auto begin = static_cast<uint32_t*> (state->data);
 
-    fullscreen->pending = false;
-    for (auto it = begin; it < begin + state->size / sizeof (uint32_t); ++it)
+    toplevel->pending = false;
+    toplevel->pendingActivated = false;
+
+    for (auto it = begin; it < begin + state->size / sizeof (uint32_t); ++it) {
         if (*it == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)
-            fullscreen->pending = true;
+            toplevel->pending = true;
+        if (*it == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
+            toplevel->pendingActivated = true;
+    }
+}
+
+bool isRelevant (
+    const WallpaperEngine::Application::ApplicationContext& ctx,
+    const bool fullscreen,
+    const bool activated,
+    const std::string& appId
+) {
+    if (!fullscreen)
+        return false;
+
+    if (ctx.settings.render.pauseOnFullscreenOnlyWhenActive && !activated)
+        return false;
+
+    if (!appId.empty ()) {
+        for (const auto& ignore : ctx.settings.render.fullscreenPauseIgnoreAppIds) {
+            if (ignore.empty ())
+                continue;
+            if (icontains (appId, ignore))
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool isFullscreenRelevant (const FullscreenState& toplevel) {
+    const auto& ctx = toplevel.data->detector->getApplicationContext ();
+    return isRelevant (ctx, toplevel.pending, toplevel.pendingActivated, toplevel.appId);
+}
+
+bool isCurrentlyRelevant (const FullscreenState& toplevel) {
+    const auto& ctx = toplevel.data->detector->getApplicationContext ();
+    return isRelevant (ctx, toplevel.current, toplevel.currentActivated, toplevel.appId);
 }
 
 void toplevelHandleDone (void* data, struct zwlr_foreign_toplevel_handle_v1* handle) {
-    const auto fullscreen = static_cast<FullscreenState*> (data);
-    if (fullscreen->current != fullscreen->pending) {
-        fullscreen->current = fullscreen->pending;
-        if (fullscreen->current) {
-            ++(*fullscreen->count);
+    const auto toplevel = static_cast<FullscreenState*> (data);
+
+    const bool pendingRelevant = isFullscreenRelevant (*toplevel);
+    const bool currentRelevant = isCurrentlyRelevant (*toplevel);
+
+    if (currentRelevant != pendingRelevant) {
+        if (pendingRelevant) {
+            ++(*toplevel->data->fullscreenCount);
         } else {
-            // sanity check
-            if (*fullscreen->count == 0) {
+            if (*toplevel->data->fullscreenCount == 0) {
                 sLog.error ("Fullscreen count underflow!!!");
             } else {
-                --(*fullscreen->count);
+                --(*toplevel->data->fullscreenCount);
             }
         }
     }
+
+    toplevel->current = toplevel->pending;
+    toplevel->currentActivated = toplevel->pendingActivated;
 }
 
 void toplevelHandleClosed (void* data, struct zwlr_foreign_toplevel_handle_v1* handle) {
-    const auto fullscreen = static_cast<FullscreenState*> (data);
+    const auto toplevel = static_cast<FullscreenState*> (data);
+
+    // If it was counted as relevant fullscreen, remove it.
+    const bool currentRelevant = isCurrentlyRelevant (*toplevel);
 
-    if (fullscreen->current) {
-        // sanity check
-        if (*fullscreen->count == 0) {
+    if (currentRelevant) {
+        if (*toplevel->data->fullscreenCount == 0) {
             sLog.error ("Fullscreen count underflow!!!");
         } else {
-            --(*fullscreen->count);
+            --(*toplevel->data->fullscreenCount);
         }
     }
 
     zwlr_foreign_toplevel_handle_v1_destroy (handle);
-    delete fullscreen;
+    delete toplevel;
 }
 
 constexpr struct zwlr_foreign_toplevel_handle_v1_listener toplevelHandleListener = {
@@ -81,8 +164,16 @@ constexpr struct zwlr_foreign_toplevel_handle_v1_listener toplevelHandleListener
 
 void handleToplevel (void* data, struct zwlr_foreign_toplevel_manager_v1* manager,
                      struct zwlr_foreign_toplevel_handle_v1* handle) {
-    const auto fullscreen = new FullscreenState {.count = static_cast<uint32_t*> (data)};
-    zwlr_foreign_toplevel_handle_v1_add_listener (handle, &toplevelHandleListener, fullscreen);
+    const auto cb = static_cast<WallpaperEngine::Render::Drivers::Detectors::WaylandFullscreenDetectorCallbackData*> (data);
+    const auto toplevel = new FullscreenState {
+        .pending = false,
+        .current = false,
+        .pendingActivated = false,
+        .currentActivated = false,
+        .appId = {},
+        .data = cb,
+    };
+    zwlr_foreign_toplevel_handle_v1_add_listener (handle, &toplevelHandleListener, toplevel);
 }
 
 void handleFinished (void* data, struct zwlr_foreign_toplevel_manager_v1* manager) {
@@ -103,7 +194,7 @@ void handleGlobal (void* data, struct wl_registry* registry, uint32_t name, cons
             wl_registry_bind (registry, name, &zwlr_foreign_toplevel_manager_v1_interface, 3));
         if (detector->m_toplevelManager) {
             zwlr_foreign_toplevel_manager_v1_add_listener (detector->m_toplevelManager, &toplevelManagerListener,
-                                                           &detector->m_fullscreenCount);
+                                                           &detector->m_callbackData);
         }
     }
 }
@@ -118,7 +209,8 @@ constexpr struct wl_registry_listener registryListener = {
 };
 
 WaylandFullScreenDetector::WaylandFullScreenDetector (Application::ApplicationContext& appContext) :
-    FullScreenDetector (appContext) {
+    FullScreenDetector (appContext),
+    m_callbackData {.detector = this, .fullscreenCount = &m_fullscreenCount} {
     m_display = wl_display_connect (nullptr);
     if (!m_display)
         sLog.exception ("Failed to query wayland display");

+ 10 - 0
src/WallpaperEngine/Render/Drivers/Detectors/WaylandFullScreenDetector.h

@@ -11,6 +11,14 @@ struct wl_registry;
 struct zwlr_foreign_toplevel_manager_v1;
 
 namespace WallpaperEngine::Render::Drivers::Detectors {
+
+class WaylandFullScreenDetector;
+
+struct WaylandFullscreenDetectorCallbackData {
+    WaylandFullScreenDetector* detector;
+    uint32_t* fullscreenCount;
+};
+
 class WaylandFullScreenDetector final : public FullScreenDetector {
   public:
     explicit WaylandFullScreenDetector (Application::ApplicationContext& appContext);
@@ -25,6 +33,8 @@ class WaylandFullScreenDetector final : public FullScreenDetector {
 
     uint32_t m_fullscreenCount = 0;
 
+    WaylandFullscreenDetectorCallbackData m_callbackData {};
+
     friend void handleGlobal (
         void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version);
 };