diff --git a/src/app/application.h b/src/app/application.h index f7d16cff58..77782752ec 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -251,6 +251,8 @@ class Application { bool m_releaseSleepDelayWhenLocked = false; // Set before Noctalia-initiated suspend so PrepareForSleep skips lock-before-sleep. bool m_skipLockOnNextSleep = false; + // Prevents the unlock hook from resuming tracking during suspend. + bool m_preparingForSleep = false; std::unique_ptr m_accountsService; std::unique_ptr m_screenSaverService; std::unique_ptr m_screenSaverPollSource; diff --git a/src/app/application_services.cpp b/src/app/application_services.cpp index 81c38af243..8759ff4c27 100644 --- a/src/app/application_services.cpp +++ b/src/app/application_services.cpp @@ -964,6 +964,9 @@ void Application::initSystemBusServices() { // fade-complete cleanup races with process freeze. m_idleGraceOverlay.hide(); if (sleeping) { + // Screen time must not accumulate across suspend even when lock-before-suspend is off. + m_preparingForSleep = true; + m_screenTimeService.setTrackingPaused(true); // Delay inhibit (when lock_before_suspend is on) holds sleep until we lock. // Do not use runAfterSessionLocked here — that slot belongs to lock-and-suspend. if (m_skipLockOnNextSleep) { @@ -1011,6 +1014,12 @@ void Application::initSystemBusServices() { } m_skipLockOnNextSleep = false; m_releaseSleepDelayWhenLocked = false; + m_preparingForSleep = false; + if (!m_lockScreen.isSessionLocked()) { + // After wake, resume tracking if the session is already unlocked. + // Otherwise, keep it paused until the session unlocks. + m_screenTimeService.setTrackingPaused(false); + } if (m_configService.shouldLockBeforeSuspend() && m_logindService != nullptr) { (void)m_logindService->acquireSleepDelayInhibit(); } diff --git a/src/app/application_ui.cpp b/src/app/application_ui.cpp index 12eb2ffbb8..edc36feded 100644 --- a/src/app/application_ui.cpp +++ b/src/app/application_ui.cpp @@ -327,6 +327,7 @@ void Application::initLockScreenAndSession() { m_idleGraceOverlay.hide(); m_lockscreenWidgetsController.onLockStateChanged(); m_idleManager.setSessionLocked(true); + m_screenTimeService.setTrackingPaused(true); m_hookManager.fire(HookKind::SessionLocked); releaseSleepDelayInhibitIfPending(); }, @@ -334,6 +335,9 @@ void Application::initLockScreenAndSession() { m_idleGraceOverlay.hide(); m_lockscreenWidgetsController.onLockStateChanged(); m_idleManager.setSessionLocked(false); + if (!m_preparingForSleep) { + m_screenTimeService.setTrackingPaused(false); + } m_hookManager.fire(HookKind::SessionUnlocked); // Lock aborted before engage (e.g. compositor finished the lock object) — still release // so PrepareForSleep is not stuck on the delay inhibit. diff --git a/src/system/screen_time_service.cpp b/src/system/screen_time_service.cpp index a09cc90be6..37233ecf0d 100644 --- a/src/system/screen_time_service.cpp +++ b/src/system/screen_time_service.cpp @@ -313,18 +313,47 @@ void ScreenTimeService::setEnabled(bool enabled) { } m_tickTimer.stop(); } else { - onFocusChange(); - if (!m_tickTimer.active()) { - m_tickTimer.startRepeating(kTickInterval, [this]() { tick(); }); - } + restartTicking(); } if (m_changeCallback) { m_changeCallback(); } } +void ScreenTimeService::restartTicking() { + if (m_trackingPaused) { + return; + } + onFocusChange(); + if (!m_tickTimer.active()) { + m_tickTimer.startRepeating(kTickInterval, [this]() { tick(); }); + } +} + +void ScreenTimeService::setTrackingPaused(bool paused) { + if (m_trackingPaused == paused) { + return; + } + m_trackingPaused = paused; + if (paused) { + // Pause counts (session lock or suspend) and stop counting. + flushActiveSession(std::chrono::steady_clock::now()); + m_activeAppKey.clear(); + m_activeSince = {}; + if (m_dirty) { + save(); + } + m_tickTimer.stop(); + } else { + // Resume on unlock/login, resume the tick timer and credit the current app. + if (m_enabled) { + restartTicking(); + } + } +} + void ScreenTimeService::onFocusChange() { - if (!m_enabled) { + if (!m_enabled || m_trackingPaused) { return; } const std::string candidate = appKeyForActive(); diff --git a/src/system/screen_time_service.h b/src/system/screen_time_service.h index 9752a15dd7..7bad24a88f 100644 --- a/src/system/screen_time_service.h +++ b/src/system/screen_time_service.h @@ -48,6 +48,7 @@ class ScreenTimeService { void onFocusChange(); void setChangeCallback(std::function callback); void setEnabled(bool enabled); + void setTrackingPaused(bool paused); [[nodiscard]] bool enabled() const noexcept { return m_enabled; } [[nodiscard]] ScreenTimeSnapshot snapshot(int rangeDays = 1); @@ -65,6 +66,7 @@ class ScreenTimeService { }; void tick(); + void restartTicking(); void flushActiveSession(std::chrono::steady_clock::time_point now); void ensureCurrentDayLocked(std::chrono::system_clock::time_point now); void load(); @@ -96,4 +98,5 @@ class ScreenTimeService { std::chrono::steady_clock::time_point m_activeSince; bool m_dirty = false; bool m_enabled = false; + bool m_trackingPaused = false; };