From 01971cb6c7b7607329314645bf46e83d985eafeb Mon Sep 17 00:00:00 2001 From: Maximilian Seidler <78690852+PaideiaDilemma@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:13:54 +0200 Subject: [PATCH] session-lock: don't render workspaces when locked (#10865) Avoid rendering the workspace behind if we are locked --- src/config/ConfigDescriptions.hpp | 6 ++ src/config/ConfigManager.cpp | 1 + src/managers/AnimationManager.cpp | 2 +- src/managers/SessionLockManager.cpp | 83 ++++++++++++++-------- src/managers/SessionLockManager.hpp | 12 ++-- src/render/Renderer.cpp | 105 +++++++++++++++------------- src/render/Renderer.hpp | 1 + 7 files changed, 129 insertions(+), 81 deletions(-) diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 0cf5d87b7..927d67de1 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1218,6 +1218,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + SConfigOptionDescription{ + .value = "misc:session_lock_xray", + .description = "keep rendering workspaces below your lockscreen", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, SConfigOptionDescription{ .value = "misc:background_color", .description = "change the background color. (requires enabled disable_hyprland_logo)", diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 6154fcff5..bc198fac4 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -484,6 +484,7 @@ CConfigManager::CConfigManager() { registerConfigVar("misc:focus_on_activate", Hyprlang::INT{0}); registerConfigVar("misc:mouse_move_focuses_monitor", Hyprlang::INT{1}); registerConfigVar("misc:allow_session_lock_restore", Hyprlang::INT{0}); + registerConfigVar("misc:session_lock_xray", Hyprlang::INT{0}); registerConfigVar("misc:close_special_on_empty", Hyprlang::INT{1}); registerConfigVar("misc:background_color", Hyprlang::INT{0xff111111}); registerConfigVar("misc:new_window_takes_over_fullscreen", Hyprlang::INT{0}); diff --git a/src/managers/AnimationManager.cpp b/src/managers/AnimationManager.cpp index d1da1a361..333111e98 100644 --- a/src/managers/AnimationManager.cpp +++ b/src/managers/AnimationManager.cpp @@ -33,7 +33,7 @@ static int wlTick(SP self, void* data) { } CHyprAnimationManager::CHyprAnimationManager() { - m_animationTimer = SP(new CEventLoopTimer(std::chrono::microseconds(500), wlTick, nullptr)); + m_animationTimer = makeShared(std::chrono::microseconds(500), wlTick, nullptr); if (g_pEventLoopManager) // null in --verify-config mode g_pEventLoopManager->addTimer(m_animationTimer); diff --git a/src/managers/SessionLockManager.cpp b/src/managers/SessionLockManager.cpp index bfd819cf7..9ef37806e 100644 --- a/src/managers/SessionLockManager.cpp +++ b/src/managers/SessionLockManager.cpp @@ -3,9 +3,10 @@ #include "../config/ConfigValue.hpp" #include "../protocols/FractionalScale.hpp" #include "../protocols/SessionLock.hpp" -#include "../managers/SeatManager.hpp" #include "../render/Renderer.hpp" -#include "../managers/input/InputManager.hpp" +#include "./managers/SeatManager.hpp" +#include "./managers/input/InputManager.hpp" +#include "./managers/eventLoop/EventLoopManager.hpp" #include #include @@ -49,16 +50,19 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { static auto PALLOWRELOCK = CConfigValue("misc:allow_session_lock_restore"); if (PROTO::sessionLock->isLocked() && !*PALLOWRELOCK) { - Debug::log(LOG, "Cannot re-lock, misc:allow_session_lock_restore is disabled"); + LOGM(LOG, "Cannot re-lock, misc:allow_session_lock_restore is disabled"); pLock->sendDenied(); return; } - Debug::log(LOG, "Session got locked by {:x}", (uintptr_t)pLock.get()); + if (m_sessionLock && !clientDenied() && !clientLocked()) + return; // Not allowing to relock in case the old lock is still in a limbo + + LOGM(LOG, "Session got locked by {:x}", (uintptr_t)pLock.get()); m_sessionLock = makeUnique(); m_sessionLock->lock = pLock; - m_sessionLock->mLockTimer.reset(); + m_sessionLock->lockTimer.reset(); m_sessionLock->listeners.newSurface = pLock->m_events.newLockSurface.listen([this](const SP& surface) { const auto PMONITOR = surface->monitor(); @@ -87,14 +91,48 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { g_pCompositor->focusSurface(nullptr); g_pSeatManager->setGrab(nullptr); + m_sessionLock->sendDeniedTimer = makeShared( + // Within this arbitrary amount of time, a session-lock client is expected to create and commit a lock surface for each output. If the client fails to do that, it will be denied. + std::chrono::seconds(5), + [](auto, auto) { + if (!g_pSessionLockManager || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied()) + return; + + if (g_pCompositor->m_unsafeState || !g_pCompositor->m_aqBackend->hasSession() || !g_pCompositor->m_aqBackend->session->active) { + // Because the session is inactive, there is a good reason for why the client did't recieve locked or denied. + // We send locked, although this could lead to imperfect frames when we start to render again. + g_pSessionLockManager->m_sessionLock->lock->sendLocked(); + g_pSessionLockManager->m_sessionLock->hasSentLocked = true; + return; + } + + if (g_pSessionLockManager->m_sessionLock && g_pSessionLockManager->m_sessionLock->lock) { + g_pSessionLockManager->m_sessionLock->lock->sendDenied(); + g_pSessionLockManager->m_sessionLock->hasSentDenied = true; + } + }, + nullptr); + + if (m_sessionLock->sendDeniedTimer) + g_pEventLoopManager->addTimer(m_sessionLock->sendDeniedTimer); + // Normally the locked event is sent after each output rendered a lock screen frame. // When there are no outputs, send it right away. if (g_pCompositor->m_unsafeState) { + removeSendDeniedTimer(); m_sessionLock->lock->sendLocked(); m_sessionLock->hasSentLocked = true; } } +void CSessionLockManager::removeSendDeniedTimer() { + if (!m_sessionLock || !m_sessionLock->sendDeniedTimer) + return; + + g_pEventLoopManager->removeTimer(m_sessionLock->sendDeniedTimer); + m_sessionLock->sendDeniedTimer.reset(); +} + bool CSessionLockManager::isSessionLocked() { return PROTO::sessionLock->isLocked(); } @@ -115,28 +153,13 @@ WP CSessionLockManager::getSessionLockSurfaceForMonitor(uin return {}; } -// We don't want the red screen to flash. -float CSessionLockManager::getRedScreenAlphaForMonitor(uint64_t id) { - if (!m_sessionLock) - return 1.F; - - const auto& NOMAPPEDSURFACETIMER = m_sessionLock->mMonitorsWithoutMappedSurfaceTimers.find(id); - - if (NOMAPPEDSURFACETIMER == m_sessionLock->mMonitorsWithoutMappedSurfaceTimers.end()) { - m_sessionLock->mMonitorsWithoutMappedSurfaceTimers.emplace(id, CTimer()); - m_sessionLock->mMonitorsWithoutMappedSurfaceTimers[id].reset(); - return 0.f; - } - - return std::clamp(NOMAPPEDSURFACETIMER->second.getSeconds() - /* delay for screencopy */ 0.5f, 0.f, 1.f); -} - void CSessionLockManager::onLockscreenRenderedOnMonitor(uint64_t id) { - if (!m_sessionLock || m_sessionLock->hasSentLocked) + if (!m_sessionLock || m_sessionLock->hasSentLocked || m_sessionLock->hasSentDenied) return; m_sessionLock->lockedMonitors.emplace(id); const bool LOCKED = std::ranges::all_of(g_pCompositor->m_monitors, [this](auto m) { return m_sessionLock->lockedMonitors.contains(m->m_id); }); if (LOCKED && m_sessionLock->lock->good()) { + removeSendDeniedTimer(); m_sessionLock->lock->sendLocked(); m_sessionLock->hasSentLocked = true; } @@ -157,6 +180,10 @@ bool CSessionLockManager::isSurfaceSessionLock(SP pSurface) return false; } +bool CSessionLockManager::anySessionLockSurfacesPresent() { + return m_sessionLock && std::ranges::any_of(m_sessionLock->vSessionLockSurfaces, [](const auto& surf) { return surf->mapped; }); +} + void CSessionLockManager::removeSessionLockSurface(SSessionLockSurface* pSLS) { if (!m_sessionLock) return; @@ -175,19 +202,19 @@ void CSessionLockManager::removeSessionLockSurface(SSessionLockSurface* pSLS) { } } -bool CSessionLockManager::isSessionLockPresent() { - return m_sessionLock && !m_sessionLock->vSessionLockSurfaces.empty(); +bool CSessionLockManager::clientLocked() { + return m_sessionLock && m_sessionLock->hasSentLocked; } -bool CSessionLockManager::anySessionLockSurfacesPresent() { - return m_sessionLock && std::ranges::any_of(m_sessionLock->vSessionLockSurfaces, [](const auto& surf) { return surf->mapped; }); +bool CSessionLockManager::clientDenied() { + return m_sessionLock && m_sessionLock->hasSentDenied; } bool CSessionLockManager::shallConsiderLockMissing() { if (!m_sessionLock) - return false; + return true; static auto LOCKDEAD_SCREEN_DELAY = CConfigValue("misc:lockdead_screen_delay"); - return m_sessionLock->mLockTimer.getMillis() > *LOCKDEAD_SCREEN_DELAY; + return m_sessionLock->lockTimer.getMillis() > *LOCKDEAD_SCREEN_DELAY; } diff --git a/src/managers/SessionLockManager.hpp b/src/managers/SessionLockManager.hpp index da15496e0..2938a851a 100644 --- a/src/managers/SessionLockManager.hpp +++ b/src/managers/SessionLockManager.hpp @@ -3,6 +3,7 @@ #include "../defines.hpp" #include "../helpers/time/Timer.hpp" #include "../helpers/signal/Signal.hpp" +#include "./eventLoop/EventLoopTimer.hpp" #include #include #include @@ -29,10 +30,10 @@ struct SSessionLockSurface { struct SSessionLock { WP lock; - CTimer mLockTimer; + CTimer lockTimer; + SP sendDeniedTimer; std::vector> vSessionLockSurfaces; - std::unordered_map mMonitorsWithoutMappedSurfaceTimers; struct { CHyprSignalListener newSurface; @@ -41,6 +42,7 @@ struct SSessionLock { } listeners; bool hasSentLocked = false; + bool hasSentDenied = false; std::unordered_set lockedMonitors; }; @@ -51,10 +53,9 @@ class CSessionLockManager { WP getSessionLockSurfaceForMonitor(uint64_t); - float getRedScreenAlphaForMonitor(uint64_t); - bool isSessionLocked(); - bool isSessionLockPresent(); + bool clientLocked(); + bool clientDenied(); bool isSurfaceSessionLock(SP); bool anySessionLockSurfacesPresent(); @@ -72,6 +73,7 @@ class CSessionLockManager { } m_listeners; void onNewSessionLock(SP pWlrLock); + void removeSendDeniedTimer(); }; inline UP g_pSessionLockManager; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 015fc24b6..b2dd5ffc4 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -36,6 +36,7 @@ #include "../protocols/ColorManagement.hpp" #include "../protocols/types/ContentType.hpp" #include "../helpers/MiscFunctions.hpp" +#include "render/OpenGL.hpp" #include using namespace Hyprutils::Utils; @@ -831,13 +832,16 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA static auto PRENDERTEX = CConfigValue("misc:disable_hyprland_logo"); static auto PBACKGROUNDCOLOR = CConfigValue("misc:background_color"); static auto PXPMODE = CConfigValue("render:xp_mode"); + static auto PSESSIONLOCKXRAY = CConfigValue("misc:session_lock_xray"); if (!pMonitor) return; - if (g_pSessionLockManager->isSessionLocked() && !g_pSessionLockManager->isSessionLockPresent()) { - // do not render anything. We will render a lockscreen anyways later. - return; + if (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { + // We stop to render workspaces as soon as the lockscreen was sent the "locked" or "finished" (aka denied) event. + // In addition we make sure to stop rendering workspaces after misc:lockdead_screen_delay has passed. + if (g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied()) + return; } // todo: matrices are buggy atm for some reason, but probably would be preferable in the long run @@ -990,66 +994,73 @@ void CHyprRenderer::renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& TRACY_GPU_ZONE("RenderLockscreen"); const bool LOCKED = g_pSessionLockManager->isSessionLocked(); + if (!LOCKED) { + g_pHyprOpenGL->ensureLockTexturesRendered(false); + return; + } - g_pHyprOpenGL->ensureLockTexturesRendered( // - LOCKED && // session is locked AND - !g_pSessionLockManager->getSessionLockSurfaceForMonitor(pMonitor->m_id) && // no session lock surface AND - (g_pSessionLockManager->shallConsiderLockMissing() || - !g_pSessionLockManager->isSessionLockPresent()) // we can consider rendering the lockMissing texture OR there is no client altogether - ); + const bool RENDERPRIMER = g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied(); + if (RENDERPRIMER) + renderSessionLockPrimer(pMonitor); - if (LOCKED) { - Vector2D translate = {geometry.x, geometry.y}; + const auto PSLS = g_pSessionLockManager->getSessionLockSurfaceForMonitor(pMonitor->m_id); + const bool RENDERLOCKMISSING = (PSLS.expired() || g_pSessionLockManager->clientDenied()) && g_pSessionLockManager->shallConsiderLockMissing(); - const auto PSLS = g_pSessionLockManager->getSessionLockSurfaceForMonitor(pMonitor->m_id); - if (!PSLS) { - if (g_pSessionLockManager->shallConsiderLockMissing() || !g_pSessionLockManager->isSessionLockPresent()) - renderSessionLockMissing(pMonitor); - } else { - renderSessionLockSurface(PSLS, pMonitor, now); + g_pHyprOpenGL->ensureLockTexturesRendered(RENDERLOCKMISSING); - // render layers and then their popups for abovelock rule - for (auto const& lsl : pMonitor->m_layerSurfaceLayers) { - for (auto const& ls : lsl) { - renderLayer(ls.lock(), pMonitor, now, false, true); - } + if (RENDERLOCKMISSING) + renderSessionLockMissing(pMonitor); + else if (PSLS) { + renderSessionLockSurface(PSLS, pMonitor, now); + g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->m_id); + + // render layers and then their popups for abovelock rule + for (auto const& lsl : pMonitor->m_layerSurfaceLayers) { + for (auto const& ls : lsl) { + renderLayer(ls.lock(), pMonitor, now, false, true); } - for (auto const& lsl : pMonitor->m_layerSurfaceLayers) { - for (auto const& ls : lsl) { - renderLayer(ls.lock(), pMonitor, now, true, true); - } + } + for (auto const& lsl : pMonitor->m_layerSurfaceLayers) { + for (auto const& ls : lsl) { + renderLayer(ls.lock(), pMonitor, now, true, true); } - - g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->m_id); } } } +void CHyprRenderer::renderSessionLockPrimer(PHLMONITOR pMonitor) { + static auto PSESSIONLOCKXRAY = CConfigValue("misc:session_lock_xray"); + if (*PSESSIONLOCKXRAY) + return; + + CRectPassElement::SRectData data; + data.color = CHyprColor(0, 0, 0, 1.f); + data.box = CBox{{}, pMonitor->m_pixelSize}; + + m_renderPass.add(makeShared(data)); +} + void CHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) { - const auto ALPHA = g_pSessionLockManager->getRedScreenAlphaForMonitor(pMonitor->m_id); - - CBox monbox = {{}, pMonitor->m_pixelSize}; - const bool ANY_PRESENT = g_pSessionLockManager->anySessionLockSurfacesPresent(); - if (ANY_PRESENT) { - // render image2, without instructions. Lock still "alive", unless texture dead - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_lockDead2Texture, monbox, ALPHA); - } else { - // render image, with instructions. Lock is gone. - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_lockDeadTexture, monbox, ALPHA); + // ANY_PRESENT: render image2, without instructions. Lock still "alive", unless texture dead + // else: render image, with instructions. Lock is gone. + CBox monbox = {{}, pMonitor->m_pixelSize}; + CTexPassElement::SRenderData data; + data.tex = (ANY_PRESENT) ? g_pHyprOpenGL->m_lockDead2Texture : g_pHyprOpenGL->m_lockDeadTexture; + data.box = monbox; + data.a = 1; + m_renderPass.add(makeShared(data)); + + if (!ANY_PRESENT && g_pHyprOpenGL->m_lockTtyTextTexture) { // also render text for the tty number - if (g_pHyprOpenGL->m_lockTtyTextTexture) { - CBox texbox = {{}, g_pHyprOpenGL->m_lockTtyTextTexture->m_size}; - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_lockTtyTextTexture, texbox, ALPHA); - } - } + CBox texbox = {{}, g_pHyprOpenGL->m_lockTtyTextTexture->m_size}; + data.tex = g_pHyprOpenGL->m_lockTtyTextTexture; + data.box = texbox; - if (ALPHA < 1.f) /* animate */ - damageMonitor(pMonitor); - else - g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->m_id); + m_renderPass.add(makeShared(data)); + } } void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP pSurface, PHLMONITOR pMonitor, bool main, const Vector2D& projSize, diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index ad4819ebb..9f733a573 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -126,6 +126,7 @@ class CHyprRenderer { void renderDragIcon(PHLMONITOR, const Time::steady_tp&); void renderIMEPopup(CInputPopup*, PHLMONITOR, const Time::steady_tp&); void sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now); // sends frame displayed events but doesn't actually render anything + void renderSessionLockPrimer(PHLMONITOR pMonitor); void renderSessionLockMissing(PHLMONITOR pMonitor); bool commitPendingAndDoExplicitSync(PHLMONITOR pMonitor);