diff --git a/src/server/focus.cpp b/src/server/focus.cpp index 7a13ad7..4b62788 100644 --- a/src/server/focus.cpp +++ b/src/server/focus.cpp @@ -82,6 +82,10 @@ namespace umbriel { m_server.registry().promote(view); view->setUrgent(false); + if (reason == FocusReason::XdgActivation || reason == FocusReason::ForeignActivation) { + view->applyDeferredUnfullscreen(); + } + // Keep workspace focus while exclusive layer-shell holds the seat; refocus applies it later. Still clear activation // chrome so the previous window does not stay visually focused. Overview owns the seat the same way, but keeps the // chrome so card borders track the focused window; the keyboard enter replays when it closes. diff --git a/src/view/view.cpp b/src/view/view.cpp index 7eda712..c7b1b81 100644 --- a/src/view/view.cpp +++ b/src/view/view.cpp @@ -14,6 +14,7 @@ #include "view/xdg_size.h" // clang-format off #include +#include #include #include "wlr.h" // clang-format on @@ -1813,8 +1814,10 @@ namespace umbriel { // fullscreen strip the moment the user scrolls away. Deny unfullscreen from deactivated windows; the scheduled // configure re-asserts the fullscreen state (spec-compliant). if (!requested && !m_toplevel->scheduled.activated) { + m_deferredUnfullscreen = true; + m_deferredUnfullscreenAt = std::chrono::steady_clock::now(); kLog.debug( - "request_fullscreen denied for deactivated '{}'", m_toplevel->app_id != nullptr ? m_toplevel->app_id : "?" + "request_fullscreen parked for deactivated '{}'", m_toplevel->app_id != nullptr ? m_toplevel->app_id : "?" ); wlr_xdg_surface_schedule_configure(m_toplevel->base); return; @@ -1840,6 +1843,25 @@ namespace umbriel { setFullscreen(!m_toplevel->scheduled.fullscreen); } + void View::applyDeferredUnfullscreen() { + constexpr auto kDeferredUnfullscreenGrace = std::chrono::milliseconds(750); + if (!m_deferredUnfullscreen) { + return; + } + m_deferredUnfullscreen = false; + if (!m_toplevel->base->initialized + || std::chrono::steady_clock::now() - m_deferredUnfullscreenAt > kDeferredUnfullscreenGrace) { + return; + } + if (m_toplevel->scheduled.fullscreen || m_toplevel->current.fullscreen) { + kLog.debug( + "deferred unfullscreen applied on activation for '{}'", + m_toplevel->app_id != nullptr ? m_toplevel->app_id : "?" + ); + setFullscreen(false); + } + } + void View::toggleFloating() { setFloating(m_tiled); } void View::restorePinnedSceneParent() { @@ -2081,6 +2103,7 @@ namespace umbriel { } void View::setFullscreen(bool fullscreen) { + m_deferredUnfullscreen = false; kLog.debug( "set_fullscreen '{}' [{}] -> {} (tiled={}, ws_active={})", m_toplevel->app_id != nullptr ? m_toplevel->app_id : "?", static_cast(this), fullscreen, m_tiled, diff --git a/src/view/view.h b/src/view/view.h index eaa113e..59a5aac 100644 --- a/src/view/view.h +++ b/src/view/view.h @@ -125,6 +125,7 @@ namespace umbriel { void applyFullscreenLayout(bool animate = false); // Compositor-driven fullscreen toggle (keybind); client requests use handleRequestFullscreen. void toggleFullscreen(); + void applyDeferredUnfullscreen(); void setMaximizedToEdges(bool maximized); void toggleMaximizedToEdges(); // Detach from the scrolling layout (float) or re-insert as a tiled column. @@ -326,6 +327,8 @@ namespace umbriel { // 0 until the first frame tick after arming; the grace deadline counts // from there so a stalled frame clock cannot expire it instantly. uint64_t m_unfullscreenGraceStartMsec = 0; + bool m_deferredUnfullscreen = false; + std::chrono::steady_clock::time_point m_deferredUnfullscreenAt{}; // Geometry at unfullscreen time; a commit with a different geometry means // the client accepted windowed mode and the grace can end early. wlr_box m_unfullscreenGeometry{};