CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
LockScreenView.cpp
Go to the documentation of this file.
1
6
9#include "cdc_ui/I18n.h"
11#include "cdc_views/KeyCodes.h"
12#include "cdc_hal/IDisplay.h"
13#include "cdc_hal/IKeypad.h"
17#include "cdc_log.h"
18#include "freertos/FreeRTOS.h"
19#include "freertos/task.h"
20#include <goodisplay/gdey029T94.h>
21#include "cdc_views/Fonts.h"
23#include <cstring>
24
25static const char* TAG = "LockScreen";
26
30static constexpr int CLOCK_Y = 5;
31static constexpr int DATE_Y = 22;
32static constexpr int ICONS_Y = 5;
33static constexpr int NAME_Y = 60; // Name position (FreeFont baseline)
34static constexpr int INFO_Y = 80; // Info line 1 (small text)
35static constexpr int INFO2_Y = 96; // Info line 2 (small text)
36static constexpr int BATTERY_X = 260;
37static constexpr int BATTERY_Y = 5;
38static constexpr int DISPLAY_WIDTH = 296;
39
40
44static constexpr int BAT_WIDTH = 28;
45static constexpr int BAT_HEIGHT = 12;
46static constexpr int BAT_TIP_WIDTH = 3;
47static constexpr int BAT_TIP_HEIGHT = 6;
48
49namespace cdc::ui {
50
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_));
60 batteryPercent_ = 0;
61 statusIcons_ = StatusIcon::NONE;
62 nPressStartMs_ = 0;
63 dirty_ = true;
64}
65
71 const char* incoming = name ? name : "";
72 if (strncmp(name_, incoming, MAX_TEXT_LEN) == 0) return;
73 if (name) {
74 strncpy(name_, name, MAX_TEXT_LEN - 1);
75 name_[MAX_TEXT_LEN - 1] = '\0';
76 } else {
77 name_[0] = '\0';
78 }
79 dirty_ = true;
80}
81
86void LockScreenView::setInfo(const char* info) {
87 const char* incoming = info ? info : "";
88 if (strncmp(info_, incoming, MAX_TEXT_LEN) == 0) return;
89 if (info) {
90 strncpy(info_, info, MAX_TEXT_LEN - 1);
91 info_[MAX_TEXT_LEN - 1] = '\0';
92 } else {
93 info_[0] = '\0';
94 }
95 dirty_ = true;
96}
97
102void LockScreenView::setInfo2(const char* info2) {
103 const char* incoming = info2 ? info2 : "";
104 if (strncmp(info2_, incoming, MAX_TEXT_LEN) == 0) return;
105 if (info2) {
106 strncpy(info2_, info2, MAX_TEXT_LEN - 1);
107 info2_[MAX_TEXT_LEN - 1] = '\0';
108 } else {
109 info2_[0] = '\0';
110 }
111 dirty_ = true;
112}
113
118void LockScreenView::setClock(const char* clock) {
119 const char* incoming = clock ? clock : "--:--";
120 if (strncmp(clock_, incoming, sizeof(clock_)) == 0) return;
121 if (clock) {
122 strncpy(clock_, clock, sizeof(clock_) - 1);
123 clock_[sizeof(clock_) - 1] = '\0';
124 } else {
125 strcpy(clock_, "--:--");
126 }
127 dirty_ = true;
128}
129
134void LockScreenView::setDate(const char* date) {
135 const char* incoming = date ? date : "";
136 if (strncmp(date_, incoming, sizeof(date_)) == 0) return;
137 if (date) {
138 strncpy(date_, date, sizeof(date_) - 1);
139 date_[sizeof(date_) - 1] = '\0';
140 } else {
141 date_[0] = '\0';
142 }
143 dirty_ = true;
144}
145
151 if (percent > 100) percent = 100;
152 if (batteryPercent_ != percent) {
153 batteryPercent_ = percent;
154 dirty_ = true;
155 }
156}
157
163 if (statusIcons_ != icons) {
164 statusIcons_ = icons;
165 dirty_ = true;
166 }
167}
168
174 setStatusIcons(statusIcons_ | icon);
175}
176
182 setStatusIcons(static_cast<StatusIcon>(
183 static_cast<uint16_t>(statusIcons_) & ~static_cast<uint16_t>(icon)
184 ));
185}
186
191
196void LockScreenView::onEnter(void* context) {
197 (void)context;
199 nPressStartMs_ = 0; // Reset deep sleep trigger
200
201 // Turn off backlight when entering lock screen (unless persistent light is on)
202 if ((statusIcons_ & StatusIcon::BACKLIGHT) == StatusIcon::NONE) {
204 if (display) {
205 display->backlightOff();
206 }
207 }
208 dirty_ = true;
209}
210
215 // Called when returning to lock screen (e.g., via N from main menu)
216 nPressStartMs_ = 0; // Reset deep sleep trigger
217
218 // Turn off backlight (unless persistent light is on)
219 if ((statusIcons_ & StatusIcon::BACKLIGHT) == StatusIcon::NONE) {
221 if (display) {
222 display->backlightOff();
223 }
224 }
225 dirty_ = true;
226}
227
233 if (!display) return;
234
235 if (display->isBacklightOn()) {
236 display->backlightOff();
238 } else {
239 display->backlightOn();
241 }
242 dirty_ = true;
243}
244
248static void onLightMenuCallback() {
250 s_lockScreenInstance->toggleBacklight();
251 }
253}
254
258static void onWifiToggleCallback() {
260 auto& wifi = WifiHandlers::instance();
261 wifi.setUserEnabled(!wifi.isConnected());
262}
263
267static constexpr uint8_t MAX_CONTEXT_ITEMS = 12;
268static constexpr uint8_t MAX_PLUGIN_ITEMS = 4;
269// Number of module context-menu wrapper callbacks defined below. The module
270// item array and the registry request are both bounded by this so that
271// s_moduleCallbacks is never indexed out of range. Keep in sync with the
272// s_moduleCallbacks[] table (enforced by static_assert after it).
273static constexpr uint8_t MAX_MODULE_CONTEXT_ITEMS = 7;
276static uint8_t s_moduleContextCount = 0;
277
279static uint8_t s_pluginContextCount = 0;
280
285static void moduleContextCallback0() { if (s_moduleContextItems[0].callback) { s_moduleContextItems[0].callback(); } hideContextMenu(); }
290static void moduleContextCallback1() { if (s_moduleContextItems[1].callback) { s_moduleContextItems[1].callback(); } hideContextMenu(); }
295static void moduleContextCallback2() { if (s_moduleContextItems[2].callback) { s_moduleContextItems[2].callback(); } hideContextMenu(); }
300static void moduleContextCallback3() { if (s_moduleContextItems[3].callback) { s_moduleContextItems[3].callback(); } hideContextMenu(); }
305static void moduleContextCallback4() { if (s_moduleContextItems[4].callback) { s_moduleContextItems[4].callback(); } hideContextMenu(); }
310static void moduleContextCallback5() { if (s_moduleContextItems[5].callback) { s_moduleContextItems[5].callback(); } hideContextMenu(); }
315static void moduleContextCallback6() { if (s_moduleContextItems[6].callback) { s_moduleContextItems[6].callback(); } hideContextMenu(); }
316
322static_assert(sizeof(s_moduleCallbacks) / sizeof(s_moduleCallbacks[0]) == MAX_MODULE_CONTEXT_ITEMS,
323 "module context callback count must match MAX_MODULE_CONTEXT_ITEMS");
324
345
355 // KEY_MENU ('3') opens context menu for light toggle + module items
356 if (key == KEY_MENU) {
357 uint8_t itemCount = 0;
358
359 // First item: Light toggle (built-in)
360 s_contextItems[itemCount++] = {ui::tr("core.light"), onLightMenuCallback};
361
362 // Second item: WiFi toggle (built-in); label reflects current state
363 const char* wifiLabel = WifiHandlers::instance().isConnected()
364 ? ui::tr("core.wifi_off") : ui::tr("core.wifi_on");
365 s_contextItems[itemCount++] = {wifiLabel, onWifiToggleCallback};
366
367 // Get module items from registry
368 auto& moduleReg = core::ModuleRegistry::instance();
369 s_moduleContextCount = moduleReg.getLockScreenContextItems(s_moduleContextItems, MAX_MODULE_CONTEXT_ITEMS);
370
371 // Add module items to context menu
372 for (uint8_t i = 0; i < s_moduleContextCount && itemCount < MAX_CONTEXT_ITEMS; i++) {
373 const char* label = s_moduleContextItems[i].getLabel ? s_moduleContextItems[i].getLabel() : "???";
374 s_contextItems[itemCount++] = {label, s_moduleCallbacks[i]};
375 }
376
377 // Plugin-contributed lockscreen items.
380 for (uint8_t i = 0; i < s_pluginContextCount && itemCount < MAX_CONTEXT_ITEMS; i++) {
381 s_contextItems[itemCount++] = {s_pluginContextItems[i].label, s_pluginCallbacks[i]};
382 }
383
384 showContextMenu(ui::tr("core.actions"), s_contextItems, itemCount);
386 }
387
388 // Any other key triggers unlock
389 if (onUnlock_) {
390 onUnlock_();
391 }
393}
394
399void LockScreenView::onTick(uint32_t nowMs) {
400 // Check for long-press N -> deep sleep (flight mode)
401 checkDeepSleepTrigger(nowMs);
402}
403
408void LockScreenView::checkDeepSleepTrigger(uint32_t nowMs) {
409 auto* keypad = hal::getKeypadInstance();
410 if (!keypad) return;
411
412 bool nPressed = keypad->isKeyPressed(hal::Key::KEY_NO);
413
414 if (nPressed) {
415 if (nPressStartMs_ == 0) {
416 // N key just pressed, start timing
417 nPressStartMs_ = nowMs;
418 } else {
419 // Check if held long enough
420 uint32_t elapsed = nowMs - nPressStartMs_;
421 if (elapsed >= DEEP_SLEEP_HOLD_MS) {
422 LOG_I(TAG, "Long-press N detected, entering deep sleep...");
423
424 // Render deep sleep screen and push to e-paper
425 renderDeepSleepScreen();
426
427 // Turn off backlight
429 if (display) {
430 display->backlightOff();
431 }
432
433 // Wait 2s so user can release button without triggering wakeup
434 vTaskDelay(pdMS_TO_TICKS(2000));
435
436 // Enter deep sleep (does not return - causes reset on wake)
437 auto* sleep = hal::getSleepControllerInstance();
438 if (sleep) {
439 sleep->enterDeepSleep();
440 }
441
442 // Should not reach here
443 nPressStartMs_ = 0;
444 }
445 }
446 } else {
447 // N released, reset timer
448 nPressStartMs_ = 0;
449 }
450}
451
456const char* LockScreenView::getFooterHint() const {
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");
461 }
462}
463
470void LockScreenView::renderBattery(void* gfxPtr, int x, int y) {
471 auto* gfx = static_cast<Gdey029T94*>(gfxPtr);
472 // Battery outline
473 gfx->drawRect(x, y, BAT_WIDTH, BAT_HEIGHT, EPD_BLACK);
474
475 // Battery tip
476 gfx->fillRect(x + BAT_WIDTH, y + (BAT_HEIGHT - BAT_TIP_HEIGHT) / 2,
477 BAT_TIP_WIDTH, BAT_TIP_HEIGHT, EPD_BLACK);
478
479 // Fill level (skip if no battery - show empty outline)
480 bool noBattery = (statusIcons_ & StatusIcon::NO_BATTERY) != StatusIcon::NONE;
481 if (!noBattery) {
482 int fillWidth = (batteryPercent_ * (BAT_WIDTH - 4)) / 100;
483 if (fillWidth > 0) {
484 gfx->fillRect(x + 2, y + 2, fillWidth, BAT_HEIGHT - 4, EPD_BLACK);
485 }
486 }
487
488 // Charging indicator
489 if ((statusIcons_ & StatusIcon::CHARGING) != StatusIcon::NONE) {
490 // Draw lightning bolt
491 gfx->drawLine(x + BAT_WIDTH / 2 + 2, y + 1, x + BAT_WIDTH / 2 - 2, y + BAT_HEIGHT / 2, EPD_BLACK);
492 gfx->drawLine(x + BAT_WIDTH / 2 - 2, y + BAT_HEIGHT / 2, x + BAT_WIDTH / 2 + 2, y + BAT_HEIGHT / 2, EPD_BLACK);
493 gfx->drawLine(x + BAT_WIDTH / 2 + 2, y + BAT_HEIGHT / 2, x + BAT_WIDTH / 2 - 2, y + BAT_HEIGHT - 2, EPD_BLACK);
494 }
495
496 // No battery indicator - diagonal strike-through
497 if ((statusIcons_ & StatusIcon::NO_BATTERY) != StatusIcon::NONE) {
498 gfx->drawLine(x - 2, y + BAT_HEIGHT + 2, x + BAT_WIDTH + BAT_TIP_WIDTH + 2, y - 2, EPD_BLACK);
499 gfx->drawLine(x - 2, y + BAT_HEIGHT + 3, x + BAT_WIDTH + BAT_TIP_WIDTH + 2, y - 1, EPD_BLACK);
500 }
501}
502
509void LockScreenView::renderStatusIcons(void* gfxPtr, int x, int y) {
510 auto* gfx = static_cast<Gdey029T94*>(gfxPtr);
511 int iconX = x;
512 const int iconSpacing = 14;
513
514 // Lock icon
515 if ((statusIcons_ & StatusIcon::LOCK) != StatusIcon::NONE) {
516 // Draw padlock
517 gfx->drawRect(iconX, y + 4, 8, 6, EPD_BLACK);
518 gfx->drawCircle(iconX + 4, y + 3, 3, EPD_BLACK);
519 iconX -= iconSpacing;
520 }
521
522 // WiFi icon - three stacked ripple arcs with a base dot
523 if ((statusIcons_ & StatusIcon::WIFI) != StatusIcon::NONE) {
524 const int cx = iconX + 5;
525 const int cy = y + 10;
526
527 // Arc 3 (large): 5px horizontal cap with stepped sides
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);
533
534 // Arc 2 (medium): 3px horizontal cap with stepped sides
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);
538
539 // Arc 1 (small): single-pixel peak
540 gfx->drawPixel(cx, cy - 2, EPD_BLACK);
541
542 // Base dot (2x2)
543 gfx->fillRect(cx, cy, 2, 2, EPD_BLACK);
544
545 iconX -= iconSpacing;
546 }
547
548 // BLE icon
549 if ((statusIcons_ & StatusIcon::BLE) != StatusIcon::NONE) {
550 // Bluetooth rune
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;
557 }
558
559 // USB icon (simplified USB trident)
560 if ((statusIcons_ & StatusIcon::USB) != StatusIcon::NONE) {
561 int ux = iconX, uy = y;
562 // Main stem
563 gfx->drawLine(ux + 5, uy + 4, ux + 5, uy + 12, EPD_BLACK);
564 // Top horizontal
565 gfx->drawLine(ux + 2, uy + 4, ux + 8, uy + 4, EPD_BLACK);
566 // Left branch with circle
567 gfx->drawLine(ux + 2, uy + 4, ux + 2, uy, EPD_BLACK);
568 gfx->fillCircle(ux + 2, uy, 1, EPD_BLACK);
569 // Right branch with rectangle
570 gfx->drawLine(ux + 8, uy + 4, ux + 8, uy + 2, EPD_BLACK);
571 gfx->fillRect(ux + 6, uy, 4, 3, EPD_BLACK);
572 // Bottom arrow
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;
576 }
577
578 // Backlight icon (sun)
579 if ((statusIcons_ & StatusIcon::BACKLIGHT) != StatusIcon::NONE) {
580 gfx->fillCircle(iconX + 4, y + 5, 2, EPD_BLACK);
581 for (int i = 0; i < 8; i++) {
582 int angle = i * 45;
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);
588 }
589 iconX -= iconSpacing;
590 }
591
592 // Sleep icons
593 if ((statusIcons_ & StatusIcon::DEEP_SLEEP) != StatusIcon::NONE) {
594 gfx->setCursor(iconX, y + 2);
595 gfx->print("zzZ");
596 iconX -= iconSpacing + 10;
597 } else if ((statusIcons_ & StatusIcon::LIGHT_SLEEP) != StatusIcon::NONE) {
598 gfx->setCursor(iconX, y + 2);
599 gfx->print("z");
600 iconX -= iconSpacing;
601 }
602
603 // Caffeinated icon (sleep inhibited) - coffee cup
604 if ((statusIcons_ & StatusIcon::CAFFEINATED) != StatusIcon::NONE) {
605 int cx = iconX, cy = y;
606 // Cup body
607 gfx->drawRect(cx, cy + 3, 8, 7, EPD_BLACK);
608 // Cup handle
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);
612 // Steam (wavy lines)
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;
618 }
619
620 // Background plugin running - play triangle inside a square frame
621 if ((statusIcons_ & StatusIcon::BACKGROUND) != StatusIcon::NONE) {
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);
631 }
632 }
633 iconX -= iconSpacing;
634 }
635 (void)iconX;
636}
637
642void LockScreenView::renderTransitionScreen(FooterMode mode) {
643 footerMode_ = mode;
644
645 // Clear clock and status icons for minimal screen
646 setClock("");
647 setDate("");
648 statusIcons_ = StatusIcon::NONE;
649
650 // Render normal lockscreen (with transition footer) and push to display
651 render(false);
652
654 if (display) {
656 }
657}
658
662void LockScreenView::renderDeepSleepScreen() {
663 renderTransitionScreen(FooterMode::DEEP_SLEEP);
664}
665
670 renderTransitionScreen(FooterMode::SHIP_MODE);
671}
672
677void LockScreenView::render(bool partial) {
678 if (preRenderCb_) {
679 preRenderCb_();
680 }
681
683 if (!display) return;
684
685 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
686 if (!gfx) return;
687
688 if (!partial) {
689 gfx->fillScreen(EPD_WHITE);
690 }
691
692 gfx->setTextColor(EPD_BLACK);
693
694 // === Top Left: Clock ===
695 gfx->setFont(nullptr);
696 gfx->setTextSize(2); // Size 2 for better fit
697 gfx->setCursor(5, CLOCK_Y);
698 gfx->print(clock_);
699
700 // === Below clock: Date ===
701 gfx->setTextSize(1);
702 gfx->setCursor(5, DATE_Y);
703 render::printText(gfx, date_);
704
705 // === Top Right: Battery ===
706 renderBattery(gfx, BATTERY_X, BATTERY_Y);
707
708 // === Right of battery: Status icons ===
709 renderStatusIcons(gfx, BATTERY_X - 20, ICONS_Y);
710
711 constexpr int FIT_BUDGET = DISPLAY_WIDTH - 10;
712
713 auto fitAndDraw = [&](const char* text, int baselineY,
714 const GFXfont* const* candidates, size_t count) {
715 if (!text || !text[0]) return;
716 const GFXfont* f = cdc::ui::render::pickFontThatFits(
717 gfx, text, FIT_BUDGET, candidates, count, true);
718 int16_t x1, y1;
719 uint16_t w = 0, h = 0;
720 cdc::ui::render::measureText(gfx, text, f, 0, 0, &x1, &y1, &w, &h);
721 int x = (display->getWidth() - w) / 2;
722 gfx->setCursor(x, baselineY);
723 cdc::ui::render::drawText(gfx, text, f);
724 };
725
726 using cdc::ui::FontId;
727 static const GFXfont* const NAME_FONTS[] = {
731 };
732 static const GFXfont* const INFO_FONTS[] = {
735 };
736
737 // === Center: Name (size 3 = 12pt, fallback to smaller) ===
738 fitAndDraw(name_, NAME_Y, NAME_FONTS, std::size(NAME_FONTS));
739
740 // === Info line 1 (size 2 = 9pt, fallback to 1) ===
741 fitAndDraw(info_, INFO_Y, INFO_FONTS, std::size(INFO_FONTS));
742
743 // === Info line 2 (size 2 = 9pt, fallback to 1) ===
744 fitAndDraw(info2_, INFO2_Y, INFO_FONTS, std::size(INFO_FONTS));
745
746 // === Bottom: Footer hint (size 1 = built-in 6x8) ===
747 gfx->setFont(nullptr);
748 gfx->setTextSize(1);
749 const char* hint = getFooterHint();
750 if (hint && hint[0]) {
751 int16_t x1, y1;
752 uint16_t w, h;
753 gfx->getTextBounds(hint, 0, 0, &x1, &y1, &w, &h);
754 int hintX = (display->getWidth() - w) / 2;
755 gfx->setCursor(hintX, display->getHeight() - 10);
756 render::printText(gfx, hint);
757 }
758
759 dirty_ = false;
760}
761
762} // namespace cdc::ui
static const char * TAG
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,...)
Definition cdc_log.h:147
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.
Definition IModule.h:8
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.
Definition I18n.h:209
static void pluginContextCallback1()
const GFXfont * getGfxFont(FontId id)
Resolves a FontId to its underlying GFX font pointer.
Definition Fonts.cpp:26
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'.
Definition KeyCodes.h:47
static ContextMenuItem s_contextItems[MAX_CONTEXT_ITEMS]
static void moduleContextCallback3()
Wrapper callback for module context item at index 3.
Gdey029T94 * display
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.
Definition Fonts.h:15
@ Bold12pt
FreeMonoBold 12pt. Latin-1 indexed; same mapping path as Bold9pt.
Definition Fonts.h:18
@ Builtin
Adafruit-GFX 6x8. Umlauts present at CP437 codepoints (0x84..0x9C). Pass raw CP437 bytes to draw them...
Definition Fonts.h:16
@ Bold9pt
FreeMonoBold 9pt. Latin-1 indexed; render via drawCp437Text() to map CP437 input.
Definition Fonts.h:17
InputResult
Definition IView.h:11
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.
Definition IModule.h:42