From 7486576fa7a3d394254cb07734679df4b2469071 Mon Sep 17 00:00:00 2001
From: Junxuan Liao <70618504+MikeWalrus@users.noreply.github.com>
Date: Sat, 13 Jul 2024 18:32:08 +0800
Subject: [PATCH] session-lock: send `locked` after the lock screen is properly
 rendered (#6850)

The protocol says:
> The locked event "must not be sent until a new "locked" frame (either from a
> session lock surface or the compositor blanking the output) has been presented
> on all outputs and no security sensitive normal/unlocked content is possibly
> visible".

This helps users ensure the screen is properly locked before suspending
the machine. (e.g. with swaylock --ready-fd)
---
 src/managers/SessionLockManager.cpp | 15 +++++++++++++--
 src/managers/SessionLockManager.hpp |  8 +++++++-
 src/render/Renderer.cpp             |  3 +++
 3 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/src/managers/SessionLockManager.cpp b/src/managers/SessionLockManager.cpp
index b4695e0e3..83ff3ee70 100644
--- a/src/managers/SessionLockManager.cpp
+++ b/src/managers/SessionLockManager.cpp
@@ -3,6 +3,7 @@
 #include "../config/ConfigValue.hpp"
 #include "../protocols/FractionalScale.hpp"
 #include "../protocols/SessionLock.hpp"
+#include <algorithm>
 
 SSessionLockSurface::SSessionLockSurface(SP<CSessionLockSurface> surface_) : surface(surface_) {
     pWlrSurface = surface->surface();
@@ -77,7 +78,6 @@ void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
             g_pHyprRenderer->damageMonitor(m.get());
     });
 
-    pLock->sendLocked();
     g_pCompositor->focusSurface(nullptr);
 }
 
@@ -102,7 +102,6 @@ SSessionLockSurface* CSessionLockManager::getSessionLockSurfaceForMonitor(uint64
 }
 
 // We don't want the red screen to flash.
-// This violates the protocol a bit, but tries to handle the missing sync between a lock surface beeing created and the red screen beeing drawn.
 float CSessionLockManager::getRedScreenAlphaForMonitor(uint64_t id) {
     if (!m_pSessionLock)
         return 0.F;
@@ -118,6 +117,18 @@ float CSessionLockManager::getRedScreenAlphaForMonitor(uint64_t id) {
     return std::clamp(NOMAPPEDSURFACETIMER->second.getSeconds() - /* delay for screencopy */ 0.5f, 0.f, 1.f);
 }
 
+void CSessionLockManager::onLockscreenRenderedOnMonitor(uint64_t id) {
+    if (!m_pSessionLock || m_pSessionLock->m_hasSentLocked)
+        return;
+    m_pSessionLock->m_lockedMonitors.emplace(id);
+    const auto MONITORS = g_pCompositor->m_vMonitors;
+    const bool LOCKED   = std::all_of(MONITORS.begin(), MONITORS.end(), [this](auto m) { return m_pSessionLock->m_lockedMonitors.contains(m->ID); });
+    if (LOCKED) {
+        m_pSessionLock->lock->sendLocked();
+        m_pSessionLock->m_hasSentLocked = true;
+    }
+}
+
 bool CSessionLockManager::isSurfaceSessionLock(SP<CWLSurfaceResource> pSurface) {
     // TODO: this has some edge cases when it's wrong (e.g. destroyed lock but not yet surfaces)
     // but can be easily fixed when I rewrite wlr_surface
diff --git a/src/managers/SessionLockManager.hpp b/src/managers/SessionLockManager.hpp
index fe4a4434f..b01ee288c 100644
--- a/src/managers/SessionLockManager.hpp
+++ b/src/managers/SessionLockManager.hpp
@@ -5,6 +5,7 @@
 #include "../helpers/signal/Signal.hpp"
 #include <cstdint>
 #include <unordered_map>
+#include <unordered_set>
 
 class CSessionLockSurface;
 class CSessionLock;
@@ -37,6 +38,9 @@ struct SSessionLock {
         CHyprSignalListener unlock;
         CHyprSignalListener destroy;
     } listeners;
+
+    bool                         m_hasSentLocked = false;
+    std::unordered_set<uint64_t> m_lockedMonitors;
 };
 
 class CSessionLockManager {
@@ -54,6 +58,8 @@ class CSessionLockManager {
 
     void                 removeSessionLockSurface(SSessionLockSurface*);
 
+    void                 onLockscreenRenderedOnMonitor(uint64_t id);
+
   private:
     UP<SSessionLock> m_pSessionLock;
 
@@ -64,4 +70,4 @@ class CSessionLockManager {
     void onNewSessionLock(SP<CSessionLock> pWlrLock);
 };
 
-inline std::unique_ptr<CSessionLockManager> g_pSessionLockManager;
\ No newline at end of file
+inline std::unique_ptr<CSessionLockManager> g_pSessionLockManager;
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
index 955a16b9a..a7f3c9417 100644
--- a/src/render/Renderer.cpp
+++ b/src/render/Renderer.cpp
@@ -990,8 +990,11 @@ void CHyprRenderer::renderLockscreen(CMonitor* pMonitor, timespec* now, const CB
 
             if (ALPHA < 1.f) /* animate */
                 damageMonitor(pMonitor);
+            else
+                g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->ID);
         } else {
             renderSessionLockSurface(PSLS, pMonitor, now);
+            g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->ID);
         }
     }
 }