From f55ac265fed5f1ef591151acfb991a5bc87f393f Mon Sep 17 00:00:00 2001 From: dianjixz <18637716021@163.com> Date: Sun, 20 Sep 2026 19:03:19 +0800 Subject: [PATCH] feat(APPLaunch): add terminal help overlays --- projects/APPLaunch/AGENTS.md | 4 +- .../main/ui/builtin_app_registry.cpp | 17 +++++- .../APPLaunch/main/ui/cli_terminal_help.cpp | 50 +++++++++++++++ .../APPLaunch/main/ui/cli_terminal_help.hpp | 11 ++++ projects/APPLaunch/main/ui/launch.cpp | 5 +- projects/APPLaunch/main/ui/launch.h | 32 +++++++++- projects/APPLaunch/main/ui/launcher_app.cpp | 17 ++++++ .../main/ui/model/screensaver_model.hpp | 2 +- .../APPLaunch/main/ui/page_app/ui_app_st.cpp | 6 +- .../APPLaunch/main/ui/page_app/ui_app_st.hpp | 10 ++- .../main/ui/page_app/ui_app_st_renderer.cpp | 61 ++++++++++++++++++- .../main/ui/page_app/ui_app_st_session.cpp | 1 + .../main/ui/python_terminal_help.cpp | 42 +++++++++++++ .../main/ui/python_terminal_help.hpp | 11 ++++ .../ui/settings/settings_about_help_model.cpp | 9 ++- .../main/ui/terminal_help_factory.hpp | 11 ++++ projects/APPLaunch/main/ui/ui_screensaver.cpp | 2 +- .../low_battery_ui/test_low_battery_ui.cpp | 4 +- .../tests/test_lockscreen_contract.py | 2 +- .../tests/test_screensaver_model.cpp | 8 +-- 20 files changed, 282 insertions(+), 23 deletions(-) create mode 100644 projects/APPLaunch/main/ui/cli_terminal_help.cpp create mode 100644 projects/APPLaunch/main/ui/cli_terminal_help.hpp create mode 100644 projects/APPLaunch/main/ui/python_terminal_help.cpp create mode 100644 projects/APPLaunch/main/ui/python_terminal_help.hpp create mode 100644 projects/APPLaunch/main/ui/terminal_help_factory.hpp diff --git a/projects/APPLaunch/AGENTS.md b/projects/APPLaunch/AGENTS.md index 24d560cf..65b971a8 100644 --- a/projects/APPLaunch/AGENTS.md +++ b/projects/APPLaunch/AGENTS.md @@ -166,8 +166,8 @@ neither business path. The screensaver filter also observes one key it does not own: while idle it tracks the press and release of the long-press gesture that enters the lock and reports them as *not* consumed, so the page underneath keeps the short-press -meaning of that key. The gesture is TAB held for 5000 ms; the model announces it -with `Hold TAB for 5s to lock` on the shared launcher toast from its hint delay (500 ms) +meaning of that key. The gesture is TAB held for 3000 ms; the model announces it +with `Hold TAB for 3s to lock` on the shared launcher toast from its hint delay (500 ms) until the hold ends. Clear that toast on release, on a different key, and when the lock is entered or left. Once the gesture matures the lock takes every key over, and because only a fresh press changes lock state, the held key's repeats and its diff --git a/projects/APPLaunch/main/ui/builtin_app_registry.cpp b/projects/APPLaunch/main/ui/builtin_app_registry.cpp index ff5046af..597329f8 100644 --- a/projects/APPLaunch/main/ui/builtin_app_registry.cpp +++ b/projects/APPLaunch/main/ui/builtin_app_registry.cpp @@ -8,11 +8,13 @@ #include "app_display_order.hpp" #include "app_registry.h" +#include "cli_terminal_help.hpp" #include "launch.h" #include "ui.h" #include "generated/page_app.h" #include "launcher_platform.hpp" #include "model/dynamic_app_registry.hpp" +#include "python_terminal_help.hpp" #include "settings/settings_page.hpp" #include #include @@ -30,6 +32,7 @@ struct BuiltinAppRegistration { bool sysplause; bool run_as_root; BuiltinAppAppender append; + TerminalHelpFactory help_factory = nullptr; }; template @@ -38,6 +41,12 @@ void append_page_app(std::list &apps, const AppDescriptor &desc) apps.emplace_back(desc.label, launcher_platform::path(desc.icon), page_v); } +void append_cli_app(std::list &apps, const AppDescriptor &desc) +{ + apps.emplace_back(desc.label, launcher_platform::path(desc.icon), page_v, + create_cli_help); +} + std::string resolved_exec(const BuiltinAppRegistration ®istration) { std::string exec = registration.exec ? registration.exec : ""; @@ -56,7 +65,8 @@ void append_builtin_app(std::list &apps, const BuiltinAppRegistration ®i resolved_exec(registration), registration.terminal, registration.sysplause, - registration.run_as_root); + registration.run_as_root, + registration.help_factory); } constexpr BuiltinAppRegistration BUILTIN_APPS[] = { @@ -65,8 +75,9 @@ constexpr BuiltinAppRegistration BUILTIN_APPS[] = { {{"Store", "store_100.png", "app_Store", false, true}, "@appstore_exec", false, true, false, nullptr}, {{"CLI", "cli_100.png", "app_CLI", false, true}, - nullptr, false, true, false, append_page_app}, - {{"Python", "python_100.png", "app_Python", true, false}, "python3", true, false, false, nullptr}, + nullptr, false, true, false, append_cli_app}, + {{"Python", "python_100.png", "app_Python", true, false}, + "python3", true, false, false, nullptr, create_python_help}, #if defined(__linux__) && !defined(HAL_PLATFORM_SDL) {{"SSH", "ssh_100.png", "app_SSH", true, false}, nullptr, false, true, false, append_page_app}, diff --git a/projects/APPLaunch/main/ui/cli_terminal_help.cpp b/projects/APPLaunch/main/ui/cli_terminal_help.cpp new file mode 100644 index 00000000..884c6203 --- /dev/null +++ b/projects/APPLaunch/main/ui/cli_terminal_help.cpp @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "cli_terminal_help.hpp" + +lv_obj_t *create_cli_help(lv_obj_t *parent) +{ + if (!parent) return nullptr; + + lv_obj_t *overlay = lv_obj_create(parent); + if (!overlay) return nullptr; + lv_obj_remove_style_all(overlay); + lv_obj_set_size(overlay, 320, 150); + lv_obj_set_pos(overlay, 0, 0); + lv_obj_set_style_bg_color(overlay, lv_color_hex(0x10151C), 0); + lv_obj_set_style_bg_opa(overlay, LV_OPA_COVER, 0); + lv_obj_clear_flag(overlay, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(overlay, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_dir(overlay, LV_DIR_VER); + lv_obj_set_scrollbar_mode(overlay, LV_SCROLLBAR_MODE_OFF); + + lv_obj_t *label = lv_label_create(overlay); + if (!label) { + lv_obj_delete(overlay); + return nullptr; + } + lv_obj_set_pos(label, 6, 1); + lv_obj_set_size(label, 308, LV_SIZE_CONTENT); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_obj_set_style_text_font(label, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_color(label, lv_color_hex(0xF0F6FC), 0); + lv_obj_set_style_text_line_space(label, 0, 0); + lv_label_set_text(label, + "A terminal for command-line tools.\n\n" + "Supports two display modes: Fit to Width, which may be cramped on the " + "small screen, and Viewport, which lets you pan around a larger terminal " + "area.\n\n" + "fn + Z / C: move cursor left / right\n" + "fn + F / X: previous / next command\n" + "fn + K: move cursor to line start (Home)\n" + "fn + N: move cursor to line end (End)\n" + "shift + fn + L: page up\n" + "shift + fn + M: page down\n\n" + "F6 (fn + 6): enter / exit Viewport mode\n" + "F4/F5/F7/F8 (fn + 4/5/7/8): pan the viewport"); + return overlay; +} diff --git a/projects/APPLaunch/main/ui/cli_terminal_help.hpp b/projects/APPLaunch/main/ui/cli_terminal_help.hpp new file mode 100644 index 00000000..c7d5167f --- /dev/null +++ b/projects/APPLaunch/main/ui/cli_terminal_help.hpp @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "lvgl/lvgl.h" + +lv_obj_t *create_cli_help(lv_obj_t *parent); diff --git a/projects/APPLaunch/main/ui/launch.cpp b/projects/APPLaunch/main/ui/launch.cpp index 419024f5..c12ca06d 100644 --- a/projects/APPLaunch/main/ui/launch.cpp +++ b/projects/APPLaunch/main/ui/launch.cpp @@ -129,13 +129,14 @@ void Launch::abort_page_launch() noexcept } } -void Launch::launch_Exec_in_terminal(const std::string &exec, bool sysplause) +void Launch::launch_Exec_in_terminal(const std::string &exec, bool sysplause, + TerminalHelpFactory help_factory) { if (!begin_page_launch()) return; SLOGI("Launching terminal app: %s", exec.c_str()); ui_loading::show("Loading..."); lv_refr_now(nullptr); - auto p = std::make_shared(); + auto p = std::make_shared(help_factory); if (!p->screen()) throw std::runtime_error("terminal page creation failed"); app_Page = p; diff --git a/projects/APPLaunch/main/ui/launch.h b/projects/APPLaunch/main/ui/launch.h index f8578b59..1b7509b3 100644 --- a/projects/APPLaunch/main/ui/launch.h +++ b/projects/APPLaunch/main/ui/launch.h @@ -12,6 +12,7 @@ #include "ui_loading.h" #include "esc_ui_watchdog.h" #include "model/launcher_navigation_model.hpp" +#include "terminal_help_factory.hpp" #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include class Launch; @@ -43,9 +45,14 @@ struct app app(std::string name, std::string icon, std::string exec, bool terminal); app(std::string name, std::string icon, std::string exec, bool terminal, bool sysplause); app(std::string name, std::string icon, std::string exec, bool terminal, bool sysplause, bool run_as_root); + app(std::string name, std::string icon, std::string exec, bool terminal, bool sysplause, + bool run_as_root, TerminalHelpFactory help_factory); template app(std::string name, std::string icon, page_t tag); + template + app(std::string name, std::string icon, page_t tag, + TerminalHelpFactory help_factory); }; class Launch @@ -69,7 +76,8 @@ class Launch void go_back_home(); bool begin_page_launch(); void abort_page_launch() noexcept; - void launch_Exec_in_terminal(const std::string &exec, bool sysplause = true); + void launch_Exec_in_terminal(const std::string &exec, bool sysplause = true, + TerminalHelpFactory help_factory = nullptr); void launch_Exec(const std::string &exec, bool keep_root = false); void applications_load(); void refresh_home_carousel(); @@ -111,3 +119,25 @@ app::app(std::string name, std::string icon, page_t) cp0_lvgl_start_app_page(*page); }; } + +template +app::app(std::string name, std::string icon, page_t, TerminalHelpFactory help_factory) + : Name(std::move(name)), Icon(std::move(icon)) +{ + launch = [help_factory](Launch *owner) { + if (!owner->begin_page_launch()) return; + ui_loading::show("Loading..."); + lv_refr_now(nullptr); + std::shared_ptr page; + if constexpr (std::is_constructible_v) + page = std::make_shared(help_factory); + else + page = std::make_shared(); + if (!page->screen()) + throw std::runtime_error("application page creation failed"); + owner->app_Page = page; + page->navigate_home = std::bind(&Launch::go_back_home, owner); + ui_loading::hide(); + cp0_lvgl_start_app_page(*page); + }; +} diff --git a/projects/APPLaunch/main/ui/launcher_app.cpp b/projects/APPLaunch/main/ui/launcher_app.cpp index e6d2eda6..f960cbc5 100644 --- a/projects/APPLaunch/main/ui/launcher_app.cpp +++ b/projects/APPLaunch/main/ui/launcher_app.cpp @@ -42,3 +42,20 @@ app::app(std::string name, else owner->launch_Exec(exec, run_as_root); }; } + +app::app(std::string name, + std::string icon, + std::string exec, + bool terminal, + bool sysplause, + bool run_as_root, + TerminalHelpFactory help_factory) + : Name(std::move(name)), Icon(std::move(icon)), Exec(exec) +{ + launch = [exec = std::move(exec), terminal, sysplause, run_as_root, + help_factory](Launch *owner) { + if (!owner || exec.empty()) return; + if (terminal) owner->launch_Exec_in_terminal(exec, sysplause, help_factory); + else owner->launch_Exec(exec, run_as_root); + }; +} diff --git a/projects/APPLaunch/main/ui/model/screensaver_model.hpp b/projects/APPLaunch/main/ui/model/screensaver_model.hpp index 5bb362f4..1774c1fd 100644 --- a/projects/APPLaunch/main/ui/model/screensaver_model.hpp +++ b/projects/APPLaunch/main/ui/model/screensaver_model.hpp @@ -40,7 +40,7 @@ class ScreensaverModel Count = 8, }; enum class ScreenOffMetric : uint32_t { - HoldMs = 5000, + HoldMs = 3000, }; enum class HoldHintMetric : uint32_t { HintMs = 500, diff --git a/projects/APPLaunch/main/ui/page_app/ui_app_st.cpp b/projects/APPLaunch/main/ui/page_app/ui_app_st.cpp index 9c007741..08e277cf 100644 --- a/projects/APPLaunch/main/ui/page_app/ui_app_st.cpp +++ b/projects/APPLaunch/main/ui/page_app/ui_app_st.cpp @@ -13,12 +13,13 @@ #include #include -UISTPage::UISTPage() - : AppPage() +UISTPage::UISTPage(TerminalHelpFactory help_factory) + : AppPage(), help_factory_(help_factory) { set_page_title("CLI"); reset_terminal(); create_ui(); + create_help(); if (!renderer_ready()) { if (terminal_container_) lv_obj_delete(terminal_container_); return; @@ -55,6 +56,7 @@ void UISTPage::exec(const std::string &command, const std::list &ar terminal_active_ = true; waiting_key_to_exit_ = false; + hide_help(); big_mode_ = false; term_cols_ = NORMAL_COLS; term_rows_ = NORMAL_ROWS; diff --git a/projects/APPLaunch/main/ui/page_app/ui_app_st.hpp b/projects/APPLaunch/main/ui/page_app/ui_app_st.hpp index 8e2d3748..40bd0bc0 100644 --- a/projects/APPLaunch/main/ui/page_app/ui_app_st.hpp +++ b/projects/APPLaunch/main/ui/page_app/ui_app_st.hpp @@ -10,6 +10,7 @@ #include "../launcher_ui_app_page.hpp" #include "../model/page_timer_lifecycle.hpp" #include "../model/st_page_contract.hpp" +#include "../terminal_help_factory.hpp" #include "../model/terminal_unicode.hpp" #include "cp0_lvgl_app.h" #include "input_keys.h" @@ -132,7 +133,7 @@ class UISTPage : public AppPage public: bool terminal_sysplause = true; - UISTPage(); + explicit UISTPage(TerminalHelpFactory help_factory = nullptr); ~UISTPage(); void exec(std::string cmd); @@ -170,6 +171,7 @@ class UISTPage : public AppPage lv_obj_t *scrollbar_thumb_ = nullptr; lv_obj_t *hscrollbar_track_ = nullptr; lv_obj_t *hscrollbar_thumb_ = nullptr; + lv_obj_t *help_overlay_ = nullptr; std::array bottom_labels_{}; std::array bottom_indicators_{}; const lv_font_t *mono_font_ = nullptr; @@ -178,6 +180,7 @@ class UISTPage : public AppPage PageTimerLifecycle cursor_timer_; std::string pty_handle_; + TerminalHelpFactory help_factory_ = nullptr; bool terminal_active_ = false; bool waiting_key_to_exit_ = false; bool cursor_blink_visible_ = false; @@ -240,6 +243,11 @@ class UISTPage : public AppPage void process_bytes(const char *data, int length); void create_ui(); + void create_help(); + void show_help(); + void hide_help(); + void scroll_help(int direction); + bool help_visible() const; bool renderer_ready() const; void bind_events(); void detach_renderer_callbacks(); diff --git a/projects/APPLaunch/main/ui/page_app/ui_app_st_renderer.cpp b/projects/APPLaunch/main/ui/page_app/ui_app_st_renderer.cpp index 0d230c9a..f41f82af 100644 --- a/projects/APPLaunch/main/ui/page_app/ui_app_st_renderer.cpp +++ b/projects/APPLaunch/main/ui/page_app/ui_app_st_renderer.cpp @@ -60,7 +60,8 @@ void UISTPage::create_ui() 0, BIG_VIEW_ROWS * CHAR_H, 0x8B949E); static constexpr const char *BOTTOM_TEXT[BOTTOM_BAR_SLOTS] = { - "F4 <", "F5 up", "F6 normal", "F7 down", "F8 >", + "F4 " LV_SYMBOL_LEFT, "F5 " LV_SYMBOL_UP, "F6 normal", + "F7 " LV_SYMBOL_DOWN, "F8 " LV_SYMBOL_RIGHT, }; constexpr int SLOT_WIDTH = TERM_W / BOTTOM_BAR_SLOTS; for (int index = 0; index < BOTTOM_BAR_SLOTS; ++index) { @@ -119,6 +120,41 @@ void UISTPage::create_ui() lv_obj_add_flag(cursor_label_, LV_OBJ_FLAG_HIDDEN); } +void UISTPage::create_help() +{ + if (!help_factory_ || !terminal_container_ || help_overlay_) return; + help_overlay_ = help_factory_(terminal_container_); + if (!help_overlay_) return; + lv_obj_add_event_cb(help_overlay_, static_renderer_delete_cb, LV_EVENT_DELETE, this); + lv_obj_add_flag(help_overlay_, LV_OBJ_FLAG_HIDDEN); +} + +bool UISTPage::help_visible() const +{ + return help_overlay_ && !lv_obj_has_flag(help_overlay_, LV_OBJ_FLAG_HIDDEN); +} + +void UISTPage::show_help() +{ + if (!help_overlay_) return; + lv_obj_clear_flag(help_overlay_, LV_OBJ_FLAG_HIDDEN); + lv_obj_move_foreground(help_overlay_); + show_cursor(false); +} + +void UISTPage::hide_help() +{ + if (!help_overlay_) return; + lv_obj_add_flag(help_overlay_, LV_OBJ_FLAG_HIDDEN); + if (terminal_active_) update_cursor(); +} + +void UISTPage::scroll_help(int direction) +{ + if (!help_overlay_ || direction == 0) return; + lv_obj_scroll_by_bounded(help_overlay_, 0, direction * 36, LV_ANIM_ON); +} + void UISTPage::bind_events() { if (!root_screen_) return; @@ -142,7 +178,7 @@ void UISTPage::detach_renderer_callbacks() { std::vector objects = { terminal_container_, term_canvas_, scrollbar_track_, scrollbar_thumb_, - hscrollbar_track_, hscrollbar_thumb_, cursor_label_, + hscrollbar_track_, hscrollbar_thumb_, cursor_label_, help_overlay_, }; objects.insert(objects.end(), bottom_labels_.begin(), bottom_labels_.end()); objects.insert(objects.end(), bottom_indicators_.begin(), bottom_indicators_.end()); @@ -167,6 +203,7 @@ void UISTPage::static_renderer_delete_cb(lv_event_t *event) noexcept if (self->scrollbar_thumb_ == deleted) self->scrollbar_thumb_ = nullptr; if (self->hscrollbar_track_ == deleted) self->hscrollbar_track_ = nullptr; if (self->hscrollbar_thumb_ == deleted) self->hscrollbar_thumb_ = nullptr; + if (self->help_overlay_ == deleted) self->help_overlay_ = nullptr; for (lv_obj_t *&label : self->bottom_labels_) if (label == deleted) label = nullptr; for (lv_obj_t *&indicator : self->bottom_indicators_) @@ -198,6 +235,7 @@ void UISTPage::static_renderer_delete_cb(lv_event_t *event) noexcept self->scrollbar_thumb_ = nullptr; self->hscrollbar_track_ = nullptr; self->hscrollbar_thumb_ = nullptr; + self->help_overlay_ = nullptr; self->cursor_label_ = nullptr; self->bottom_labels_.fill(nullptr); self->bottom_indicators_.fill(nullptr); @@ -247,6 +285,7 @@ void UISTPage::event_cb(lv_event_t *event) scrollbar_thumb_ = nullptr; hscrollbar_track_ = nullptr; hscrollbar_thumb_ = nullptr; + help_overlay_ = nullptr; cursor_label_ = nullptr; bottom_labels_.fill(nullptr); bottom_indicators_.fill(nullptr); @@ -274,6 +313,24 @@ void UISTPage::event_cb(lv_event_t *event) shift_down_ = key->key_state != KBD_KEY_RELEASED; return; } + const bool help_key = key->key_code == KEY_HELP || + (key->key_code == KEY_H && (key->mods & KBD_MOD_FN) != 0); + if (help_visible()) { + if (key->key_state == KBD_KEY_PRESSED && (help_key || key->key_code == KEY_ESC)) + hide_help(); + else if (key->key_state) { + const bool up = key->key_code == KEY_UP || key->key_code == KEY_F || + key->semantic_key == KEY_UP; + const bool down = key->key_code == KEY_DOWN || key->key_code == KEY_X || + key->semantic_key == KEY_DOWN; + if (up || down) scroll_help(up ? 1 : -1); + } + return; + } + if (help_key && key->key_state == KBD_KEY_PRESSED) { + if (help_overlay_) show_help(); + return; + } if (key->key_state && key->key_code == KEY_F6) { switch_big_mode(!big_mode_); return; diff --git a/projects/APPLaunch/main/ui/page_app/ui_app_st_session.cpp b/projects/APPLaunch/main/ui/page_app/ui_app_st_session.cpp index 231a8f66..e3bb7210 100644 --- a/projects/APPLaunch/main/ui/page_app/ui_app_st_session.cpp +++ b/projects/APPLaunch/main/ui/page_app/ui_app_st_session.cpp @@ -76,6 +76,7 @@ void UISTPage::start_command(const std::string &command, void UISTPage::start_shell() { + hide_help(); start_command("bash", { "-c", "cd ~ && " diff --git a/projects/APPLaunch/main/ui/python_terminal_help.cpp b/projects/APPLaunch/main/ui/python_terminal_help.cpp new file mode 100644 index 00000000..a078dc59 --- /dev/null +++ b/projects/APPLaunch/main/ui/python_terminal_help.cpp @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "python_terminal_help.hpp" + +lv_obj_t *create_python_help(lv_obj_t *parent) +{ + if (!parent) return nullptr; + + lv_obj_t *overlay = lv_obj_create(parent); + if (!overlay) return nullptr; + lv_obj_remove_style_all(overlay); + lv_obj_set_size(overlay, 320, 150); + lv_obj_set_pos(overlay, 0, 0); + lv_obj_set_style_bg_color(overlay, lv_color_hex(0x10151C), 0); + lv_obj_set_style_bg_opa(overlay, LV_OPA_COVER, 0); + lv_obj_clear_flag( + overlay, static_cast(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE)); + + lv_obj_t *label = lv_label_create(overlay); + if (!label) { + lv_obj_delete(overlay); + return nullptr; + } + lv_obj_set_pos(label, 12, 9); + lv_obj_set_size(label, 296, 132); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_obj_set_style_text_font(label, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_color(label, lv_color_hex(0xF0F6FC), 0); + lv_obj_set_style_text_line_space(label, 2, 0); + lv_label_set_text(label, + "Python Help\n" + "An interactive Python REPL.\n\n" + "Fn + Z / C: move cursor left / right\n" + "Fn + F / X: previous / next command\n" + "Shift + Fn + L: page up\n" + "Shift + Fn + M: page down"); + return overlay; +} diff --git a/projects/APPLaunch/main/ui/python_terminal_help.hpp b/projects/APPLaunch/main/ui/python_terminal_help.hpp new file mode 100644 index 00000000..112f051b --- /dev/null +++ b/projects/APPLaunch/main/ui/python_terminal_help.hpp @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "lvgl/lvgl.h" + +lv_obj_t *create_python_help(lv_obj_t *parent); diff --git a/projects/APPLaunch/main/ui/settings/settings_about_help_model.cpp b/projects/APPLaunch/main/ui/settings/settings_about_help_model.cpp index b73e13e9..797eed39 100644 --- a/projects/APPLaunch/main/ui/settings/settings_about_help_model.cpp +++ b/projects/APPLaunch/main/ui/settings/settings_about_help_model.cpp @@ -59,6 +59,8 @@ Content credit() "Third-Party Licenses & Credits", split_lines(R"CP0_LICENSES(Our sincere thanks to all Kickstarter backers who helped make CardputerZero possible. +We also thank the early community contributors who helped shape CardputerZero: eggfly (Factory Firmware), zhuzhe1983 (Store), whywilson (RFID), JdaieLin (XiaoZhi), vicliu624 (LoFiBox-Zero), doodlewind (PocketJS), 7h30th3r0n3 (RaspyJack), bmorcelli (ESP Flasher), geo-tp (Retro-Zero), andreahaku IU4APC (Radio), n0xa (czconsole), marivaaldo (Flint), YUK_KND (ZeroTop), hishizuka (Bikecomputer), NDenchi (StackChan-Zero), GOROman. + CardputerZero is built with open-source software, fonts and media, along with attributed public-domain and Creative Commons resources. - Software: @@ -408,7 +410,12 @@ CC BY 3.0 Apollo 11 Guidance Computer excerpt: BURN_BABY_BURN Virtual AGC project and MIT Museum https://github.com/chrislgarry/Apollo-11/blob/master/Luminary099/BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc -Public Domain)CP0_LICENSES"), +Public Domain + +Lofoten Aurora lock screen +Johannes Groll +https://commons.wikimedia.org/wiki/File:Lofoten,_Norway_(Unsplash).jpg +CC0-1.0)CP0_LICENSES"), }; } diff --git a/projects/APPLaunch/main/ui/terminal_help_factory.hpp b/projects/APPLaunch/main/ui/terminal_help_factory.hpp new file mode 100644 index 00000000..9f20067d --- /dev/null +++ b/projects/APPLaunch/main/ui/terminal_help_factory.hpp @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "lvgl/lvgl.h" + +using TerminalHelpFactory = lv_obj_t *(*)(lv_obj_t *parent); diff --git a/projects/APPLaunch/main/ui/ui_screensaver.cpp b/projects/APPLaunch/main/ui/ui_screensaver.cpp index ab12d807..9b83a211 100644 --- a/projects/APPLaunch/main/ui/ui_screensaver.cpp +++ b/projects/APPLaunch/main/ui/ui_screensaver.cpp @@ -35,7 +35,7 @@ constexpr uint32_t kIdleCheckMs = 500; constexpr uint32_t kHoldPollMs = 100; constexpr uint32_t kExitAnimationMs = 350; /* Shown once the idle TAB hold matures past the model's hint delay. */ -constexpr const char *kHoldHintText = "Hold TAB for 5s to lock"; +constexpr const char *kHoldHintText = "Hold TAB for 3s to lock"; class ScreensaverImageCache { diff --git a/projects/APPLaunch/tests/low_battery_ui/test_low_battery_ui.cpp b/projects/APPLaunch/tests/low_battery_ui/test_low_battery_ui.cpp index fe7d0504..5a1ae39f 100644 --- a/projects/APPLaunch/tests/low_battery_ui/test_low_battery_ui.cpp +++ b/projects/APPLaunch/tests/low_battery_ui/test_low_battery_ui.cpp @@ -190,8 +190,8 @@ static void test_hold_gesture_enters_lock() advance(1); ::timer_cb(s_timer); assert(hold_hint_shows == 1); - assert(std::strcmp(hold_hint_text, "Hold TAB for 5s to lock") == 0); - advance(4499); + assert(std::strcmp(hold_hint_text, "Hold TAB for 3s to lock") == 0); + advance(2499); ::timer_cb(s_timer); assert(!ui_screensaver_is_active()); assert(backlight_raw == 128 && backlight_suspends == 0); diff --git a/projects/APPLaunch/tests/test_lockscreen_contract.py b/projects/APPLaunch/tests/test_lockscreen_contract.py index 75eb23ee..7a592142 100644 --- a/projects/APPLaunch/tests/test_lockscreen_contract.py +++ b/projects/APPLaunch/tests/test_lockscreen_contract.py @@ -70,7 +70,7 @@ # The gesture announces itself through the shared launcher toast before it locks, # and clears that announcement again as soon as the gesture ends. assert "launcher_toast().show_persistent(kHoldHintText)" in SOURCE -assert '"Hold TAB for 5s to lock"' in SOURCE +assert '"Hold TAB for 3s to lock"' in SOURCE assert "hold.hide_hint" in SOURCE assert "hold.fire" in SOURCE diff --git a/projects/APPLaunch/tests/test_screensaver_model.cpp b/projects/APPLaunch/tests/test_screensaver_model.cpp index fd3cf248..cf5fb0f3 100644 --- a/projects/APPLaunch/tests/test_screensaver_model.cpp +++ b/projects/APPLaunch/tests/test_screensaver_model.cpp @@ -82,7 +82,7 @@ int main() // ---- Long-press gesture that requests the lock ---- ScreensaverModel hold; - assert(ScreensaverModel::screen_off_hold_ms() == 5000); + assert(ScreensaverModel::screen_off_hold_ms() == 3000); assert(ScreensaverModel::hold_hint_ms() == 500); hold.reset(1000); assert(!hold.hold_pending()); @@ -100,12 +100,12 @@ int main() assert(!hold.poll_hold(4000).show_hint); // Auto-repeat must not restart the window, or a held key never matures. assert(!hold.observe_hold_key(KEY_TAB, false, 4000).hide_hint); - assert(!hold.poll_hold(6999).fire); - decision = hold.poll_hold(7000); + assert(!hold.poll_hold(4999).fire); + decision = hold.poll_hold(5000); assert(decision.fire && !decision.show_hint); // The threshold reports once per hold, and entering the lock clears it. assert(!hold.poll_hold(9000).fire); - hold.activate(240, 135, 7000); + hold.activate(240, 135, 5000); assert(hold.active() && !hold.hold_pending() && !hold.hold_hint_visible()); // A short tap never requests anything and never shows the hint.