Skip to content

Show a confirmed shutdown screen before cutting power#555

Open
Crazypedia wants to merge 6 commits into
TactilityProject:mainfrom
Crazypedia:display-wait-for-flush-on-poweroff
Open

Show a confirmed shutdown screen before cutting power#555
Crazypedia wants to merge 6 commits into
TactilityProject:mainfrom
Crazypedia:display-wait-for-flush-on-poweroff

Conversation

@Crazypedia

@Crazypedia Crazypedia commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Small, standalone core change — no board-specific code, doesn't depend on any of my other open PRs.

Motivation

Today, Launcher.cpp's power-off button calls PowerDevice::powerOff() directly, with no visual feedback. On a normal display that's fine (the screen goes dark with the power rail). On an e-paper display, though, the panel keeps showing whatever was drawn last — so if power cuts mid-refresh (or while still showing the app the user was in), there's no way to tell "the device shut down cleanly" from "the device crashed or hung."

I hit this while building an e-paper board (T-Deck Max, #539/#552/#553): those PRs' GDEQ031T10 driver refreshes asynchronously on a background task (needed to keep input responsive during multi-second e-paper refreshes), so simply drawing a message isn't enough — the caller needs to know the panel has physically finished drawing before the power rail dies, or the message itself could get cut off mid-refresh.

What this adds

  • DisplayDevice::waitForFlushComplete() — a new virtual, default no-op. Displays that draw synchronously in their flush callback (i.e. every current upstream display) are completely unaffected. Displays with an async refresh pipeline can override it to block until any pending frame has physically finished drawing.
  • Launcher.cpp's power-off handler now shows a plain "Tactility OS / Powered off" screen, forces a synchronous LVGL redraw (lv_refr_now), calls waitForFlushComplete(), and only then calls powerOff().

No existing driver needs changes — the new virtual defaults to a no-op everywhere, so this is a pure API addition plus a small UX improvement for every board with a power-off-capable device.

Verification

  • Simulator build clean.
  • Verified end-to-end on real T-Deck Max hardware (the actual motivating case, using my as-yet-unmerged GDEQ031T10 driver + a waitForFlushComplete() override): pressed the power-off button, the "Tactility OS / Powered off" screen rendered fully before the board's power rail cut, and the panel kept showing it after power-off (no garbled/partial refresh). Device powered back on cleanly when USB was reconnected.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new “Power Off” app that prompts with a “Power off?” confirmation dialog (“Yes” / “No”).
    • Selecting “Yes” transitions to a “Powered off” screen before powering down.
  • Bug Fixes
    • Power-off now waits for the display to finish drawing the final update before shutting down, improving reliability on slower/asynchronous display hardware.
  • Changes
    • Removed the dedicated power-off button from the launcher; power-off is now handled via the new app.

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 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf6caa56-a647-4be5-9c38-c28fc002cc0b

📥 Commits

Reviewing files that changed from the base of the PR and between 0d54177 and 1b1f64c.

📒 Files selected for processing (2)
  • Tactility/Source/app/launcher/Launcher.cpp
  • Tactility/Source/app/poweroff/PowerOff.cpp
💤 Files with no reviewable changes (1)
  • Tactility/Source/app/launcher/Launcher.cpp

📝 Walkthrough

Walkthrough

This change adds waitForFlushComplete() to the display device interface, registers a new PowerOff internal app, and removes the launcher’s direct power-off button path. The new app shows a power-off confirmation screen, can render a final “Powered off” screen, waits for display flush completion, and then calls powerOff().

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: showing a confirmed shutdown screen before power is cut.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 309989ed-f8ac-45e2-9172-2e1fe3c178b0

📥 Commits

Reviewing files that changed from the base of the PR and between cca7224 and ababcf7.

📒 Files selected for processing (2)
  • Tactility/Include/Tactility/hal/display/DisplayDevice.h
  • Tactility/Source/app/launcher/Launcher.cpp

Comment on lines +95 to +104
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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Launcher.cpp around target lines ---'
sed -n '1,180p' Tactility/Source/app/launcher/Launcher.cpp

echo
echo '--- Search for text color styling in launcher/app files ---'
rg -n "lv_obj_set_style_text_color|lv_label_set_text_color|lv_obj_set_style_bg_color|Powered off|Tactility OS" Tactility/Source -S

Repository: TactilityProject/Tactility

Length of output: 14329


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Search for theme/default style setup ---'
rg -n "lv_theme|default_init|text color|text_color|set_style_text_color|LV_COLOR" Tactility/Source -S

echo
echo '--- Inspect style/theme related files ---'
for f in Tactility/Source/lvgl/Style.cpp Tactility/Source/service/gui/GuiService.cpp Tactility/Source/app/launcher/Launcher.cpp; do
  echo "### $f"
  wc -l "$f"
done

echo
echo '--- Style.cpp ---'
sed -n '1,220p' Tactility/Source/lvgl/Style.cpp

echo
echo '--- GuiService.cpp around initialization ---'
sed -n '1,200p' Tactility/Source/service/gui/GuiService.cpp

Repository: TactilityProject/Tactility

Length of output: 14053


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Search for theme initialization / mode selection ---'
rg -n "lv_theme_default_init|lv_theme_set|lv_display_set_theme|LV_THEME|lv_theme_get_color" Tactility/Source -S

echo
echo '--- Search for label text color inheritance utilities or defaults ---'
rg -n "lv_label_create\\(|lv_obj_set_style_text_color\\(.*label|lv_obj_set_style_text_color\\(.*screen" Tactility/Source/app Tactility/Source/lvgl -S

echo
echo '--- Inspect launcher shutdown context lines ---'
sed -n '88,110p' Tactility/Source/app/launcher/Launcher.cpp

Repository: TactilityProject/Tactility

Length of output: 22946


🌐 Web query:

LVGL label default text color theme inherited from parent background white default theme text color

💡 Result:

In LVGL, text color properties (such as text_color) are inheritable from parent widgets [1][2][3]. If a label does not have a text color explicitly set, it will search up the object hierarchy to find a parent that has a text color style defined [1][2]. Key aspects of this behavior include: 1. Inheritance Mechanism: Inheritance applies only if the property is not explicitly set in the label's own styles (even in the default state) [1][2][3]. If the label has no local style for text color, it will continue to look at its parents until a value is found [1][2]. 2. Theme Influence: LVGL themes apply default styles to widgets upon creation [1][4]. These default styles often include predefined text colors [5][6]. If you want a label to inherit a specific color from a parent (e.g., a container with a white background), ensure the label itself does not have a local text color style, and that the parent or a higher ancestor has the desired text color set [7]. 3. Precedence: Local styles set on the label will always override inherited values [1][2]. If your labels are defaulting to a color that conflicts with your white background, you should either update the parent's style or explicitly add a style to the label that defines the correct text color [8][9]. 4. Troubleshooting: If a label is not inheriting correctly, it is often because an intermediate parent has a style set that defines a text color, preventing the search from reaching the grandparent or intended container [7]. In such cases, removing the style from the intermediate parent or explicitly applying the desired color to the label is necessary [7][10]. For consistent application of colors, you can create a custom style and add it to your labels, or update the active theme's styles [8][6]. If you need to force a color change when a theme changes, ensure your style is linked to the primary or secondary color palette provided by the theme [8].

Citations:


🌐 Web query:

LVGL default label text color theme defaults light text on white background label inherits style documentation

💡 Result:

In LVGL, the default appearance of a label is determined by the active theme, which is initialized automatically if the default theme is enabled in your configuration (LV_USE_THEME_DEFAULT) [1]. When using this default theme, text color defaults are predefined based on whether the theme is set to light or dark mode [2][1]. Regarding your specific query on text inheritance and styling: 1. Style Inheritance: LVGL supports style inheritance for text-related properties (such as text color and font) [3][4]. If a property is not explicitly set for a specific widget (including a label), LVGL will search up the object hierarchy (parent to child) until it finds a widget that specifies a value for that property [3][5]. 2. Theme Defaults: When the default theme is active, it applies styles to various widgets. If you require a specific behavior—such as white text on a white background—it is generally recommended to explicitly set the style for that label or its parent container, as the default theme aims for contrast rather than monochromatic rendering [1]. 3. How to Override: - To change text color locally: You can use lv_obj_set_style_text_color(obj, color, selector) to apply a specific color to a label [1]. - To change inheritance: By setting a style on a parent container, all child labels that do not have their own explicit text color will inherit the parent's color [3][4]. For detailed implementation, consult the LVGL Styles Overview documentation, which outlines how properties cascade, how inheritance functions, and how to use local styles to override theme defaults [3][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Search for LVGL theme configuration / dark mode settings ---'
rg -n "LV_USE_THEME_DEFAULT|LV_THEME_DEFAULT|DARK|LIGHT|theme.*dark|theme.*light|lv_theme_default_init|lv_display_set_theme|lv_theme_set" -S .

echo
echo '--- Search config-like files for LVGL theme flags ---'
fd -t f 'lv_conf.h|sdkconfig|config.*|*.conf|*.ini|*.json|*.yaml|*.yml' . | sed -n '1,200p'

Repository: TactilityProject/Tactility

Length of output: 7299


Set the shutdown labels to a dark color. The default dark theme can leave these labels light on the white powered-off screen, making them hard to read.

Comment on lines +108 to +114
if (display != nullptr) {
auto* lvgl_display = display->getLvglDisplay();
if (lvgl_display != nullptr) {
lv_refr_now(lvgl_display);
}
display->waitForFlushComplete();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and nearby symbols first.
ast-grep outline Tactility/Source/app/launcher/Launcher.cpp --view expanded || true

# Read the relevant section with line numbers.
sed -n '1,220p' Tactility/Source/app/launcher/Launcher.cpp | cat -n

# Find the display-device API and the launcher call sites.
rg -n "findFirstDevice<DisplayDevice>|getLvglDisplay|waitForFlushComplete|lv_refr_now|lv_obj_get_display|lv_event_get_target_obj|showPoweredOffScreenAndWait" Tactility/Source -S

# Inspect the display device implementation and LVGL ownership helpers.
fd -a "DisplayDevice" Tactility/Source

Repository: TactilityProject/Tactility

Length of output: 15898


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the display-device interface and implementation.
fd -a "DisplayDevice" Tactility/Source | sed -n '1,20p'
ast-grep outline Tactility/Source/hal/display/DisplayDevice.h --view expanded || true
ast-grep outline Tactility/Source/hal/display/DisplayDevice.cpp --view expanded || true

# Inspect the relevant parts of the files if they exist.
for f in \
  Tactility/Source/hal/display/DisplayDevice.h \
  Tactility/Source/hal/display/DisplayDevice.cpp \
  Tactility/Source/lvgl/Lvgl.cpp
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
  fi
done

# Show the display-device declarations/definitions and LVGL startup wiring.
for f in \
  Tactility/Source/hal/display/DisplayDevice.h \
  Tactility/Source/hal/display/DisplayDevice.cpp \
  Tactility/Source/lvgl/Lvgl.cpp
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    sed -n '1,260p' "$f" | cat -n
  fi
done

Repository: TactilityProject/Tactility

Length of output: 8428


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the display device interface and the device registry semantics.
rg -n "class DisplayDevice|struct DisplayDevice|findFirstDevice<|findDevices<|supportsLvgl|getLvglDisplay|waitForFlushComplete|startLvgl\\(" Tactility/Source -S

# Find where the launcher screen is created/attached to a display.
rg -n "Launcher|showPoweredOffScreenAndWait|onPowerOffPressed|lv_screen_load|lv_obj_get_display\\(parent\\)|lv_obj_get_display\\(.*launcher|power off" Tactility/Source/app Tactility/Source/lvgl -S

# Inspect the device registry implementation if present.
fd -a "Device" Tactility/Source

Repository: TactilityProject/Tactility

Length of output: 7157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the launcher's power-button setup and the surrounding display selection logic.
sed -n '200,280p' Tactility/Source/app/launcher/Launcher.cpp | cat -n

# Check whether the launcher or nearby code explicitly selects a display from the LVGL object tree.
rg -n "lv_obj_get_display\\(|lv_screen_load\\(|power_button|onPowerOffPressed|onShow\\(" Tactility/Source/app/launcher/Launcher.cpp Tactility/Source/app/boot/Boot.cpp Tactility/Source/app/display/Display.cpp -S

# Read the boot/display app code that picks the display device.
sed -n '1,120p' Tactility/Source/app/display/Display.cpp | cat -n
sed -n '1,260p' Tactility/Source/app/boot/Boot.cpp | cat -n

Repository: TactilityProject/Tactility

Length of output: 23251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the device registry and any helpers that define first-device selection order.
sed -n '1,220p' Tactility/Source/hal/Hal.cpp | cat -n

# Inspect the app/display service that creates visible screens, if present.
rg -n "lv_screen_load|lv_obj_get_screen|lv_display_get_default|findFirstDevice<hal::display::DisplayDevice>|redraw|AppContext|create<.*App>|startService\\(\"Gui\"\\)" Tactility/Source/service Tactility/Source/app Tactility/Source/hal -S

# Read any compact file that appears to manage app-to-screen attachment.
fd -a "Gui" Tactility/Source/service Tactility/Source/app Tactility/Source/hal

Repository: TactilityProject/Tactility

Length of output: 22189


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect GUI service redraw/app instantiation to see whether apps are per-display or shared.
sed -n '1,360p' Tactility/Source/service/gui/GuiService.cpp | cat -n

# Inspect AppContext / LoaderService ownership if needed.
sed -n '1,360p' Tactility/Source/service/loader/Loader.cpp | cat -n
sed -n '1,240p' Tactility/Source/app/App.cpp | cat -n

Repository: TactilityProject/Tactility

Length of output: 26223


Refresh the launcher’s own display before power-off. findFirstDevice<DisplayDevice>() can return a different panel than the one hosting the power button, so lv_refr_now() and waitForFlushComplete() may run on the wrong display or be skipped. Use the event target’s lv_display_t and match the corresponding DisplayDevice before cutting power.

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 <noreply@anthropic.com>
@KenVanHoeylandt

Copy link
Copy Markdown
Contributor

It's a great idea for a feature, but the way it's implemented doesn't work well as it puts unnecessary functionality in the Launcher which should only be responsible about basic launcher tasks and not power off logic:

I'd rather see this as a separate application. It can even be a full screen app (set flag in the app manifest to hide the system top bar) and have a text with "Power off?" and buttons "Yes" and "No" underneath.

Once "Yes" is pressed, e-paper behaviour can be put in place if the display supports it.

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 <noreply@anthropic.com>
@Crazypedia

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up fix: while testing the confirmation dialog on hardware, I found `showPoweredOffScreenAndWait()` (added in this PR) was calling LVGL functions without acquiring the LVGL lock. It's invoked from `App::onResult`, which runs on the loader dispatcher thread rather than the LVGL thread, so it was racing the LVGL render task — every other place in the codebase that touches LVGL from a non-LVGL context (e.g. `DisplayIdleService`'s screensaver overlay) already does this. Confirmed on hardware: without the lock, the shutdown screen intermittently failed to render correctly before the board's power rail cut.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Tactility/Source/app/launcher/Launcher.cpp (1)

92-151: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider moving the shutdown flow out of Launcher.

The launcher now owns confirmation UI, final-screen rendering, display-specific flush waiting, and power HAL orchestration. A dedicated full-screen shutdown app would keep Launcher focused and let the manifest hide system chrome for the confirmation/final screen. Based on the PR comments summary recommending a separate full-screen power-off app.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d6fff74-bc99-440c-a4a0-2ffc697c9f6b

📥 Commits

Reviewing files that changed from the base of the PR and between ababcf7 and 5e2062a.

📒 Files selected for processing (1)
  • Tactility/Source/app/launcher/Launcher.cpp

Comment on lines +101 to +104
static void showPoweredOffScreenAndWait(hal::display::DisplayDevice* display) {
if (!lvgl::lock(lvgl::defaultLockTime)) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not cut power when the shutdown screen was skipped.

If lvgl::lock() times out, showPoweredOffScreenAndWait() returns silently and performPowerOff() still calls powerOff(), so the confirmed shutdown can cut power without drawing/flushing the final screen.

🐛 Proposed fix
-    static void showPoweredOffScreenAndWait(hal::display::DisplayDevice* display) {
+    static bool showPoweredOffScreenAndWait(hal::display::DisplayDevice* display) {
         if (!lvgl::lock(lvgl::defaultLockTime)) {
-            return;
+            return false;
         }
@@
         lvgl::unlock();
+        return true;
     }
@@
         auto display = hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
-        showPoweredOffScreenAndWait(display.get());
+        if (!showPoweredOffScreenAndWait(display.get())) {
+            return;
+        }
 
         power->powerOff();

Also applies to: 137-140

Launcher was doing too much (Ken review, PR TactilityProject#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.
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
Tactility/Source/app/poweroff/PowerOff.cpp (1)

23-59: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Display used for the shutdown screen may not be the display resolved for flush-wait.

lv_obj_create(nullptr) (line 24) attaches the new screen to LVGL's default display, while display (used for lv_refr_now/waitForFlushComplete) is independently resolved via hal::findFirstDevice<hal::display::DisplayDevice>() at line 55. In a multi-display setup these can diverge, so the flush-wait could target/skip the wrong panel — the same concern previously raised (and unresolved) when this logic lived in Launcher.cpp, now carried over into this new file.

Consider resolving the DisplayDevice from the actual lv_display_t the screen was loaded on (e.g. via lv_obj_get_display(screen) and matching it against registered DisplayDevice::getLvglDisplay() instances) rather than blindly taking the first registered display device.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66e68510-3a23-4018-8b8b-bb3eb12ee20b

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2062a and 0d54177.

📒 Files selected for processing (3)
  • Tactility/Source/Tactility.cpp
  • Tactility/Source/app/launcher/Launcher.cpp
  • Tactility/Source/app/poweroff/PowerOff.cpp

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants