From ababcf70706cee25363f51a2eb4213e3c1acea00 Mon Sep 17 00:00:00 2001 From: Crazypedia Date: Sun, 5 Jul 2026 16:46:20 -0400 Subject: [PATCH 1/6] feat(display): confirm shutdown visually with a wait-for-flush hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add DisplayDevice::waitForFlushComplete() (default no-op) so callers can block until a frame handed to the display has physically finished drawing, and use it in the Launcher's power-off handler to show a plain "Tactility OS / Powered off" screen before cutting power. Displays that draw synchronously within their flush callback are unaffected. Displays with an asynchronous refresh pipeline (e.g. e-paper, where a full refresh can take seconds) should override this so a caller doing something irreversible — like cutting power — can be sure the screen physically shows what was just drawn, rather than freezing mid-refresh. On e-paper this also doubles as a real signal that the device shut down cleanly rather than crashed or hung, since the panel keeps showing it after power cuts. Co-Authored-By: Claude Fable 5 --- .../Tactility/hal/display/DisplayDevice.h | 8 ++++ Tactility/Source/app/launcher/Launcher.cpp | 39 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Tactility/Include/Tactility/hal/display/DisplayDevice.h b/Tactility/Include/Tactility/hal/display/DisplayDevice.h index 64c243c79..7918bfb7e 100644 --- a/Tactility/Include/Tactility/hal/display/DisplayDevice.h +++ b/Tactility/Include/Tactility/hal/display/DisplayDevice.h @@ -29,6 +29,14 @@ class DisplayDevice : public Device { /** For e-paper screens */ virtual void requestFullRefresh() {} + /** Blocks until any frame already handed to this display has physically + * finished drawing. Displays that draw synchronously within their flush + * callback can rely on the default no-op; displays with an asynchronous + * refresh pipeline (e.g. e-paper, where a full refresh can take seconds) + * should override this so callers can safely do something irreversible + * (like cutting power) right after a screen update. */ + virtual void waitForFlushComplete() {} + /** Could return nullptr if not started */ virtual std::shared_ptr getTouchDevice() = 0; diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index a5ee66906..bf758f056 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -84,11 +85,45 @@ class LauncherApp final : public App { start(appId); } + /** Replaces the screen with a plain "powered off" message, forces it to draw + * synchronously, and waits for the display to confirm the draw physically + * finished. On e-paper this is what's left showing once power cuts, so it + * doubles as visual confirmation that the device shut down cleanly rather + * than crashed or hung. */ + static void showPoweredOffScreenAndWait(hal::display::DisplayDevice* display) { + auto* screen = lv_obj_create(nullptr); + lv_obj_set_style_bg_color(screen, lv_color_white(), 0); + lv_obj_set_flex_flow(screen, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(screen, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + auto* title = lv_label_create(screen); + lv_label_set_text(title, "Tactility OS"); + lv_obj_set_style_text_font(title, lvgl_get_text_font(FONT_SIZE_LARGE), 0); + + auto* subtitle = lv_label_create(screen); + lv_label_set_text(subtitle, "Powered off"); + + lv_screen_load(screen); + + if (display != nullptr) { + auto* lvgl_display = display->getLvglDisplay(); + if (lvgl_display != nullptr) { + lv_refr_now(lvgl_display); + } + display->waitForFlushComplete(); + } + } + static void onPowerOffPressed(lv_event_t* e) { auto power = hal::findFirstDevice(hal::Device::Type::Power); - if (power != nullptr && power->supportsPowerOff()) { - power->powerOff(); + if (power == nullptr || !power->supportsPowerOff()) { + return; } + + auto display = hal::findFirstDevice(hal::Device::Type::Display); + showPoweredOffScreenAndWait(display.get()); + + power->powerOff(); } // The screen object outlives the launcher's views (it's recreated by GuiService::redraw() From 291019ebbaa32eea572a17fc92bbc501762a0f28 Mon Sep 17 00:00:00 2001 From: Crazypedia Date: Sun, 5 Jul 2026 17:12:39 -0400 Subject: [PATCH 2/6] feat(launcher): confirm before powering off The power-off button was one accidental tap away from cutting power with no way back. Show an alert dialog ("Power off?" / Cancel) and only proceed to the shutdown screen + PowerDevice::powerOff() once the user picks "Power off". Co-Authored-By: Claude Fable 5 --- Tactility/Source/app/launcher/Launcher.cpp | 28 ++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index bf758f056..cd3aaa0e0 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,8 @@ static int32_t computeButtonMargin(int32_t available_span, int32_t total_button_ class LauncherApp final : public App { + LaunchId powerOffConfirmLaunchId = 0; + static lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) { const auto button_size = lvgl_get_launcher_icon_font_height(); const auto button_padding = getButtonPadding(uiDensity, button_size); @@ -114,7 +117,7 @@ class LauncherApp final : public App { } } - static void onPowerOffPressed(lv_event_t* e) { + static void performPowerOff() { auto power = hal::findFirstDevice(hal::Device::Type::Power); if (power == nullptr || !power->supportsPowerOff()) { return; @@ -126,6 +129,17 @@ class LauncherApp final : public App { power->powerOff(); } + static void onPowerOffPressed(lv_event_t* e) { + auto* self = static_cast(lv_event_get_user_data(e)); + auto power = hal::findFirstDevice(hal::Device::Type::Power); + if (power == nullptr || !power->supportsPowerOff()) { + return; + } + + auto choices = std::vector { "Power off", "Cancel" }; + self->powerOffConfirmLaunchId = alertdialog::start("Power off?", "Are you sure you want to power off?", choices); + } + // The screen object outlives the launcher's views (it's recreated by GuiService::redraw() // via lv_obj_clean() on every app switch), so the LV_EVENT_SIZE_CHANGED callback registered // on it must be removed once buttons_wrapper is destroyed, to avoid a dangling user-data @@ -197,6 +211,16 @@ class LauncherApp final : public App { } } + void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr resultData) override { + if (launchId == powerOffConfirmLaunchId && + result == Result::Ok && + resultData != nullptr && + alertdialog::getResultIndex(*resultData) == 0 + ) { + performPowerOff(); + } + } + void onShow(AppContext& app, lv_obj_t* parent) override { auto* buttons_wrapper = lv_obj_create(parent); @@ -241,7 +265,7 @@ class LauncherApp final : public App { auto* power_button = lv_button_create(parent); lv_obj_set_style_pad_all(power_button, 8, 0); lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10); - lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, nullptr); + lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, this); lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT); lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN); From 5e2062ad881f472c415e84411d2cf816144e57aa Mon Sep 17 00:00:00 2001 From: Crazypedia Date: Sun, 5 Jul 2026 21:57:41 -0400 Subject: [PATCH 3/6] fix(launcher): acquire the LVGL lock before drawing the shutdown screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit showPoweredOffScreenAndWait() is called from App::onResult, which runs on the loader dispatcher thread — not the LVGL thread. Every other piece of code in this project that touches LVGL objects from a non-LVGL context (e.g. DisplayIdleService's screensaver overlay) acquires the LVGL lock first; this call site didn't, so it was racing the LVGL render task. Confirmed on hardware: without the lock, the shutdown screen sometimes failed to render correctly before power cut. Co-Authored-By: Claude Fable 5 --- Tactility/Source/app/launcher/Launcher.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index cd3aaa0e0..edea02221 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -92,8 +93,16 @@ class LauncherApp final : public App { * synchronously, and waits for the display to confirm the draw physically * finished. On e-paper this is what's left showing once power cuts, so it * doubles as visual confirmation that the device shut down cleanly rather - * than crashed or hung. */ + * than crashed or hung. + * + * Called from App::onResult, which runs on the loader dispatcher thread, + * not the LVGL thread — every LVGL call here must happen under the LVGL + * lock, same as DisplayIdleService's screensaver overlay. */ static void showPoweredOffScreenAndWait(hal::display::DisplayDevice* display) { + if (!lvgl::lock(lvgl::defaultLockTime)) { + return; + } + auto* screen = lv_obj_create(nullptr); lv_obj_set_style_bg_color(screen, lv_color_white(), 0); lv_obj_set_flex_flow(screen, LV_FLEX_FLOW_COLUMN); @@ -115,6 +124,8 @@ class LauncherApp final : public App { } display->waitForFlushComplete(); } + + lvgl::unlock(); } static void performPowerOff() { From badf62c8fecb55040e0cfb95fc08d4b27b63e7e0 Mon Sep 17 00:00:00 2001 From: Crazypedia Date: Mon, 6 Jul 2026 09:34:31 -0400 Subject: [PATCH 4/6] feat(app): move power-off confirmation into its own fullscreen app Launcher was doing too much (Ken review, PR #555): the confirmation dialog, the 'powered off' shutdown screen, and the power-off call all lived in LauncherApp. Move that into a standalone PowerOff app (HideStatusBar + Hidden) with its own Yes/No buttons; Launcher now just starts it. As a side effect, the LVGL-lock workaround from the previous commit is no longer needed: the power-off flow now runs from an LVGL button click callback (already on the LVGL thread) instead of from App::onResult (loader dispatcher thread), so it can call LVGL functions directly. --- Tactility/Source/Tactility.cpp | 2 + Tactility/Source/app/launcher/Launcher.cpp | 79 +--------------- Tactility/Source/app/poweroff/PowerOff.cpp | 100 +++++++++++++++++++++ 3 files changed, 105 insertions(+), 76 deletions(-) create mode 100644 Tactility/Source/app/poweroff/PowerOff.cpp diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 8fbb966c5..b79f49887 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -103,6 +103,7 @@ namespace app { namespace localesettings { extern const AppManifest manifest; } namespace notes { extern const AppManifest manifest; } namespace power { extern const AppManifest manifest; } + namespace poweroff { extern const AppManifest manifest; } namespace selectiondialog { extern const AppManifest manifest; } namespace settings { extern const AppManifest manifest; } namespace setup { extern const AppManifest manifest; } @@ -157,6 +158,7 @@ static void registerInternalApps() { addAppManifest(app::launcher::manifest); addAppManifest(app::localesettings::manifest); addAppManifest(app::notes::manifest); + addAppManifest(app::poweroff::manifest); addAppManifest(app::settings::manifest); addAppManifest(app::selectiondialog::manifest); addAppManifest(app::setup::manifest); diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index edea02221..61ddfc562 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -3,11 +3,8 @@ #include #include #include -#include #include -#include #include -#include #include #include @@ -38,8 +35,6 @@ static int32_t computeButtonMargin(int32_t available_span, int32_t total_button_ class LauncherApp final : public App { - LaunchId powerOffConfirmLaunchId = 0; - static lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) { const auto button_size = lvgl_get_launcher_icon_font_height(); const auto button_padding = getButtonPadding(uiDensity, button_size); @@ -89,66 +84,8 @@ class LauncherApp final : public App { start(appId); } - /** Replaces the screen with a plain "powered off" message, forces it to draw - * synchronously, and waits for the display to confirm the draw physically - * finished. On e-paper this is what's left showing once power cuts, so it - * doubles as visual confirmation that the device shut down cleanly rather - * than crashed or hung. - * - * Called from App::onResult, which runs on the loader dispatcher thread, - * not the LVGL thread — every LVGL call here must happen under the LVGL - * lock, same as DisplayIdleService's screensaver overlay. */ - static void showPoweredOffScreenAndWait(hal::display::DisplayDevice* display) { - if (!lvgl::lock(lvgl::defaultLockTime)) { - return; - } - - auto* screen = lv_obj_create(nullptr); - lv_obj_set_style_bg_color(screen, lv_color_white(), 0); - lv_obj_set_flex_flow(screen, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(screen, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - - auto* title = lv_label_create(screen); - lv_label_set_text(title, "Tactility OS"); - lv_obj_set_style_text_font(title, lvgl_get_text_font(FONT_SIZE_LARGE), 0); - - auto* subtitle = lv_label_create(screen); - lv_label_set_text(subtitle, "Powered off"); - - lv_screen_load(screen); - - if (display != nullptr) { - auto* lvgl_display = display->getLvglDisplay(); - if (lvgl_display != nullptr) { - lv_refr_now(lvgl_display); - } - display->waitForFlushComplete(); - } - - lvgl::unlock(); - } - - static void performPowerOff() { - auto power = hal::findFirstDevice(hal::Device::Type::Power); - if (power == nullptr || !power->supportsPowerOff()) { - return; - } - - auto display = hal::findFirstDevice(hal::Device::Type::Display); - showPoweredOffScreenAndWait(display.get()); - - power->powerOff(); - } - - static void onPowerOffPressed(lv_event_t* e) { - auto* self = static_cast(lv_event_get_user_data(e)); - auto power = hal::findFirstDevice(hal::Device::Type::Power); - if (power == nullptr || !power->supportsPowerOff()) { - return; - } - - auto choices = std::vector { "Power off", "Cancel" }; - self->powerOffConfirmLaunchId = alertdialog::start("Power off?", "Are you sure you want to power off?", choices); + static void onPowerOffPressed(lv_event_t* /*e*/) { + start("PowerOff"); } // The screen object outlives the launcher's views (it's recreated by GuiService::redraw() @@ -222,16 +159,6 @@ class LauncherApp final : public App { } } - void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr resultData) override { - if (launchId == powerOffConfirmLaunchId && - result == Result::Ok && - resultData != nullptr && - alertdialog::getResultIndex(*resultData) == 0 - ) { - performPowerOff(); - } - } - void onShow(AppContext& app, lv_obj_t* parent) override { auto* buttons_wrapper = lv_obj_create(parent); @@ -276,7 +203,7 @@ class LauncherApp final : public App { auto* power_button = lv_button_create(parent); lv_obj_set_style_pad_all(power_button, 8, 0); lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10); - lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, this); + lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, nullptr); lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT); lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN); diff --git a/Tactility/Source/app/poweroff/PowerOff.cpp b/Tactility/Source/app/poweroff/PowerOff.cpp new file mode 100644 index 000000000..d60ce6cf0 --- /dev/null +++ b/Tactility/Source/app/poweroff/PowerOff.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace tt::app::poweroff { + +extern const AppManifest manifest; + +class PowerOffApp final : public App { + + /** Replaces the screen with a plain "powered off" message, forces it to draw + * synchronously, and waits for the display to confirm the draw physically + * finished. On e-paper this is what's left showing once power cuts, so it + * doubles as visual confirmation that the device shut down cleanly rather + * than crashed or hung. Called from an LVGL button click callback, so it's + * already running on the LVGL thread. */ + static void showPoweredOffScreenAndWait(hal::display::DisplayDevice* display) { + auto* screen = lv_obj_create(nullptr); + lv_obj_set_style_bg_color(screen, lv_color_white(), 0); + lv_obj_set_flex_flow(screen, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(screen, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + auto* title = lv_label_create(screen); + lv_label_set_text(title, "Tactility OS"); + lv_obj_set_style_text_font(title, lvgl_get_text_font(FONT_SIZE_LARGE), 0); + + auto* subtitle = lv_label_create(screen); + lv_label_set_text(subtitle, "Powered off"); + + lv_screen_load(screen); + + if (display != nullptr) { + auto* lvgl_display = display->getLvglDisplay(); + if (lvgl_display != nullptr) { + lv_refr_now(lvgl_display); + } + display->waitForFlushComplete(); + } + } + + static void onYesPressed(lv_event_t* /*event*/) { + auto power = hal::findFirstDevice(hal::Device::Type::Power); + if (power == nullptr || !power->supportsPowerOff()) { + return; + } + + auto display = hal::findFirstDevice(hal::Device::Type::Display); + showPoweredOffScreenAndWait(display.get()); + + power->powerOff(); + } + + static void onNoPressed(lv_event_t* /*event*/) { + stop(manifest.appId); + } + +public: + + void onShow(AppContext& /*app*/, lv_obj_t* parent) override { + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(parent, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + auto* label = lv_label_create(parent); + lv_label_set_text(label, "Power off?"); + lv_obj_set_style_text_font(label, lvgl_get_text_font(FONT_SIZE_LARGE), 0); + + auto* button_wrapper = lv_obj_create(parent); + lv_obj_set_flex_flow(button_wrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_size(button_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(button_wrapper, 0, 0); + lv_obj_set_style_border_width(button_wrapper, 0, 0); + lv_obj_set_flex_align(button_wrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + auto* yes_button = lv_button_create(button_wrapper); + auto* yes_label = lv_label_create(yes_button); + lv_label_set_text(yes_label, "Yes"); + lv_obj_add_event_cb(yes_button, onYesPressed, LV_EVENT_SHORT_CLICKED, nullptr); + + auto* no_button = lv_button_create(button_wrapper); + auto* no_label = lv_label_create(no_button); + lv_label_set_text(no_label, "No"); + lv_obj_add_event_cb(no_button, onNoPressed, LV_EVENT_SHORT_CLICKED, nullptr); + } +}; + +extern const AppManifest manifest = { + .appId = "PowerOff", + .appName = "Power Off", + .appCategory = Category::System, + .appFlags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden, + .createApp = create +}; + +} // namespace From 0d54177424df479c74546bed3ef5c1d7dfc946a0 Mon Sep 17 00:00:00 2001 From: Crazypedia Date: Mon, 6 Jul 2026 10:46:30 -0400 Subject: [PATCH 5/6] fix(app): set dark text color on the powered-off screen CodeRabbit flagged this on the original PR: the screen forces a white background, but the title/subtitle labels had no explicit text color, so the default theme's (potentially light) text color could render unreadable on it. --- Tactility/Source/app/poweroff/PowerOff.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tactility/Source/app/poweroff/PowerOff.cpp b/Tactility/Source/app/poweroff/PowerOff.cpp index d60ce6cf0..4d56dd90d 100644 --- a/Tactility/Source/app/poweroff/PowerOff.cpp +++ b/Tactility/Source/app/poweroff/PowerOff.cpp @@ -29,9 +29,11 @@ class PowerOffApp final : public App { auto* title = lv_label_create(screen); lv_label_set_text(title, "Tactility OS"); lv_obj_set_style_text_font(title, lvgl_get_text_font(FONT_SIZE_LARGE), 0); + lv_obj_set_style_text_color(title, lv_color_black(), 0); auto* subtitle = lv_label_create(screen); lv_label_set_text(subtitle, "Powered off"); + lv_obj_set_style_text_color(subtitle, lv_color_black(), 0); lv_screen_load(screen); From 1b1f64c3c4a6cd93ff2c6db93a51ebedfb52d122 Mon Sep 17 00:00:00 2001 From: Crazypedia Date: Mon, 6 Jul 2026 11:55:29 -0400 Subject: [PATCH 6/6] refactor(app): move PowerOff out of Launcher into Settings Per further discussion of Ken's PR feedback: the power button itself shouldn't live in the Launcher UI either, not just the power-off logic. Drop the power icon from Launcher entirely and list PowerOff as a regular Settings entry (Category::Settings, own icon), the same way the existing Power (battery info) app is reached. --- Tactility/Source/app/launcher/Launcher.cpp | 31 ---------------------- Tactility/Source/app/poweroff/PowerOff.cpp | 6 +++-- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index 61ddfc562..729376cd7 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -66,28 +65,11 @@ class LauncherApp final : public App { return apps_button; } - static bool shouldShowPowerButton() { - bool show_power_button = false; - hal::findDevices(hal::Device::Type::Power, [&show_power_button](const auto& device) { - if (device->supportsPowerOff()) { - show_power_button = true; - return false; // stop iterating - } else { - return true; // continue iterating - } - }); - return show_power_button; - } - static void onAppPressed(lv_event_t* e) { auto* appId = static_cast(lv_event_get_user_data(e)); start(appId); } - static void onPowerOffPressed(lv_event_t* /*e*/) { - start("PowerOff"); - } - // The screen object outlives the launcher's views (it's recreated by GuiService::redraw() // via lv_obj_clean() on every app switch), so the LV_EVENT_SIZE_CHANGED callback registered // on it must be removed once buttons_wrapper is destroyed, to avoid a dangling user-data @@ -198,19 +180,6 @@ class LauncherApp final : public App { // handler is attached there, with buttons_wrapper passed through as user data. lv_obj_add_event_cb(lv_obj_get_screen(parent), onButtonsWrapperResized, LV_EVENT_SIZE_CHANGED, buttons_wrapper); lv_obj_add_event_cb(buttons_wrapper, onButtonsWrapperDeleted, LV_EVENT_DELETE, nullptr); - - if (shouldShowPowerButton()) { - auto* power_button = lv_button_create(parent); - lv_obj_set_style_pad_all(power_button, 8, 0); - lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10); - lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, nullptr); - lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN); - - auto* power_label = lv_label_create(power_button); - lv_label_set_text(power_label, LV_SYMBOL_POWER); - lv_obj_set_style_text_color(power_label, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); - } } }; diff --git a/Tactility/Source/app/poweroff/PowerOff.cpp b/Tactility/Source/app/poweroff/PowerOff.cpp index 4d56dd90d..04e8c3c4d 100644 --- a/Tactility/Source/app/poweroff/PowerOff.cpp +++ b/Tactility/Source/app/poweroff/PowerOff.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace tt::app::poweroff { @@ -94,8 +95,9 @@ class PowerOffApp final : public App { extern const AppManifest manifest = { .appId = "PowerOff", .appName = "Power Off", - .appCategory = Category::System, - .appFlags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden, + .appIcon = LVGL_ICON_SHARED_POWER_SETTINGS_NEW, + .appCategory = Category::Settings, + .appFlags = AppManifest::Flags::HideStatusBar, .createApp = create };