From 6589c5580a653fcae748a50abae8588f46ac064d Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Sun, 26 Apr 2026 01:15:27 -0700 Subject: [PATCH] LockscreenActivity.cpp: Redesigned lockscreen UI with passcode-style dot input, clean keypad layout, and fixed Select button leaking a press to the next activity on unlock. --- plugins/lockscreen/LockscreenActivity.cpp | 115 +++++++++++++--------- plugins/lockscreen/LockscreenActivity.h | 4 +- plugins/lockscreen/patch.py | 22 ++--- 3 files changed, 80 insertions(+), 61 deletions(-) diff --git a/plugins/lockscreen/LockscreenActivity.cpp b/plugins/lockscreen/LockscreenActivity.cpp index 4e99c22..e30dc41 100644 --- a/plugins/lockscreen/LockscreenActivity.cpp +++ b/plugins/lockscreen/LockscreenActivity.cpp @@ -9,12 +9,12 @@ #include "components/UITheme.h" #include "fontIds.h" -static constexpr int SCREEN_W = 480; -static constexpr int KEY_W = 130; -static constexpr int KEY_H = 80; -static constexpr int KEY_PAD = 8; -static constexpr int GRID_W = LockscreenActivity::COLS * (KEY_W + KEY_PAD) - KEY_PAD; -static constexpr int GRID_X = (SCREEN_W - GRID_W) / 2; +static constexpr int CARD_MARGIN = 30; + +static constexpr int KEY_W = 110; +static constexpr int KEY_H = 72; +static constexpr int KEY_PAD_X = 14; +static constexpr int KEY_PAD_Y = 10; void LockscreenActivity::onEnter() { Activity::onEnter(); @@ -63,6 +63,10 @@ void LockscreenActivity::pressCurrentKey() { finish(); } else { if (LockscreenPlugin::checkPin(pin_, SETTINGS.lockscreenPinHash)) { + while (mappedInput.isPressed(MappedInputManager::Button::Confirm)) { + mappedInput.update(); + delay(10); + } success_ = true; done_ = true; } else { @@ -86,70 +90,91 @@ void LockscreenActivity::pressCurrentKey() { cursorRow_ = 3; cursorCol_ = 2; } - renderPending_ = true; + renderPending_ = true; requestUpdate(); } } -void LockscreenActivity::renderPinDisplay(int startY, bool masked) const { - constexpr int BOX_W = 50; - constexpr int BOX_H = 60; - constexpr int BOX_PAD = 12; - constexpr int TOTAL_W = 4 * BOX_W + 3 * BOX_PAD; - int x = (SCREEN_W - TOTAL_W) / 2; - - for (int i = 0; i < PIN_LEN; i++) { - int bx = x + i * (BOX_W + BOX_PAD); - renderer.drawRect(bx, startY, BOX_W, BOX_H); - if (i < pinLen_) { - char display[2] = {masked ? '*' : pin_[i], '\0'}; - renderer.drawText(UI_12_FONT_ID, bx + BOX_W / 2 - 8, startY + BOX_H / 2 - 10, display); +void LockscreenActivity::renderPinDisplay(int inputX, int inputY, int inputLineY, int inputW, bool masked) const { + char dotStr[PIN_LEN * 4 + 2] = {}; + int pos = 0; + for (int i = 0; i < pinLen_; i++) { + if (masked) { + dotStr[pos++] = '\xe2'; + dotStr[pos++] = '\x80'; + dotStr[pos++] = '\xa2'; + } else { + dotStr[pos++] = pin_[i]; } } + dotStr[pos] = '\0'; + + renderer.drawText(UI_12_FONT_ID, inputX, inputY, dotStr, true, EpdFontFamily::BOLD); + renderer.drawLine(inputX, inputLineY, inputX + inputW, inputLineY); } -void LockscreenActivity::renderKeypad(int startY) const { +void LockscreenActivity::renderKeypad(int gridX, int gridY) const { for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { - int kx = GRID_X + c * (KEY_W + KEY_PAD); - int ky = startY + r * (KEY_H + KEY_PAD); + int kx = gridX + c * (KEY_W + KEY_PAD_X); + int ky = gridY + r * (KEY_H + KEY_PAD_Y); bool isCursor = (r == cursorRow_ && c == cursorCol_); + + const char* displayLabel = KEYS[r][c]; + if (isCursor) { - renderer.fillRect(kx, ky, KEY_W, KEY_H); - renderer.drawText(UI_12_FONT_ID, kx + KEY_W / 2 - 8, ky + KEY_H / 2 - 10, - KEYS[r][c], false, EpdFontFamily::BOLD); + constexpr int HL_PAD = 8; + renderer.fillRect(kx - HL_PAD, ky - HL_PAD / 2, + KEY_W + HL_PAD * 2, KEY_H + HL_PAD, true); + renderer.drawText(UI_12_FONT_ID, + kx + KEY_W / 2 - 8, + ky + KEY_H / 2 - 10, + displayLabel, false, EpdFontFamily::BOLD); } else { - renderer.drawRect(kx, ky, KEY_W, KEY_H); - renderer.drawText(UI_12_FONT_ID, kx + KEY_W / 2 - 8, ky + KEY_H / 2 - 10, - KEYS[r][c], true, EpdFontFamily::REGULAR); + renderer.drawText(UI_12_FONT_ID, + kx + KEY_W / 2 - 8, + ky + KEY_H / 2 - 10, + displayLabel, true, EpdFontFamily::REGULAR); } } } } void LockscreenActivity::render(RenderLock&&) { - const auto& metrics = UITheme::getInstance().getMetrics(); - const auto pageH = renderer.getScreenHeight(); - const bool isCreate = (purpose_ == Purpose::CREATE); - const bool masked = !isCreate; + const bool isCreate = (purpose_ == Purpose::CREATE); + const bool masked = !isCreate; + + const int screenW = renderer.getScreenWidth(); + const int screenH = renderer.getScreenHeight(); + + const int cardX = CARD_MARGIN; + const int cardY = CARD_MARGIN; + const int cardW = screenW - 2 * CARD_MARGIN; + const int cardH = screenH - 2 * CARD_MARGIN; + + const int titleY = cardY + 36; + const int inputY = cardY + 110; + const int inputLineY = inputY + 42; + const int inputX = cardX + 30; + const int inputW = cardW - 60; + + const int gridW = COLS * KEY_W + (COLS - 1) * KEY_PAD_X; + const int gridX = (screenW - gridW) / 2; + const int gridY = inputLineY + 60; renderer.clearScreen(); + renderer.drawRect(cardX, cardY, cardW, cardH); - const char* title = isCreate ? "Set PIN" : "Unlock"; - GUI.drawHeader(renderer, Rect{0, metrics.topPadding, SCREEN_W, metrics.headerHeight}, title); + const char* title = isCreate ? "Set Passcode" : "Enter Passcode"; + renderer.drawText(UI_12_FONT_ID, cardX + 30, titleY, title, true, EpdFontFamily::BOLD); - int y = metrics.topPadding + metrics.headerHeight + 80; + renderPinDisplay(inputX, inputY, inputLineY, inputW, masked); + renderKeypad(gridX, gridY); - renderPinDisplay(y, masked); - y += 90; - renderKeypad(y); - - if (isCreate) { - renderer.drawCenteredText(UI_10_FONT_ID, pageH - 40, "Navigate: Directional buttons | Select: Power"); - } else if (attempts_ > 0) { - char msg[40]; + if (attempts_ > 0) { + char msg[48]; snprintf(msg, sizeof(msg), "Incorrect PIN. %d attempt(s) remaining.", MAX_ATTEMPTS - attempts_); - renderer.drawCenteredText(UI_10_FONT_ID, pageH - 40, msg); + renderer.drawCenteredText(UI_10_FONT_ID, cardY + cardH - 28, msg); } renderer.displayBuffer(); diff --git a/plugins/lockscreen/LockscreenActivity.h b/plugins/lockscreen/LockscreenActivity.h index 811568a..ee6c5c0 100644 --- a/plugins/lockscreen/LockscreenActivity.h +++ b/plugins/lockscreen/LockscreenActivity.h @@ -59,7 +59,7 @@ class LockscreenActivity final : public Activity { unsigned long enterTime_ = 0; void pressCurrentKey(); - void renderPinDisplay(int startY, bool masked) const; - void renderKeypad(int startY) const; + void renderPinDisplay(int inputX, int inputY, int inputLineY, int inputW, bool masked) const; + void renderKeypad(int gridX, int gridY) const; void goToSleep(); }; diff --git a/plugins/lockscreen/patch.py b/plugins/lockscreen/patch.py index 995f2b0..fecd28f 100644 --- a/plugins/lockscreen/patch.py +++ b/plugins/lockscreen/patch.py @@ -332,7 +332,7 @@ def patch_web_server(repo_dir): path = find_first("CrossPointWebServer.cpp", repo_dir) content = read_file(path) - if 'lockscreenMode") continue' in content: + if 'lockscreenMode") == 0 || strcmp(s.key, "lockscreenPinHash") == 0)) continue' in content: print(" CrossPointWebServer.cpp already patched, skipping.") return @@ -352,20 +352,12 @@ def patch_web_server(repo_dir): print(" CrossPointWebServer.cpp patched (lockscreen hidden from WUI).") return - name_override = ( - '\n' - ' if (s.key && (strcmp(s.key, "lockscreenMode") == 0 || strcmp(s.key, "lockscreenPinHash") == 0)) continue;\n' - ) + skip = ' if (s.key && (strcmp(s.key, "lockscreenMode") == 0 || strcmp(s.key, "lockscreenPinHash") == 0)) continue;\n' - content = content.replace( - ' doc["category"] = I18N.get(s.category);\n' - '\n' - ' switch (s.type) {', - ' doc["category"] = I18N.get(s.category);\n' - + name_override - + '\n' - ' switch (s.type) {' - ) + # Insert continue right after category line regardless of what follows + anchor = ' doc["category"] = I18N.get(s.category);\n' + if anchor in content: + content = content.replace(anchor, anchor + '\n' + skip, 1) write_file(path, content) print(" CrossPointWebServer.cpp patched (lockscreen hidden from WUI).") @@ -409,6 +401,8 @@ def patch_main_cpp(repo_dir): ' delay(10);\n' ' }\n' ' lockAct.onExit();\n' + ' renderer.clearScreen();\n' + ' renderer.displayBuffer();\n' ' if (!lockAct.wasSuccessful()) {\n' ' enterDeepSleep();\n' ' return;\n'