session-lock: don't render workspaces when locked (#10865)

Avoid rendering the workspace behind if we are locked
This commit is contained in:
Maximilian Seidler
2025-07-14 13:13:54 +02:00
committed by GitHub
parent d0f58baf29
commit 01971cb6c7
7 changed files with 129 additions and 81 deletions

View File

@@ -1218,6 +1218,12 @@ inline static const std::vector<SConfigOptionDescription> 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)",

View File

@@ -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});

View File

@@ -33,7 +33,7 @@ static int wlTick(SP<CEventLoopTimer> self, void* data) {
}
CHyprAnimationManager::CHyprAnimationManager() {
m_animationTimer = SP<CEventLoopTimer>(new CEventLoopTimer(std::chrono::microseconds(500), wlTick, nullptr));
m_animationTimer = makeShared<CEventLoopTimer>(std::chrono::microseconds(500), wlTick, nullptr);
if (g_pEventLoopManager) // null in --verify-config mode
g_pEventLoopManager->addTimer(m_animationTimer);

View File

@@ -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 <algorithm>
#include <ranges>
@@ -49,16 +50,19 @@ void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
static auto PALLOWRELOCK = CConfigValue<Hyprlang::INT>("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<SSessionLock>();
m_sessionLock->lock = pLock;
m_sessionLock->mLockTimer.reset();
m_sessionLock->lockTimer.reset();
m_sessionLock->listeners.newSurface = pLock->m_events.newLockSurface.listen([this](const SP<CSessionLockSurface>& surface) {
const auto PMONITOR = surface->monitor();
@@ -87,14 +91,48 @@ void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
g_pCompositor->focusSurface(nullptr);
g_pSeatManager->setGrab(nullptr);
m_sessionLock->sendDeniedTimer = makeShared<CEventLoopTimer>(
// 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<SSessionLockSurface> 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<CWLSurfaceResource> 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<Hyprlang::INT>("misc:lockdead_screen_delay");
return m_sessionLock->mLockTimer.getMillis() > *LOCKDEAD_SCREEN_DELAY;
return m_sessionLock->lockTimer.getMillis() > *LOCKDEAD_SCREEN_DELAY;
}

View File

@@ -3,6 +3,7 @@
#include "../defines.hpp"
#include "../helpers/time/Timer.hpp"
#include "../helpers/signal/Signal.hpp"
#include "./eventLoop/EventLoopTimer.hpp"
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
@@ -29,10 +30,10 @@ struct SSessionLockSurface {
struct SSessionLock {
WP<CSessionLock> lock;
CTimer mLockTimer;
CTimer lockTimer;
SP<CEventLoopTimer> sendDeniedTimer;
std::vector<UP<SSessionLockSurface>> vSessionLockSurfaces;
std::unordered_map<uint64_t, CTimer> mMonitorsWithoutMappedSurfaceTimers;
struct {
CHyprSignalListener newSurface;
@@ -41,6 +42,7 @@ struct SSessionLock {
} listeners;
bool hasSentLocked = false;
bool hasSentDenied = false;
std::unordered_set<uint64_t> lockedMonitors;
};
@@ -51,10 +53,9 @@ class CSessionLockManager {
WP<SSessionLockSurface> getSessionLockSurfaceForMonitor(uint64_t);
float getRedScreenAlphaForMonitor(uint64_t);
bool isSessionLocked();
bool isSessionLockPresent();
bool clientLocked();
bool clientDenied();
bool isSurfaceSessionLock(SP<CWLSurfaceResource>);
bool anySessionLockSurfacesPresent();
@@ -72,6 +73,7 @@ class CSessionLockManager {
} m_listeners;
void onNewSessionLock(SP<CSessionLock> pWlrLock);
void removeSendDeniedTimer();
};
inline UP<CSessionLockManager> g_pSessionLockManager;

View File

@@ -36,6 +36,7 @@
#include "../protocols/ColorManagement.hpp"
#include "../protocols/types/ContentType.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "render/OpenGL.hpp"
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;
@@ -831,13 +832,16 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA
static auto PRENDERTEX = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_logo");
static auto PBACKGROUNDCOLOR = CConfigValue<Hyprlang::INT>("misc:background_color");
static auto PXPMODE = CConfigValue<Hyprlang::INT>("render:xp_mode");
static auto PSESSIONLOCKXRAY = CConfigValue<Hyprlang::INT>("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<Hyprlang::INT>("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<CRectPassElement>(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<CTexPassElement>(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<CTexPassElement>(data));
}
}
void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface, PHLMONITOR pMonitor, bool main, const Vector2D& projSize,

View File

@@ -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);