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
This commit is contained in:
Jérémie Rodon 2026-02-20 17:18:58 +01:00
parent d75e93f8ee
commit 9f1ff2af51
6 changed files with 78 additions and 1 deletions

View file

@ -40,6 +40,8 @@ CFingerprint::CFingerprint() {
m_sFingerprintReady = *FINGERPRINTREADY;
static const auto FINGERPRINTPRESENT = g_pConfigManager->getValue<Hyprlang::STRING>("auth:fingerprint:present_message");
m_sFingerprintPresent = *FINGERPRINTPRESENT;
static const auto INACTIVETIMEOUT = g_pConfigManager->getValue<Hyprlang::INT>("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<CTimer> 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<sdbus::IConnection> 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();
});

View file

@ -22,6 +22,8 @@ class CFingerprint : public IAuthImplementation {
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
void onActivity();
std::shared_ptr<sdbus::IConnection> 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<CTimer> 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();
};

View file

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

View file

@ -34,6 +34,8 @@ void CSeatManager::registerSeat(SP<CCWlSeat> 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);

View file

@ -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<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());

View file

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