Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions projects/APPLaunch/main/ui/settings/settings_page.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,71 @@ void bluetooth_named_only_api(int cmd, void *data)
bluetooth_named_only_state = previous;
}

// "lock_screen" (1 = on, the default): when off, the idle timeout still turns the
// screen black and the backlight off, but any key wakes straight back to the launcher
// instead of asking for TAB then ENTER. Read by ui_screensaver.cpp on each lock.
constexpr const char *kLockScreenConfigKey = "lock_screen";
bool lock_screen_state = true;
std::mutex lock_screen_mutex;

bool read_lock_screen_config(bool fallback)
{
int value = fallback ? 1 : 0;
try {
cp0_signal_config_api(
{"GetInt", kLockScreenConfigKey, "1"},
[&](int code, std::string data) {
int parsed = 0;
const char *begin = data.data();
const char *end = begin + data.size();
const auto result = std::from_chars(begin, end, parsed);
if (code == 0 && result.ec == std::errc{} && result.ptr == end &&
(parsed == 0 || parsed == 1))
value = parsed;
});
} catch (...) {
}
return value != 0;
}

bool write_lock_screen_config(bool enabled)
{
bool succeeded = false;
try {
cp0_signal_config_api({"SetInt", kLockScreenConfigKey, enabled ? "1" : "0"},
[&](int code, std::string) { succeeded = code == 0; });
} catch (...) {
}
return succeeded;
}

void lock_screen_api(int cmd, void *data)
{
std::lock_guard<std::mutex> state_lock(lock_screen_mutex);
if ((cmd == SettingApiReadFlag || cmd == SettingApiReadFlagTimeStart) && data) {
lock_screen_state = read_lock_screen_config(lock_screen_state);
if (cmd == SettingApiReadFlag)
*static_cast<bool *>(data) = lock_screen_state;
else
std::get<0>(*static_cast<SettingApiReadFlagTimeStartData *>(data)) = lock_screen_state;
return;
}
if (cmd != SettingApiActivate) return;

const bool previous = read_lock_screen_config(lock_screen_state);
const bool desired = !previous;
if (!write_lock_screen_config(desired)) {
lock_screen_state = previous;
return;
}
if (save_bluetooth_config()) { // "Save" persists the whole config file
lock_screen_state = desired;
return;
}
if (write_lock_screen_config(previous)) (void)save_bluetooth_config();
lock_screen_state = previous;
}

static void append_numeric_options(Tree &tree, const NodeIter &parent, int first, int last)
{
for (int value = first; value <= last; ++value) tree.append_child(parent, SettingEntry{std::to_string(value)});
Expand Down Expand Up @@ -621,6 +686,7 @@ void UISettingTreePage::create_page_detail()
mode_tree.append_child(dark_time, SettingEntry{"60S"});
mode_tree.append_child(dark_time, SettingEntry{"300S"});
}
mode_tree.append_child(screen, SettingEntry{"Lock Screen", lock_screen_api, true});
}

{
Expand Down
26 changes: 25 additions & 1 deletion projects/APPLaunch/main/ui/ui_screensaver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ LockscreenBackgroundCache s_background_cache;
/* Raw backlight value captured when the panel is forced dark, or -1 while the
* backlight is under normal control. */
int s_screen_off_backlight_raw = -1;
/* Read from "lock_screen" on every lock: when off, any key wakes straight back. */
bool s_lock_enabled = true;

struct ScreensaverPanel
{
Expand Down Expand Up @@ -360,6 +362,20 @@ void hint_delete_cb(lv_event_t *event) noexcept
}
}

bool lock_screen_enabled() noexcept
{
int value = 1;
try {
cp0_signal_config_api({"GetInt", "lock_screen", "1"},
[&](int code, std::string data) {
if (code == 0 && (data == "0" || data == "1"))
value = data == "1" ? 1 : 0;
});
} catch (...) {
}
return value != 0;
}

