start: re-show display lock died screen on restart after crash (#15334)

* fix: display lock died screen on restart after crash

* refactor to expose events in SessionLockManager and listen from Compositor

* update Hyprland CLI help output

* add clearSessionLock() and fix allow_session_lock_restore

* implement --locked CLI flag

* exec lockscreen directly without lua

* separate --locked into --locked-cmd and --locked

* fix clang-format problems
This commit is contained in:
Neal
2026-07-11 16:43:04 +02:00
committed by GitHub
parent 128b2d5050
commit 10a34c4c2b
12 changed files with 139 additions and 15 deletions
+34 -3
View File
@@ -130,6 +130,14 @@ bool CCompositor::setWatchdogFd(int fd) {
return m_watchdogWriteFd.isValid() && !m_watchdogWriteFd.isClosed();
}
bool CCompositor::writeWatchdogFd(std::string str) {
if (!m_watchdogWriteFd.isValid())
return false;
str += '\n';
auto w = write(m_watchdogWriteFd.get(), str.c_str(), str.size());
return w >= 0;
}
void CCompositor::bumpNofile() {
if (!getrlimit(RLIMIT_NOFILE, &m_originalNofile))
Log::logger->log(Log::DEBUG, "Old rlimit: soft -> {}, hard -> {}", m_originalNofile.rlim_cur, m_originalNofile.rlim_max);
@@ -416,6 +424,30 @@ void CCompositor::initServer(std::string socketName, int socketFd) {
initManagers(STAGE_LATE);
m_listeners.lock = g_pSessionLockManager->m_events.lock.listen([this] {
static int lock_count = 0;
// lock_count used to avoid triggering condition on initial forceLock()
if (m_startLocked && lock_count >= 1) {
// Lock manager has taken over
m_startLocked = false;
m_startLockedCommand.clear();
}
lock_count++;
writeWatchdogFd("lock");
});
m_listeners.unlock = g_pSessionLockManager->m_events.unlock.listen([this] {
m_startLocked = false;
writeWatchdogFd("unlock");
});
if (m_startLocked) {
g_pSessionLockManager->forceLock();
if (!m_startLockedCommand.empty())
Config::Supplementary::executor()->spawn(m_startLockedCommand);
}
for (auto const& o : pendingOutputs) {
State::monitorState()->add(o);
}
@@ -529,8 +561,7 @@ void CCompositor::cleanup() {
if (!m_wlDisplay)
return;
if (m_watchdogWriteFd.isValid()) [[maybe_unused]]
auto w = write(m_watchdogWriteFd.get(), "end", 3);
writeWatchdogFd("end");
signal(SIGABRT, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
@@ -783,7 +814,7 @@ void CCompositor::startCompositor() {
Event::bus()->m_events.ready.emit();
if (m_watchdogWriteFd.isValid()) {
if (write(m_watchdogWriteFd.get(), "vax", 3) < 0)
if (!writeWatchdogFd("vax"))
Log::logger->log(Log::ERR, "startCompositor: failed to write to watchdogWriteFd {}: {}", m_watchdogWriteFd.get(), strerror(errno));
}
+9
View File
@@ -46,6 +46,7 @@ class CCompositor {
bool m_initialized = false;
bool m_safeMode = false;
bool m_startLocked = false;
SP<Aquamarine::CBackend> m_aqBackend;
std::string m_hyprTempDataRoot = "";
@@ -55,6 +56,8 @@ class CCompositor {
std::string m_instancePath = "";
std::string m_currentSplash = "error";
std::string m_startLockedCommand = "";
void initServer(std::string socketName, int socketFd);
void startCompositor();
void stopCompositor();
@@ -62,6 +65,7 @@ class CCompositor {
void bumpNofile();
void restoreNofile();
bool setWatchdogFd(int fd);
bool writeWatchdogFd(std::string);
bool m_sessionActive = true;
bool m_dpmsStateOn = true;
@@ -99,6 +103,11 @@ class CCompositor {
wl_event_source* m_critSigSource = nullptr;
rlimit m_originalNofile = {};
Hyprutils::OS::CFileDescriptor m_watchdogWriteFd;
struct {
CHyprSignalListener lock;
CHyprSignalListener unlock;
} m_listeners;
};
inline UP<CCompositor> g_pCompositor;
+23 -2
View File
@@ -37,7 +37,8 @@ static void help() {
--i-am-really-stupid - Omits root user privileges check (why would you do that?)
--verify-config - Do not run Hyprland, only print if the config has any errors
--version -v - Print this binary's version
--version-json - Print this binary's version as json)#");
--version-json - Print this binary's version as json
--locked-cmd [COMMAND] - Launches locker on startup via the provided command)#");
}
static void reapZombieChildrenAutomatically() {
@@ -70,8 +71,9 @@ int main(int argc, char** argv) {
// parse some args
std::string configPath;
std::string socketName;
std::string startLockedCommand;
int socketFd = -1;
bool ignoreSudo = false, verifyConfig = false, safeMode = false;
bool ignoreSudo = false, verifyConfig = false, safeMode = false, startLocked = false;
int watchdogFd = -1;
if (argc > 1) {
@@ -176,6 +178,20 @@ int main(int argc, char** argv) {
help();
return 1;
}
} else if (value == "--locked-cmd") {
if (std::next(it) == args.end()) {
help();
return 1;
}
startLocked = true;
startLockedCommand = *std::next(it);
it++;
continue;
} else if (value == "--locked") {
startLocked = true;
continue;
} else {
std::println(stderr, "[ ERROR ] Unknown option '{}' !", value);
help();
@@ -260,6 +276,11 @@ int main(int argc, char** argv) {
if (safeMode)
g_pCompositor->m_safeMode = true;
if (startLocked) {
g_pCompositor->m_startLocked = true;
g_pCompositor->m_startLockedCommand = startLockedCommand;
}
if (!watchdogOk && !verifyConfig)
Log::logger->log(Log::WARN, "WARNING: Hyprland is being launched without start-hyprland. This is highly advised against.");
+16 -2
View File
@@ -52,7 +52,7 @@ CSessionLockManager::CSessionLockManager() {
void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
static auto PALLOWRELOCK = CConfigValue<Config::INTEGER>("misc:allow_session_lock_restore");
if (PROTO::sessionLock->isLocked() && !*PALLOWRELOCK) {
if (PROTO::sessionLock->isLocked() && !*PALLOWRELOCK && g_pCompositor->m_startLockedCommand.empty()) {
LOGM(Log::DEBUG, "Cannot re-lock, misc:allow_session_lock_restore is disabled");
pLock->sendDenied();
return;
@@ -78,6 +78,8 @@ void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
});
m_sessionLock->listeners.unlock = pLock->m_events.unlockAndDestroy.listen([this] {
m_events.unlock.emit();
m_sessionLock.reset();
g_pInputManager->refocus();
@@ -93,6 +95,8 @@ void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
g_pHyprRenderer->damageMonitor(m);
});
m_events.lock.emit();
Desktop::focusState()->rawSurfaceFocus(nullptr);
g_pSeatManager->setGrab(nullptr);
@@ -216,9 +220,14 @@ bool CSessionLockManager::clientDenied() {
return m_sessionLock && m_sessionLock->hasSentDenied;
}
void CSessionLockManager::clearSessionLock() {
m_events.unlock.emit();
m_sessionLock = {};
}
void CSessionLockManager::forceUnlock() {
PROTO::sessionLock->forceUnlock();
m_sessionLock = {};
clearSessionLock();
Desktop::focusState()->rawSurfaceFocus(nullptr);
for (auto const& m : State::monitorState()->monitors())
@@ -227,6 +236,11 @@ void CSessionLockManager::forceUnlock() {
g_pInputManager->refocus();
}
void CSessionLockManager::forceLock() {
PROTO::sessionLock->forceLock();
m_events.lock.emit();
}
bool CSessionLockManager::shallConsiderLockMissing() {
if (!m_sessionLock)
return true;
+7
View File
@@ -65,6 +65,7 @@ class CSessionLockManager {
bool anySessionLockSurfacesPresent();
void forceUnlock();
void forceLock();
void removeSessionLockSurface(SSessionLockSurface*);
@@ -72,6 +73,11 @@ class CSessionLockManager {
bool shallConsiderLockMissing();
struct {
CSignalT<> lock;
CSignalT<> unlock;
} m_events;
private:
UP<SSessionLock> m_sessionLock;
@@ -81,6 +87,7 @@ class CSessionLockManager {
void onNewSessionLock(SP<CSessionLock> pWlrLock);
void removeSendLockedTimer();
void clearSessionLock();
};
inline UP<CSessionLockManager> g_pSessionLockManager;
+10
View File
@@ -248,3 +248,13 @@ void CSessionLockProtocol::forceUnlock() {
PROTO::lockNotify->onUnlocked();
}
void CSessionLockProtocol::forceLock() {
m_locked = true;
for (const auto& l : m_locks) {
l->sendLocked();
}
PROTO::lockNotify->onLocked();
}
+1
View File
@@ -74,6 +74,7 @@ class CSessionLockProtocol : public IWaylandProtocol {
bool isLocked();
void forceUnlock();
void forceLock();
struct {
CSignalT<SP<CSessionLock>> newLock;
+9 -1
View File
@@ -1614,6 +1614,7 @@ void IHyprRenderer::ensureLockTexturesRendered(bool load) {
// this will cause a small hitch. I don't think we can do much, other than wasting VRAM and having this loaded all the time.
m_lockDeadTexture = loadAsset("lockdead.png");
m_lockDead2Texture = loadAsset("lockdead2.png");
m_lockDead3Texture = loadAsset("lockdead.png");
const auto VT = g_pCompositor->getVTNr();
@@ -1621,6 +1622,7 @@ void IHyprRenderer::ensureLockTexturesRendered(bool load) {
} else {
m_lockDeadTexture.reset();
m_lockDead2Texture.reset();
m_lockDead3Texture.reset();
m_lockTtyTextTexture.reset();
}
}
@@ -1676,13 +1678,19 @@ void IHyprRenderer::renderSessionLockPrimer(PHLMONITOR pMonitor) {
}
void IHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) {
if (g_pCompositor->m_startLocked && !g_pCompositor->m_startLockedCommand.empty())
return;
const bool ANY_PRESENT = g_pSessionLockManager->anySessionLockSurfacesPresent();
// 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) ? m_lockDead2Texture : m_lockDeadTexture;
if (g_pCompositor->m_startLocked && g_pCompositor->m_startLockedCommand.empty())
data.tex = m_lockDead3Texture;
else
data.tex = (ANY_PRESENT) ? m_lockDead2Texture : m_lockDeadTexture;
data.box = monbox;
data.a = 1;
+1
View File
@@ -250,6 +250,7 @@ namespace Render {
SP<ITexture> m_lockDeadTexture;
SP<ITexture> m_lockDead2Texture;
SP<ITexture> m_lockDead3Texture;
SP<ITexture> m_lockTtyTextTexture;
CRenderPass* m_currentPass = nullptr;
bool m_monitorTransformEnabled = false; // do not modify directly
+20 -3
View File
@@ -25,13 +25,16 @@ using namespace Hyprutils::OS;
using namespace std::string_literals;
//
void CHyprlandInstance::runHyprlandThread(bool safeMode) {
void CHyprlandInstance::runHyprlandThread(bool safeMode, bool lockedCrash) {
std::vector<std::string> argsStd;
argsStd.emplace_back("--watchdog-fd");
argsStd.emplace_back(std::format("{}", m_toHlPid.get()));
if (safeMode)
argsStd.emplace_back("--safe-mode");
if (lockedCrash)
argsStd.emplace_back("--locked");
for (const auto& a : g_state->rawArgvNoBinPath) {
argsStd.emplace_back(a);
}
@@ -134,10 +137,22 @@ void CHyprlandInstance::dispatchHyprlandEvent() {
m_hyprlandExiting = true;
continue;
}
if (sv == "lock") {
// session locked
m_hyprlandLocked = true;
continue;
}
if (sv == "unlock") {
// session unlocked
m_hyprlandLocked = false;
continue;
}
}
}
bool CHyprlandInstance::run(bool safeMode) {
bool CHyprlandInstance::run(bool safeMode, bool lockedCrash) {
int pipefds[2];
if (pipe(pipefds) != 0) {
g_logger->log(Hyprutils::CLI::LOG_ERR, "pipe() failed, exiting");
@@ -159,7 +174,9 @@ bool CHyprlandInstance::run(bool safeMode) {
m_wakeupRead.setFlags(m_wakeupRead.getFlags() | FD_CLOEXEC);
m_wakeupWrite.setFlags(m_wakeupWrite.getFlags() | FD_CLOEXEC);
runHyprlandThread(safeMode);
m_hyprlandLocked = lockedCrash;
runHyprlandThread(safeMode, lockedCrash);
pollfd pollfds[2] = {
{
+4 -2
View File
@@ -14,11 +14,13 @@ class CHyprlandInstance {
CHyprlandInstance(CHyprlandInstance&) = delete;
CHyprlandInstance(CHyprlandInstance&&) = delete;
bool run(bool safeMode = false); // if returns false, restart.
bool run(bool safeMode = false, bool lockedCrash = false); // if returns false, restart.
void forceQuit();
bool m_hyprlandLocked = false;
private:
void runHyprlandThread(bool safeMode);
void runHyprlandThread(bool safeMode, bool lockedCrash);
void clearFd(const Hyprutils::OS::CFileDescriptor& fd);
void dispatchHyprlandEvent();
+5 -2
View File
@@ -101,10 +101,13 @@ int main(int argc, const char** argv, const char** envp) {
if (Nix::shouldUseNixGL())
g_logger->log(Hyprutils::CLI::LOG_DEBUG, "Hyprland was compiled with Nix - will use nixGL");
bool safeMode = false;
bool safeMode = false;
bool lockedCrash = false;
while (true) {
g_instance = makeUnique<CHyprlandInstance>();
const bool RET = g_instance->run(safeMode);
const bool RET = g_instance->run(safeMode, lockedCrash);
lockedCrash = g_instance->m_hyprlandLocked;
g_instance.reset();
if (!RET) {