fix(fingerprint): prevent sensor timeout on long-running sessions

Introduces an inactivity timeout mechanism that pauses fingerprint verification after a configurable period (default: 30 seconds) when no user input is detected. This prevents 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.

Fixes #702
This commit is contained in:
Jérémie Rodon 2026-02-06 17:41:15 +01:00
parent ef3017f5ef
commit cf560a619b
6 changed files with 96 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,62 @@ bool CFingerprint::checkWaiting() {
}
void CFingerprint::terminate() {
// Clean up inactivity timer
if (m_pInactivityTimer) {
m_pInactivityTimer->cancel();
m_pInactivityTimer.reset();
}
if (!m_sDBUSState.abort)
releaseDevice();
}
void CFingerprint::setupInactivityTimer() {
// Check if we should process activity
if (m_sInactiveTimeout <= 0 || m_sDBUSState.abort || m_sDBUSState.done)
return;
// Cancel existing inactivity timer
if (m_pInactivityTimer) {
m_pInactivityTimer->cancel();
m_pInactivityTimer.reset();
}
// Create new inactivity timer
m_pInactivityTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(m_sInactiveTimeout * 1000),
[](ASP<CTimer> self, void* data) { ((CFingerprint*)data)->onInactivityTimeout(); }, this);
}
void CFingerprint::onActivity() {
// Resume scanning if paused
if (!m_sDBUSState.verifying) {
Debug::log(LOG, "fprint: activity detected, resuming verification");
startVerify();
}
}
void CFingerprint::onInactivityTimeout() {
// Check if we should proceed with timeout
if (m_sDBUSState.abort || m_sDBUSState.done || !m_sDBUSState.verifying)
return;
Debug::log(LOG, "fprint: inactivity timeout, pausing verification");
stopVerify();
releaseDevice();
// Clear the device proxy (destructive stop)
m_sDBUSState.device.reset();
// Clear the prompt text to provide user feedback
m_sPrompt = "";
g_pHyprlock->enqueueForceUpdateTimers();
// Clear the inactivity timer
m_pInactivityTimer.reset();
}
std::shared_ptr<sdbus::IConnection> CFingerprint::getConnection() {
return m_sDBUSState.connection;
}
@ -235,8 +289,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

@ -226,6 +226,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{30});
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

@ -552,6 +552,14 @@ void CHyprlock::repeatKey(xkb_keysym_t sym) {
}
void CHyprlock::onKey(uint32_t key, bool down) {
// Notify fingerprint of activity
if (g_pAuth) {
auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT);
if (fpImpl) {
((CFingerprint*)fpImpl.get())->onActivity();
}
}
if (isUnlocked())
return;
@ -660,6 +668,14 @@ void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (!m_focusedOutput->m_sessionLockSurface)
return;
// Notify fingerprint of activity
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) {
@ -707,6 +723,16 @@ void CHyprlock::onHover(const Vector2D& pos) {
m_focusedOutput->m_sessionLockSurface->render();
}
void CHyprlock::onMouseMove(const Vector2D& pos) {
// Notify fingerprint of activity
if (g_pAuth) {
auto fpImpl = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT);
if (fpImpl) {
((CFingerprint*)fpImpl.get())->onActivity();
}
}
}
bool CHyprlock::acquireSessionLock() {
Debug::log(LOG, "Locking session");
m_sLockState.lock = makeShared<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());

View file

@ -48,6 +48,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);