18#include "freertos/FreeRTOS.h"
19#include "freertos/task.h"
20#include <goodisplay/gdey029T94.h>
25static const char*
TAG =
"LockScreen";
55 memset(name_, 0,
sizeof(name_));
56 memset(info_, 0,
sizeof(info_));
57 memset(info2_, 0,
sizeof(info2_));
58 strcpy(clock_,
"--:--");
59 memset(date_, 0,
sizeof(date_));
71 const char* incoming =
name ?
name :
"";
87 const char* incoming = info ? info :
"";
103 const char* incoming = info2 ? info2 :
"";
104 if (strncmp(info2_, incoming,
MAX_TEXT_LEN) == 0)
return;
119 const char* incoming = clock ? clock :
"--:--";
120 if (strncmp(clock_, incoming,
sizeof(clock_)) == 0)
return;
122 strncpy(clock_, clock,
sizeof(clock_) - 1);
123 clock_[
sizeof(clock_) - 1] =
'\0';
125 strcpy(clock_,
"--:--");
135 const char* incoming = date ? date :
"";
136 if (strncmp(date_, incoming,
sizeof(date_)) == 0)
return;
138 strncpy(date_, date,
sizeof(date_) - 1);
139 date_[
sizeof(date_) - 1] =
'\0';
151 if (percent > 100) percent = 100;
152 if (batteryPercent_ != percent) {
153 batteryPercent_ = percent;
163 if (statusIcons_ != icons) {
164 statusIcons_ = icons;
183 static_cast<uint16_t
>(statusIcons_) & ~
static_cast<uint16_t
>(icon)
235 if (
display->isBacklightOn()) {
261 wifi.setUserEnabled(!wifi.isConnected());
323 "module context callback count must match MAX_MODULE_CONTEXT_ITEMS");
357 uint8_t itemCount = 0;
401 checkDeepSleepTrigger(nowMs);
408void LockScreenView::checkDeepSleepTrigger(uint32_t nowMs) {
415 if (nPressStartMs_ == 0) {
417 nPressStartMs_ = nowMs;
420 uint32_t elapsed = nowMs - nPressStartMs_;
422 LOG_I(
TAG,
"Long-press N detected, entering deep sleep...");
425 renderDeepSleepScreen();
434 vTaskDelay(pdMS_TO_TICKS(2000));
439 sleep->enterDeepSleep();
457 switch (footerMode_) {
458 case FooterMode::DEEP_SLEEP:
return ui::tr(
"core.deep_sleep");
459 case FooterMode::SHIP_MODE:
return ui::tr(
"core.ship_mode_hint");
460 default:
return ui::tr(
"core.press_any_key");
470void LockScreenView::renderBattery(
void* gfxPtr,
int x,
int y) {
471 auto* gfx =
static_cast<Gdey029T94*
>(gfxPtr);
482 int fillWidth = (batteryPercent_ * (
BAT_WIDTH - 4)) / 100;
484 gfx->fillRect(x + 2, y + 2, fillWidth,
BAT_HEIGHT - 4, EPD_BLACK);
509void LockScreenView::renderStatusIcons(
void* gfxPtr,
int x,
int y) {
510 auto* gfx =
static_cast<Gdey029T94*
>(gfxPtr);
512 const int iconSpacing = 14;
517 gfx->drawRect(iconX, y + 4, 8, 6, EPD_BLACK);
518 gfx->drawCircle(iconX + 4, y + 3, 3, EPD_BLACK);
519 iconX -= iconSpacing;
524 const int cx = iconX + 5;
525 const int cy = y + 10;
528 gfx->drawLine(cx - 2, cy - 8, cx + 2, cy - 8, EPD_BLACK);
529 gfx->drawPixel(cx - 3, cy - 7, EPD_BLACK);
530 gfx->drawPixel(cx + 3, cy - 7, EPD_BLACK);
531 gfx->drawPixel(cx - 4, cy - 6, EPD_BLACK);
532 gfx->drawPixel(cx + 4, cy - 6, EPD_BLACK);
535 gfx->drawLine(cx - 1, cy - 5, cx + 1, cy - 5, EPD_BLACK);
536 gfx->drawPixel(cx - 2, cy - 4, EPD_BLACK);
537 gfx->drawPixel(cx + 2, cy - 4, EPD_BLACK);
540 gfx->drawPixel(cx, cy - 2, EPD_BLACK);
543 gfx->fillRect(cx, cy, 2, 2, EPD_BLACK);
545 iconX -= iconSpacing;
551 gfx->drawLine(iconX + 4, y, iconX + 4, y + 10, EPD_BLACK);
552 gfx->drawLine(iconX + 4, y, iconX + 8, y + 3, EPD_BLACK);
553 gfx->drawLine(iconX + 8, y + 3, iconX + 2, y + 7, EPD_BLACK);
554 gfx->drawLine(iconX + 2, y + 3, iconX + 8, y + 7, EPD_BLACK);
555 gfx->drawLine(iconX + 8, y + 7, iconX + 4, y + 10, EPD_BLACK);
556 iconX -= iconSpacing;
561 int ux = iconX, uy = y;
563 gfx->drawLine(ux + 5, uy + 4, ux + 5, uy + 12, EPD_BLACK);
565 gfx->drawLine(ux + 2, uy + 4, ux + 8, uy + 4, EPD_BLACK);
567 gfx->drawLine(ux + 2, uy + 4, ux + 2, uy, EPD_BLACK);
568 gfx->fillCircle(ux + 2, uy, 1, EPD_BLACK);
570 gfx->drawLine(ux + 8, uy + 4, ux + 8, uy + 2, EPD_BLACK);
571 gfx->fillRect(ux + 6, uy, 4, 3, EPD_BLACK);
573 gfx->drawLine(ux + 5, uy + 12, ux + 3, uy + 10, EPD_BLACK);
574 gfx->drawLine(ux + 5, uy + 12, ux + 7, uy + 10, EPD_BLACK);
575 iconX -= iconSpacing;
580 gfx->fillCircle(iconX + 4, y + 5, 2, EPD_BLACK);
581 for (
int i = 0; i < 8; i++) {
583 int dx = (angle == 0 || angle == 180) ? 4 : (angle == 90 || angle == 270) ? 0 : 3;
584 int dy = (angle == 90 || angle == 270) ? 4 : (angle == 0 || angle == 180) ? 0 : 3;
585 if (angle > 90 && angle < 270) dx = -dx;
586 if (angle > 0 && angle < 180) dy = -dy;
587 gfx->drawPixel(iconX + 4 + dx, y + 5 + dy, EPD_BLACK);
589 iconX -= iconSpacing;
594 gfx->setCursor(iconX, y + 2);
596 iconX -= iconSpacing + 10;
598 gfx->setCursor(iconX, y + 2);
600 iconX -= iconSpacing;
605 int cx = iconX, cy = y;
607 gfx->drawRect(cx, cy + 3, 8, 7, EPD_BLACK);
609 gfx->drawLine(cx + 8, cy + 4, cx + 10, cy + 4, EPD_BLACK);
610 gfx->drawLine(cx + 10, cy + 4, cx + 10, cy + 8, EPD_BLACK);
611 gfx->drawLine(cx + 8, cy + 8, cx + 10, cy + 8, EPD_BLACK);
613 gfx->drawPixel(cx + 2, cy + 1, EPD_BLACK);
614 gfx->drawPixel(cx + 3, cy, EPD_BLACK);
615 gfx->drawPixel(cx + 5, cy + 1, EPD_BLACK);
616 gfx->drawPixel(cx + 6, cy, EPD_BLACK);
617 iconX -= iconSpacing;
622 int bx = iconX, by = y;
623 gfx->drawRect(bx, by + 1, 11, 11, EPD_BLACK);
624 const int cy = by + 6;
625 for (
int row = 0; row <= 8; row++) {
626 int rowY = by + 2 + row;
627 int r = rowY > cy ? rowY - cy : cy - rowY;
628 int rightX = bx + 8 - r;
629 if (rightX >= bx + 3) {
630 gfx->drawLine(bx + 3, rowY, rightX, rowY, EPD_BLACK);
633 iconX -= iconSpacing;
642void LockScreenView::renderTransitionScreen(FooterMode mode) {
662void LockScreenView::renderDeepSleepScreen() {
663 renderTransitionScreen(FooterMode::DEEP_SLEEP);
670 renderTransitionScreen(FooterMode::SHIP_MODE);
685 auto* gfx =
static_cast<Gdey029T94*
>(
display->getNativeHandle());
689 gfx->fillScreen(EPD_WHITE);
692 gfx->setTextColor(EPD_BLACK);
695 gfx->setFont(
nullptr);
702 gfx->setCursor(5,
DATE_Y);
713 auto fitAndDraw = [&](
const char* text,
int baselineY,
714 const GFXfont*
const* candidates,
size_t count) {
715 if (!text || !text[0])
return;
717 gfx, text, FIT_BUDGET, candidates, count,
true);
719 uint16_t w = 0, h = 0;
721 int x = (
display->getWidth() - w) / 2;
722 gfx->setCursor(x, baselineY);
727 static const GFXfont*
const NAME_FONTS[] = {
732 static const GFXfont*
const INFO_FONTS[] = {
738 fitAndDraw(name_,
NAME_Y, NAME_FONTS, std::size(NAME_FONTS));
741 fitAndDraw(info_,
INFO_Y, INFO_FONTS, std::size(INFO_FONTS));
744 fitAndDraw(info2_,
INFO2_Y, INFO_FONTS, std::size(INFO_FONTS));
747 gfx->setFont(
nullptr);
750 if (hint && hint[0]) {
753 gfx->getTextBounds(hint, 0, 0, &x1, &y1, &w, &h);
754 int hintX = (
display->getWidth() - w) / 2;
755 gfx->setCursor(hintX,
display->getHeight() - 10);
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
static constexpr int DATE_Y
static constexpr int BAT_TIP_WIDTH
static constexpr int BATTERY_X
static constexpr int DISPLAY_WIDTH
static constexpr int BATTERY_Y
static constexpr int INFO2_Y
static constexpr int BAT_WIDTH
Battery icon dimensions.
static constexpr int ICONS_Y
static constexpr int BAT_TIP_HEIGHT
static constexpr int BAT_HEIGHT
static constexpr int INFO_Y
static constexpr int NAME_Y
static constexpr int CLOCK_Y
Display layout constants.
Discovers, loads, runs and unloads WASM plugins on the badge.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_I(tag, fmt,...)
static ModuleRegistry & instance()
Returns the singleton module registry instance.
uint8_t getLockscreenItems(LockscreenItem *out, uint8_t max) const
Snapshot of all plugin lockscreen items. Returns the number written.
static PluginManager & instance() noexcept
void triggerLockscreenItem(const LockscreenItem &item)
Fire plugin_on_action(item.action_id, 0, 0) on the owning plugin.
void removeStatusIcon(StatusIcon icon)
Removes one status icon flag.
void onResume() override
Handles returning to lock screen and reapplies backlight policy.
void onTick(uint32_t nowMs) override
Per-tick handler for long-press deep-sleep detection.
void renderShipModeScreen()
Renders and flushes dedicated ship-mode transition screen.
void setBatteryPercent(uint8_t percent)
Updates battery percentage indicator.
void render(bool partial) override
Renders complete lock-screen layout.
const char * getFooterHint() const override
Returns footer hint based on current lock-screen mode.
InputResult onKey(char key) override
Handles lock-screen key actions.
void setClock(const char *clock)
Sets clock text shown in the header.
void init()
Initializes lock-screen state fields to defaults.
static constexpr uint8_t MAX_TEXT_LEN
void addStatusIcon(StatusIcon icon)
Adds one status icon flag.
void setDate(const char *date)
Sets date text shown below clock.
void setStatusIcons(StatusIcon icons)
Replaces full status-icon bitmask.
void toggleBacklight()
Toggles display backlight and corresponding status icon.
void setInfo2(const char *info2)
Sets second informational line.
void setInfo(const char *info)
Sets first informational line.
void setDisplayName(const char *name)
Sets primary display name shown on lock screen.
void onEnter(void *context) override
Handles entering lock screen and updates backlight behavior.
static WifiHandlers & instance()
Returns singleton Wi-Fi handlers instance.
bool isConnected() const
Returns whether Wi-Fi is currently connected.
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
IKeypad * getKeypadInstance()
Returns the singleton keypad service instance.
ISleepController * getSleepControllerInstance()
Returns the singleton sleep controller service instance.
void drawText(Adafruit_GFX *gfx, const char *text, const GFXfont *font)
Draws CP437-encoded text correctly for the given font: the built-in glcdfont (font == nullptr) is CP4...
void measureText(Adafruit_GFX *gfx, const char *text, const GFXfont *font, int16_t x0, int16_t y0, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h)
Measures CP437 text exactly as drawText would render it with font, so width-based layout (centering,...
const GFXfont * pickFontThatFits(Adafruit_GFX *gfx, const char *text, int maxWidthPx, const GFXfont *const *candidates, size_t count, bool cp437=false)
Picks the largest font from candidates whose rendered width of text fits within maxWidthPx....
void printText(Adafruit_GFX *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
Centralized key-code constants for cdc_views.
static void moduleContextCallback5()
Wrapper callback for module context item at index 5.
const char * tr(const char *key)
Look up a translation by string key.
static void pluginContextCallback1()
const GFXfont * getGfxFont(FontId id)
Resolves a FontId to its underlying GFX font pointer.
static uint8_t s_pluginContextCount
static void onLightMenuCallback()
Context-menu callback toggling lock-screen backlight mode.
static constexpr uint8_t MAX_MODULE_CONTEXT_ITEMS
static cdc::plugin_manager::PluginManager::LockscreenItem s_pluginContextItems[MAX_PLUGIN_ITEMS]
static void pluginContextCallback2()
static void(*const s_moduleCallbacks[])()
static constexpr char KEY_MENU
Open context menu / digit '3'.
static ContextMenuItem s_contextItems[MAX_CONTEXT_ITEMS]
static void moduleContextCallback3()
Wrapper callback for module context item at index 3.
static void(*const s_pluginCallbacks[MAX_PLUGIN_ITEMS])()
static constexpr uint32_t DEEP_SLEEP_HOLD_MS
static LockScreenView * s_lockScreenInstance
Static lock-screen instance pointer for C-style callbacks.
FontId
Canonical font identifier shared by firmware UI and plugin host API.
@ Bold12pt
FreeMonoBold 12pt. Latin-1 indexed; same mapping path as Bold9pt.
@ Builtin
Adafruit-GFX 6x8. Umlauts present at CP437 codepoints (0x84..0x9C). Pass raw CP437 bytes to draw them...
@ Bold9pt
FreeMonoBold 9pt. Latin-1 indexed; render via drawCp437Text() to map CP437 input.
static void moduleContextCallback1()
Wrapper callback for module context item at index 1.
static void moduleContextCallback2()
Wrapper callback for module context item at index 2.
static void moduleContextCallback4()
Wrapper callback for module context item at index 4.
static constexpr uint8_t MAX_CONTEXT_ITEMS
Storage for dynamic context-menu items contributed by modules.
void hideContextMenu()
Hides the active context menu modal.
static uint8_t s_moduleContextCount
ContextMenuView * showContextMenu(const char *title, const ContextMenuItem *items, uint8_t count)
Shows the shared context menu instance as modal.
static void moduleContextCallback0()
Wrapper callback for module context item at index 0.
static void pluginContextCallback0()
static void onWifiToggleCallback()
Context-menu callback toggling WiFi on/off via the user intent flag.
static void moduleContextCallback6()
Wrapper callback for module context item at index 6.
static constexpr uint8_t MAX_PLUGIN_ITEMS
static void pluginContextCallback3()
static core::LockScreenContextItem s_moduleContextItems[MAX_MODULE_CONTEXT_ITEMS]
Lock screen context menu item registered by a module.