From 9218a34c3e0392c1df327bce70fd6573407d5d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Martin?= Date: Fri, 2 Oct 2026 11:35:13 +0200 Subject: [PATCH] Light the screen (dimmed) while a Notification's Toast shows An Off screen turns on dimmed for the Toast's duration so the user can see what beeped. It isn't user activity: the timeouts aren't restarted, and a key pressed meanwhile reaches the App. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT --- lib/services/src/power_policy.cpp | 6 +++++ lib/services/src/power_policy.h | 10 +++++++++ src/main.cpp | 1 + src/services/power_service.h | 3 +++ src/ui/notifier.cpp | 1 + src/ui/notifier.h | 8 ++++++- test/test_power/test_power.cpp | 37 +++++++++++++++++++++++++++++++ 7 files changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/services/src/power_policy.cpp b/lib/services/src/power_policy.cpp index 7317959..0fd5b01 100644 --- a/lib/services/src/power_policy.cpp +++ b/lib/services/src/power_policy.cpp @@ -12,6 +12,12 @@ bool PowerPolicy::activity(uint32_t nowMs) { ScreenState PowerPolicy::update(uint32_t nowMs) { uint32_t idle = nowMs - lastActivityMs_; state_ = idle >= offMs_ ? ScreenState::Off : idle >= dimMs_ ? ScreenState::Dimmed : ScreenState::On; + if (notifying_) { + if (static_cast(nowMs - notifyUntilMs_) >= 0) + notifying_ = false; + else if (state_ == ScreenState::Off) + state_ = ScreenState::Dimmed; + } return state_; } diff --git a/lib/services/src/power_policy.h b/lib/services/src/power_policy.h index 8313e66..1347b48 100644 --- a/lib/services/src/power_policy.h +++ b/lib/services/src/power_policy.h @@ -19,6 +19,14 @@ class PowerPolicy { // Records a key press. Returns true if the key only woke an Off screen and must not reach the App. bool activity(uint32_t nowMs); + // A Notification is showing until untilMs: an Off screen lights up Dimmed meanwhile. It's not + // user activity, so it doesn't restart the timeouts. + void notify(uint32_t nowMs, uint32_t untilMs) { + (void)nowMs; + notifyUntilMs_ = untilMs; + notifying_ = true; + } + ScreenState update(uint32_t nowMs); ScreenState state() const { return state_; } @@ -26,6 +34,8 @@ class PowerPolicy { uint32_t dimMs_; uint32_t offMs_; uint32_t lastActivityMs_ = 0; + uint32_t notifyUntilMs_ = 0; + bool notifying_ = false; ScreenState state_ = ScreenState::On; }; diff --git a/src/main.cpp b/src/main.cpp index 77b8391..5efb0fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -79,6 +79,7 @@ void setup() { power = new PowerService(settings); clockService = new ClockService(settings, bus); notifier = new Notifier(bus, settings); + notifier->onShow = [](uint32_t now, uint32_t until) { power->onNotification(now, until); }; services.add(*power); services.add(*clockService); services.add(*battery); diff --git a/src/services/power_service.h b/src/services/power_service.h index 3730983..6af6f3e 100644 --- a/src/services/power_service.h +++ b/src/services/power_service.h @@ -19,6 +19,9 @@ class PowerService : public Service { // Call for every key press before routing it. Returns true if the key must not reach the App. bool onKey(uint32_t nowMs) { return policy_.activity(nowMs); } + // A Toast is showing until untilMs: light an Off screen (dimmed) so it can be read. + void onNotification(uint32_t nowMs, uint32_t untilMs) { policy_.notify(nowMs, untilMs); } + ScreenState screen() const { return applied_; } private: diff --git a/src/ui/notifier.cpp b/src/ui/notifier.cpp index ad6962a..49a46be 100644 --- a/src/ui/notifier.cpp +++ b/src/ui/notifier.cpp @@ -31,6 +31,7 @@ void Notifier::update(uint32_t nowMs) { if (shown && shown->id != alertedId_) { alertedId_ = shown->id; alert(shown->level, nowMs); + if (onShow) onShow(nowMs, nowMs + kToastMs); } if (ledOn_ && static_cast(nowMs - ledOffAtMs_) >= 0) { rgbLedWrite(kLedPin, 0, 0, 0); diff --git a/src/ui/notifier.h b/src/ui/notifier.h index 01f6f1e..2f22e19 100644 --- a/src/ui/notifier.h +++ b/src/ui/notifier.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "event_bus.h" #include "settings.h" #include "toast_queue.h" @@ -14,11 +16,15 @@ class Notifier { void update(uint32_t nowMs); // alerts for a newly shown Toast, ends the LED flash ToastQueue& toasts() { return toasts_; } + // Called when a Toast appears, with the time it will disappear (e.g. to light the screen). + std::function onShow; + private: void alert(NotificationLevel level, uint32_t nowMs); const Settings& settings_; - ToastQueue toasts_{3000, 8}; + static constexpr uint32_t kToastMs = 3000; + ToastQueue toasts_{kToastMs, 8}; uint32_t ledOffAtMs_ = 0; bool ledOn_ = false; uint32_t alertedId_ = 0; diff --git a/test/test_power/test_power.cpp b/test/test_power/test_power.cpp index 0751398..60bd00d 100644 --- a/test/test_power/test_power.cpp +++ b/test/test_power/test_power.cpp @@ -38,6 +38,39 @@ void test_new_timeouts_apply_immediately() { TEST_ASSERT_EQUAL(static_cast(ScreenState::Off), static_cast(p.update(20000))); } +void test_notification_lights_an_off_screen_dimmed_until_it_ends() { + PowerPolicy p(30000, 60000); + p.activity(0); + p.update(70000); // off + p.notify(70000, 73000); + TEST_ASSERT_EQUAL(static_cast(ScreenState::Dimmed), static_cast(p.update(70001))); + TEST_ASSERT_EQUAL(static_cast(ScreenState::Dimmed), static_cast(p.update(72999))); + TEST_ASSERT_EQUAL(static_cast(ScreenState::Off), static_cast(p.update(73000))); +} + +void test_notification_does_not_count_as_activity() { + PowerPolicy p(30000, 60000); + p.activity(0); + p.notify(25000, 28000); + TEST_ASSERT_EQUAL(static_cast(ScreenState::Dimmed), static_cast(p.update(30000))); +} + +void test_notification_never_brightens_or_dims_an_on_screen() { + PowerPolicy p(30000, 60000); + p.activity(0); + p.notify(1000, 4000); + TEST_ASSERT_EQUAL(static_cast(ScreenState::On), static_cast(p.update(2000))); +} + +void test_key_on_a_notification_lit_screen_acts() { + PowerPolicy p(30000, 60000); + p.activity(0); + p.update(70000); + p.notify(70000, 73000); + p.update(70001); + TEST_ASSERT_FALSE(p.activity(71000)); // the screen is visible: the key reaches the App +} + void test_short_press_reported_on_release() { PowerButton b(2000); TEST_ASSERT_EQUAL(static_cast(ButtonAction::None), static_cast(b.update(true, 0))); @@ -59,6 +92,10 @@ int main() { RUN_TEST(test_key_on_dimmed_screen_wakes_and_still_acts); RUN_TEST(test_key_on_off_screen_only_wakes); RUN_TEST(test_new_timeouts_apply_immediately); + RUN_TEST(test_notification_lights_an_off_screen_dimmed_until_it_ends); + RUN_TEST(test_notification_does_not_count_as_activity); + RUN_TEST(test_notification_never_brightens_or_dims_an_on_screen); + RUN_TEST(test_key_on_a_notification_lit_screen_acts); RUN_TEST(test_short_press_reported_on_release); RUN_TEST(test_long_press_fires_once_while_still_held); return UNITY_END();