int timeout_seconds() noexcept
{
try {
Expand Down Expand Up @@ -482,9 +498,11 @@ void enter_lockscreen()
s_lock.reset(now);
launcher_battery_ui::refresh_visibility();

s_lock_enabled = lock_screen_enabled();
suspend_system_sound();
suspend_screen_off_backlight();
play_lockscreen_sound("lock.mp3");
if (s_lock_enabled)
play_lockscreen_sound("lock.mp3");

lv_obj_move_foreground(s_overlay);
lv_obj_clear_flag(s_overlay, LV_OBJ_FLAG_HIDDEN);
Expand Down Expand Up @@ -598,6 +616,12 @@ extern "C" int ui_screensaver_filter_key(const struct key_item *item)
return 1;

if (s_model.active()) {
if (!s_lock_enabled) {
/* Lock screen off: the first key only wakes the screen, without a sound. */
if (pressed)
stop_screensaver(true, true);
return 1;
}
/* The lock screen owns every key while it is up. */
const LockscreenDecision decision =
s_lock.handle_key(item->key_code, pressed, released, now);
Expand Down
49 changes: 47 additions & 2 deletions projects/APPLaunch/tests/low_battery_ui/test_low_battery_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ static int backlight_suspends;
static int backlight_restores;
static bool fail_backlight_write;
static std::string screensaver_timeout = "0";
static std::string lock_screen = "1";

decltype(cp0_signal_config_api) cp0_signal_config_api;
decltype(cp0_signal_audio_api) cp0_signal_audio_api;
Expand Down Expand Up @@ -447,6 +448,48 @@ static void test_unlock_restarts_idle_timeout()
screensaver_timeout = "0";
}

/* Settings > Screen > Lock Screen off: the idle timeout still blacks the panel and
* turns the backlight off, but without the lock sound, and the first fresh key
* wakes straight back (consumed, so it does not also act on the page). */
static void test_lock_screen_off_wakes_on_any_key()
{
set_battery(80);
advance(3000);
assert(!visible());
screensaver_timeout = "10";
lock_screen = "0";
backlight_raw = 128;
s_last_sound_asset = "";

key(KEY_ESC, KBD_KEY_PRESSED, false);
key(KEY_ESC, KBD_KEY_RELEASED, false);
advance(10000);
::timer_cb(s_timer);
assert(ui_screensaver_is_active() && s_panel.black);
assert(backlight_raw == 0);
assert_sound("");

key(KEY_A, KBD_KEY_PRESSED, true);
assert(s_exiting && ui_screensaver_is_active());
key(KEY_A, KBD_KEY_RELEASED, true);
advance(400);
lv_anim_refr_now();
assert(!ui_screensaver_is_active());
assert(backlight_raw == 128);
assert_sound("");

// The setting is read on each lock: back on, the next lock asks for TAB, ENTER.
lock_screen = "1";
advance(10000);
::timer_cb(s_timer);
assert(ui_screensaver_is_active() && s_panel.black);
assert_sound("lock.mp3");
unlock_screensaver();
key(KEY_ESC, KBD_KEY_PRESSED, false);
key(KEY_ESC, KBD_KEY_RELEASED, false);
screensaver_timeout = "0";
}

static void test_screensaver_black_is_independent_of_backlight()
{
set_battery(80);
Expand Down Expand Up @@ -693,8 +736,9 @@ int main()
static_cast<lv_event_code_t>(LV_EVENT_KEYBOARD), nullptr);
cp0_keyboard_set_global_key_handler([](const key_item *) { ++global_events; });
cp0_keyboard_set_key_filter(previous_filter);
cp0_signal_config_api.append([](std::list<std::string>, std::function<void(int, std::string)> reply) {
if (reply) reply(0, screensaver_timeout);
cp0_signal_config_api.append([](std::list<std::string> arguments, std::function<void(int, std::string)> reply) {
const bool lock_key = arguments.size() > 1 && *std::next(arguments.begin()) == "lock_screen";
if (reply) reply(0, lock_key ? lock_screen : screensaver_timeout);
});
cp0_signal_audio_api.append([](std::list<std::string>, std::function<void(int, std::string)> reply) {
if (reply) reply(0, "");
Expand Down Expand Up @@ -836,6 +880,7 @@ int main()
test_hold_gesture_enters_lock();
test_screensaver_panel();
test_unlock_restarts_idle_timeout();
test_lock_screen_off_wakes_on_any_key();
test_screensaver_black_is_independent_of_backlight();
shutdown_warning();
assert(!overlay && !warning_timer);
Expand Down
10 changes: 10 additions & 0 deletions projects/APPLaunch/tests/test_lockscreen_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,13 @@
assert "void restore_backlight(int raw);" in CONTROLS_HEADER
assert "write_backlight_raw(0)" in CONTROLS
assert "if (raw <= 0) return;" in CONTROLS

# Settings > Screen > Lock Screen ("lock_screen", default on). Off keeps the idle
# screen-off but skips the lock: the setting is read on every lock, no lock sound
# plays, and the first key wakes the screen before the lock model sees it.
SETTINGS = (ROOT / "main/ui/settings/settings_page.cpp").read_text()
assert '"lock_screen"' in SETTINGS and '"lock_screen"' in SOURCE
assert "s_lock_enabled = lock_screen_enabled();" in SOURCE
assert "if (s_lock_enabled)\n play_lockscreen_sound(\"lock.mp3\");" in SOURCE
bypass = SOURCE.index("if (!s_lock_enabled) {")
assert bypass < SOURCE.index("s_lock.handle_key(item->key_code")
2 changes: 1 addition & 1 deletion projects/APPLaunch/tests/test_settings_menu_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_screen_keeps_lock_timeout_options():
screen_entries = re.findall(
r'append_child\(\s*screen,\s*SettingEntry\{"([^"]+)"', builder
)
assert screen_entries == ["Brightness", "DarkTime"]
assert screen_entries == ["Brightness", "DarkTime", "Lock Screen"]
timeouts = re.findall(
r'append_child\(\s*dark_time,\s*SettingEntry\{"([^"]+)"', builder
)
Expand Down
Loading