From 9f1ff2af51cdef98ae3f02fc646bd6f7e47ff74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Fri, 20 Feb 2026 17:18:58 +0100 Subject: [PATCH 1/4] fix(fingerprint): prevent sensor timeout on long-running sessions Introduces an inactivity timeout mechanism that pauses fingerprint verification after a configurable period (default: 0 second/disabled) when no user input is detected. This prevents some device drivers from disconnecting or disabling the sensor during extended lock sessions. The verification automatically resumes when user activity is detected (mouse movement, clicks, or keyboard input). The sensor is properly released when paused and re-claimed when resuming. May fixe #702 though that's unclear as the issue is not well documented --- src/auth/Fingerprint.cpp | 51 +++++++++++++++++++++++++++++++++++- src/auth/Fingerprint.hpp | 8 ++++++ src/config/ConfigManager.cpp | 1 + src/core/Seat.cpp | 2 ++ src/core/hyprlock.cpp | 16 +++++++++++ src/core/hyprlock.hpp | 1 + 6 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/auth/Fingerprint.cpp b/src/auth/Fingerprint.cpp index 780c079..8fc37aa 100644 --- a/src/auth/Fingerprint.cpp +++ b/src/auth/Fingerprint.cpp @@ -40,6 +40,8 @@ CFingerprint::CFingerprint() { m_sFingerprintReady = *FINGERPRINTREADY; static const auto FINGERPRINTPRESENT = g_pConfigManager->getValue("auth:fingerprint:present_message"); m_sFingerprintPresent = *FINGERPRINTPRESENT; + static const auto INACTIVETIMEOUT = g_pConfigManager->getValue("auth:fingerprint:inactive_timeout"); + m_sInactiveTimeout = *INACTIVETIMEOUT; } CFingerprint::~CFingerprint() { @@ -96,10 +98,54 @@ bool CFingerprint::checkWaiting() { } void CFingerprint::terminate() { + if (m_pInactivityTimer) { + m_pInactivityTimer->cancel(); + m_pInactivityTimer.reset(); + } + if (!m_sDBUSState.abort) releaseDevice(); } +void CFingerprint::setupInactivityTimer() { + if (m_sInactiveTimeout <= 0 || m_sDBUSState.abort || m_sDBUSState.done) + return; + + if (m_pInactivityTimer) { + m_pInactivityTimer->cancel(); + m_pInactivityTimer.reset(); + } + + m_pInactivityTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(m_sInactiveTimeout * 1000), + [](ASP self, void* data) { ((CFingerprint*)data)->onInactivityTimeout(); }, this); +} + +void CFingerprint::onActivity() { + if (!m_sDBUSState.verifying) { + Debug::log(LOG, "fprint: activity detected, resuming verification"); + startVerify(); + } +} + +void CFingerprint::onInactivityTimeout() { + if (m_sDBUSState.abort || m_sDBUSState.done || !m_sDBUSState.verifying) + return; + + Debug::log(LOG, "fprint: inactivity timeout, pausing verification"); + + stopVerify(); + + releaseDevice(); + + m_sDBUSState.device.reset(); + + // Clear the prompt text to provide user feedback + m_sPrompt = ""; + g_pHyprlock->enqueueForceUpdateTimers(); + + m_pInactivityTimer.reset(); +} + std::shared_ptr CFingerprint::getConnection() { return m_sDBUSState.connection; } @@ -237,8 +283,11 @@ void CFingerprint::startVerify(bool isRetry) { if (isRetry) { m_sDBUSState.retries++; m_sPrompt = "Could not match fingerprint. Try again."; - } else + } else { m_sPrompt = m_sFingerprintReady; + + setupInactivityTimer(); + } } g_pHyprlock->enqueueForceUpdateTimers(); }); diff --git a/src/auth/Fingerprint.hpp b/src/auth/Fingerprint.hpp index 404a5ca..b940063 100644 --- a/src/auth/Fingerprint.hpp +++ b/src/auth/Fingerprint.hpp @@ -22,6 +22,8 @@ class CFingerprint : public IAuthImplementation { virtual std::optional getLastPrompt(); virtual void terminate(); + void onActivity(); + std::shared_ptr getConnection(); private: @@ -39,10 +41,13 @@ class CFingerprint : public IAuthImplementation { std::string m_sFingerprintReady; std::string m_sFingerprintPresent; + int m_sInactiveTimeout; std::string m_sPrompt{""}; std::string m_sFailureReason{""}; + ASP m_pInactivityTimer; + void handleVerifyStatus(const std::string& result, const bool done); bool createDeviceProxy(); @@ -50,4 +55,7 @@ class CFingerprint : public IAuthImplementation { void startVerify(bool isRetry = false); bool stopVerify(); bool releaseDevice(); + + void onInactivityTimeout(); + void setupInactivityTimer(); }; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 2c200f8..6a9991d 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -249,6 +249,7 @@ void CConfigManager::init() { m_config.addConfigValue("auth:fingerprint:ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"}); m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"}); m_config.addConfigValue("auth:fingerprint:retry_delay", Hyprlang::INT{250}); + m_config.addConfigValue("auth:fingerprint:inactive_timeout", Hyprlang::INT{0}); m_config.addConfigValue("animations:enabled", Hyprlang::INT{1}); diff --git a/src/core/Seat.cpp b/src/core/Seat.cpp index 6fe27ca..4bf10e6 100644 --- a/src/core/Seat.cpp +++ b/src/core/Seat.cpp @@ -34,6 +34,8 @@ void CSeatManager::registerSeat(SP seat) { m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}; + g_pHyprlock->onMouseMove(g_pHyprlock->m_vMouseLocation); + if (!*HIDECURSOR) g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation); diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 3b25667..c782615 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -696,6 +696,13 @@ void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) { if (!m_focusedOutput->m_sessionLockSurface) return; + if (g_pAuth) { + auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT); + if (fpImpl) { + ((CFingerprint*)fpImpl.get())->onActivity(); + } + } + const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale; const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface); for (const auto& widget : widgets) { @@ -743,6 +750,15 @@ void CHyprlock::onHover(const Vector2D& pos) { m_focusedOutput->m_sessionLockSurface->render(); } +void CHyprlock::onMouseMove(const Vector2D& pos) { + if (g_pAuth) { + auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT); + if (fpImpl) { + ((CFingerprint*)fpImpl.get())->onActivity(); + } + } +} + bool CHyprlock::acquireSessionLock() { Log::logger->log(Log::INFO, "Locking session"); m_sLockState.lock = makeShared(m_sWaylandState.sessionLock->sendLock()); diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index db25aec..3964f19 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -49,6 +49,7 @@ class CHyprlock { void onKey(uint32_t key, bool down); void onClick(uint32_t button, bool down, const Vector2D& pos); void onHover(const Vector2D& pos); + void onMouseMove(const Vector2D& pos); void startKeyRepeat(xkb_keysym_t sym); void repeatKey(xkb_keysym_t sym); void handleKeySym(xkb_keysym_t sym, bool compose); From 7c2822bfdb4a7b73ea0b03981fd3376c4d9ff74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Sat, 9 May 2026 09:15:11 +0200 Subject: [PATCH 2/4] refactor(fingerprint): consolidate activity notification --- src/auth/Fingerprint.cpp | 17 +++++++++++++---- src/auth/Fingerprint.hpp | 2 +- src/core/Seat.cpp | 2 +- src/core/hyprlock.cpp | 27 ++++++++++++--------------- src/core/hyprlock.hpp | 3 ++- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/auth/Fingerprint.cpp b/src/auth/Fingerprint.cpp index 8fc37aa..dae1324 100644 --- a/src/auth/Fingerprint.cpp +++ b/src/auth/Fingerprint.cpp @@ -107,6 +107,15 @@ void CFingerprint::terminate() { releaseDevice(); } +static void inactivityTimerCallback(ASP self, void* data) { + if (!g_pAuth) + return; + auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT); + if (!fpImpl) + return; + ((CFingerprint*)fpImpl.get())->onInactivityTimeout(); +} + void CFingerprint::setupInactivityTimer() { if (m_sInactiveTimeout <= 0 || m_sDBUSState.abort || m_sDBUSState.done) return; @@ -116,11 +125,12 @@ void CFingerprint::setupInactivityTimer() { m_pInactivityTimer.reset(); } - m_pInactivityTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(m_sInactiveTimeout * 1000), - [](ASP self, void* data) { ((CFingerprint*)data)->onInactivityTimeout(); }, this); + m_pInactivityTimer = g_pHyprlock->addTimer(std::chrono::seconds(m_sInactiveTimeout), inactivityTimerCallback, nullptr); } void CFingerprint::onActivity() { + setupInactivityTimer(); + if (!m_sDBUSState.verifying) { Debug::log(LOG, "fprint: activity detected, resuming verification"); startVerify(); @@ -132,9 +142,7 @@ void CFingerprint::onInactivityTimeout() { return; Debug::log(LOG, "fprint: inactivity timeout, pausing verification"); - stopVerify(); - releaseDevice(); m_sDBUSState.device.reset(); @@ -143,6 +151,7 @@ void CFingerprint::onInactivityTimeout() { m_sPrompt = ""; g_pHyprlock->enqueueForceUpdateTimers(); + m_pInactivityTimer->cancel(); m_pInactivityTimer.reset(); } diff --git a/src/auth/Fingerprint.hpp b/src/auth/Fingerprint.hpp index b940063..42d2055 100644 --- a/src/auth/Fingerprint.hpp +++ b/src/auth/Fingerprint.hpp @@ -23,6 +23,7 @@ class CFingerprint : public IAuthImplementation { virtual void terminate(); void onActivity(); + void onInactivityTimeout(); std::shared_ptr getConnection(); @@ -56,6 +57,5 @@ class CFingerprint : public IAuthImplementation { bool stopVerify(); bool releaseDevice(); - void onInactivityTimeout(); void setupInactivityTimer(); }; diff --git a/src/core/Seat.cpp b/src/core/Seat.cpp index 4bf10e6..f13e3fd 100644 --- a/src/core/Seat.cpp +++ b/src/core/Seat.cpp @@ -34,7 +34,7 @@ void CSeatManager::registerSeat(SP seat) { m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}; - g_pHyprlock->onMouseMove(g_pHyprlock->m_vMouseLocation); + g_pHyprlock->notifyActivityToFingerprint(); if (!*HIDECURSOR) g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation); diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index c782615..c334991 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -644,6 +644,8 @@ void CHyprlock::onKey(uint32_t key, bool down) { xkb_compose_state_reset(g_pSeatManager->m_pXKBComposeState); renderAllOutputs(); + + notifyActivityToFingerprint(); } void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) { @@ -696,12 +698,7 @@ void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) { if (!m_focusedOutput->m_sessionLockSurface) return; - if (g_pAuth) { - auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT); - if (fpImpl) { - ((CFingerprint*)fpImpl.get())->onActivity(); - } - } + notifyActivityToFingerprint(); const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale; const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface); @@ -750,15 +747,6 @@ void CHyprlock::onHover(const Vector2D& pos) { m_focusedOutput->m_sessionLockSurface->render(); } -void CHyprlock::onMouseMove(const Vector2D& pos) { - if (g_pAuth) { - auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT); - if (fpImpl) { - ((CFingerprint*)fpImpl.get())->onActivity(); - } - } -} - bool CHyprlock::acquireSessionLock() { Log::logger->log(Log::INFO, "Locking session"); m_sLockState.lock = makeShared(m_sWaylandState.sessionLock->sendLock()); @@ -931,6 +919,15 @@ void CHyprlock::enqueueForceUpdateTimers() { nullptr, false); } +void CHyprlock::notifyActivityToFingerprint() { + if (!g_pAuth) + return; + auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT); + if (!fpImpl) + return; + ((CFingerprint*)fpImpl.get())->onActivity(); +} + SP CHyprlock::getScreencopy() { return m_sWaylandState.screencopy; } diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 3964f19..77ab1bb 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -49,7 +49,6 @@ class CHyprlock { void onKey(uint32_t key, bool down); void onClick(uint32_t button, bool down, const Vector2D& pos); void onHover(const Vector2D& pos); - void onMouseMove(const Vector2D& pos); void startKeyRepeat(xkb_keysym_t sym); void repeatKey(xkb_keysym_t sym); void handleKeySym(xkb_keysym_t sym, bool compose); @@ -58,6 +57,8 @@ class CHyprlock { bool passwordCheckWaiting(); std::optional passwordLastFailReason(); + void notifyActivityToFingerprint(); + void renderOutput(const std::string& stringPort); void renderAllOutputs(); From 53a47186de978f56c2176a57f4f91c4523b32a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Sat, 9 May 2026 09:45:52 +0200 Subject: [PATCH 3/4] chore(fingerprint): using Hyprutils::CLI::CLogger --- src/auth/Fingerprint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/Fingerprint.cpp b/src/auth/Fingerprint.cpp index dae1324..3f32667 100644 --- a/src/auth/Fingerprint.cpp +++ b/src/auth/Fingerprint.cpp @@ -132,7 +132,7 @@ void CFingerprint::onActivity() { setupInactivityTimer(); if (!m_sDBUSState.verifying) { - Debug::log(LOG, "fprint: activity detected, resuming verification"); + Log::logger->log(Log::INFO, "fprint: activity detected, resuming verification"); startVerify(); } } @@ -141,7 +141,7 @@ void CFingerprint::onInactivityTimeout() { if (m_sDBUSState.abort || m_sDBUSState.done || !m_sDBUSState.verifying) return; - Debug::log(LOG, "fprint: inactivity timeout, pausing verification"); + Log::logger->log(Log::INFO, "fprint: inactivity timeout, pausing verification"); stopVerify(); releaseDevice(); From 617bcf71395c0a44e8938ded27249e599febdd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Tue, 19 May 2026 10:17:14 +0200 Subject: [PATCH 4/4] chore(fingerprint): clang-format --- src/auth/Fingerprint.cpp | 2 +- src/auth/Fingerprint.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth/Fingerprint.cpp b/src/auth/Fingerprint.cpp index 3f32667..56e5a32 100644 --- a/src/auth/Fingerprint.cpp +++ b/src/auth/Fingerprint.cpp @@ -41,7 +41,7 @@ CFingerprint::CFingerprint() { static const auto FINGERPRINTPRESENT = g_pConfigManager->getValue("auth:fingerprint:present_message"); m_sFingerprintPresent = *FINGERPRINTPRESENT; static const auto INACTIVETIMEOUT = g_pConfigManager->getValue("auth:fingerprint:inactive_timeout"); - m_sInactiveTimeout = *INACTIVETIMEOUT; + m_sInactiveTimeout = *INACTIVETIMEOUT; } CFingerprint::~CFingerprint() { diff --git a/src/auth/Fingerprint.hpp b/src/auth/Fingerprint.hpp index 42d2055..d272df4 100644 --- a/src/auth/Fingerprint.hpp +++ b/src/auth/Fingerprint.hpp @@ -42,12 +42,12 @@ class CFingerprint : public IAuthImplementation { std::string m_sFingerprintReady; std::string m_sFingerprintPresent; - int m_sInactiveTimeout; + int m_sInactiveTimeout; std::string m_sPrompt{""}; std::string m_sFailureReason{""}; - ASP m_pInactivityTimer; + ASP m_pInactivityTimer; void handleVerifyStatus(const std::string& result, const bool done);