Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/core/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ JsonDocument BruceConfig::toJson() const {
JsonArray dm = setting["disabledMenus"].to<JsonArray>();
for (int i = 0; i < disabledMenus.size(); i++) { dm.add(disabledMenus[i]); }

setting["lockEnabled"] = lockEnabled;
setting["lockPin"] = lockPin;

JsonArray qrArray = setting["qrCodes"].to<JsonArray>();
for (const auto &entry : qrCodes) {
JsonObject qrEntry = qrArray.add<JsonObject>();
Expand Down Expand Up @@ -416,6 +419,17 @@ void BruceConfig::fromFile(bool checkFS) {
log_e("Fail");
}

if (!setting["lockEnabled"].isNull()) {
lockEnabled = setting["lockEnabled"].as<bool>();
} else {
lockEnabled = false;
}
if (!setting["lockPin"].isNull()) {
lockPin = setting["lockPin"].as<String>();
} else {
lockPin = "";
}

if (!setting["qrCodes"].isNull()) {
qrCodes.clear();
JsonArray qrArray = setting["qrCodes"].as<JsonArray>();
Expand Down
4 changes: 4 additions & 0 deletions src/core/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class BruceConfig : public BruceTheme {

std::vector<String> disabledMenus = {};

// Device Lock
bool lockEnabled = false;
String lockPin = "";

std::vector<QrCodeEntry> qrCodes = {
{"Bruce AP", "WIFI:T:WPA;S:BruceNet;P:brucenet;;"},
{"Bruce Wiki", "https://github.com/pr3y/Bruce/wiki"},
Expand Down
69 changes: 69 additions & 0 deletions src/core/lock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include "lock.h"
#include "display.h"
#include "mykeyboard.h"
#include <globals.h>

static bool isPinAllDigits(const String &s) {
for (char c : s) {
if (c < '0' || c > '9') return false;
}
return true;
}

static void drawLockScreen() {
drawMainBorderWithTitle("Locked");

int midY = tftHeight / 2;

setTftDisplay(0, 0, bruceConfig.priColor, FM, bruceConfig.bgColor);
tft.drawCentreString("Device Locked", tftWidth / 2, midY - LH * FM, 1);

setTftDisplay(0, 0, bruceConfig.secColor, FP, bruceConfig.bgColor);
tft.drawCentreString("Press SELECT to unlock", tftWidth / 2, midY + LH * FM, 1);
}

void lockScreen() {
int failedAttempts = 0;

while (true) {
drawLockScreen();

// Wait for SELECT press
while (!check(SelPress)) { delay(50); }

// Prompt for PIN
String entered;
if (isPinAllDigits(bruceConfig.lockPin)) {
entered = num_keyboard("", 16, "Enter PIN:", true);
} else {
entered = keyboard("", 16, "Enter PIN:", true);
}

// Cancelled or ESC — redraw and keep waiting
if (entered == "\x1B" || entered.length() == 0) continue;

if (entered == bruceConfig.lockPin) return; // Correct — unlock

// Wrong PIN
failedAttempts++;
displayError("Wrong PIN");

// Exponential backoff: 15s base, doubles each failure, cap 5 min
if (failedAttempts >= 3) {
int timeoutSec = 15 * (1 << (failedAttempts - 3)); // 15, 30, 60, 120, 240 ...
if (timeoutSec > 300) timeoutSec = 300;

unsigned long deadline = millis() + (unsigned long)timeoutSec * 1000;
while (millis() < deadline) {
int remaining = (int)((deadline - millis()) / 1000) + 1;
drawMainBorderWithTitle("Locked");
setTftDisplay(0, 0, TFT_RED, FM, bruceConfig.bgColor);
tft.drawCentreString("Too many attempts!", tftWidth / 2, tftHeight / 2 - LH * FM, 1);
setTftDisplay(0, 0, bruceConfig.secColor, FP, bruceConfig.bgColor);
String msg = "Try again in " + String(remaining) + "s";
tft.drawCentreString(msg, tftWidth / 2, tftHeight / 2 + LH * FM, 1);
delay(1000);
}
}
}
}
5 changes: 5 additions & 0 deletions src/core/lock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

extern bool deviceLocked;

void lockScreen();
72 changes: 72 additions & 0 deletions src/core/menu_items/ConfigMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "../mykeyboard.h"
#include "core/display.h"
#include "core/i2c_finder.h"
#include "core/lock.h"
#include "core/main_menu.h"
#include "core/settings.h"
#include "core/utils.h"
Expand Down Expand Up @@ -48,6 +49,7 @@ void ConfigMenu::optionsMenu() {

int selected = loopOptions(localOptions, MENU_TYPE_SUBMENU, "Config");

if (deviceLocked) return;
// Exit to Main Menu only if user pressed Back
if (selected == -1 || selected == localOptions.size() - 1) { return; }
// Otherwise rebuild Config menu after submenu returns
Expand Down Expand Up @@ -172,18 +174,88 @@ void ConfigMenu::systemMenu() {
{"Hide/Show Apps", [this]() { mainMenu.hideAppsMenu(); }},
{"Clock", [this]() { setClock(); } },
{String("Keyboard Language: ") + bruceConfig.keyboardLang, [this]() { setKeyboardLanguage(); } },
{"Device Lock", [this]() { lockMenu(); } },
{"Advanced", [this]() { advancedMenu(); } },
{"Back", []() {} },
};

int selected = loopOptions(localOptions, MENU_TYPE_SUBMENU, "System Config");

if (deviceLocked) return;
// Exit only if user pressed Back or ESC
if (selected == -1 || selected == localOptions.size() - 1) { return; }
// Menu rebuilds to update toggle labels
}
}

/*********************************************************************
** Function: setLockPin
** Prompt for a new PIN with confirmation, save on match
** Returns true if a PIN was successfully set, false if user cancelled
**********************************************************************/
static bool setLockPin() {
String pin1 = keyboard("", 16, "Set PIN:", true);
if (pin1 == "\x1B" || pin1.length() == 0) return false;

String pin2 = keyboard("", 16, "Confirm PIN:", true);
if (pin2 == "\x1B") return false;

if (pin1 != pin2) {
displayError("PINs don't match");
return false;
}
bruceConfig.lockPin = pin1;
bruceConfig.saveFile();
displaySuccess("PIN set");
return true;
}

/*********************************************************************
** Function: lockMenu
** Device lock configuration submenu
**********************************************************************/
void ConfigMenu::lockMenu() {
while (true) {
std::vector<Option> localOptions;

localOptions.push_back(
{String("Lock: ") + (bruceConfig.lockEnabled ? "ON" : "OFF"),
[this]() {
if (!bruceConfig.lockEnabled) {
// Enabling — ensure a PIN exists first
if (bruceConfig.lockPin.length() == 0) {
if (!setLockPin()) return; // User cancelled PIN setup
}
bruceConfig.lockEnabled = true;
} else {
bruceConfig.lockEnabled = false;
bruceConfig.lockPin = "";
}
bruceConfig.saveFile();
}}
);

if (bruceConfig.lockEnabled) {
localOptions.push_back({"Set PIN", []() { setLockPin(); }});
localOptions.push_back(
{"Lock Now",
[]() {
deviceLocked = true;
}}
);
}

localOptions.push_back({"Back", []() {}});

int selected = loopOptions(localOptions, MENU_TYPE_SUBMENU, "Device Lock");

if (selected == -1 || selected == (int)localOptions.size() - 1) return;

// "Lock Now" selected — exit all the way back to the main loop
if (deviceLocked) return;
}
}

/*********************************************************************
** Function: advancedMenu
** Advanced settings submenu (nested under System Config)
Expand Down
1 change: 1 addition & 0 deletions src/core/menu_items/ConfigMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ConfigMenu : public MenuItemInterface {
void audioMenu(void);
void systemMenu(void);
void advancedMenu(void);
void lockMenu(void);
void powerMenu(void);
void devMenu(void);

Expand Down
8 changes: 8 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ String wifiIP;

bool BLEConnected = false;
bool returnToMenu;
bool deviceLocked = false;
bool isSleeping = false;
bool isScreenOff = false;
bool dimmer = false;
Expand Down Expand Up @@ -145,6 +146,7 @@ volatile int tftHeight = VECTOR_DISPLAY_DEFAULT_WIDTH;

#include "core/display.h"
#include "core/led_control.h"
#include "core/lock.h"
#include "core/mykeyboard.h"
#include "core/sd_functions.h"
#include "core/serialcmds.h"
Expand Down Expand Up @@ -444,6 +446,7 @@ void setup() {
tft.begin();
#endif
begin_storage();
deviceLocked = bruceConfig.lockEnabled;
begin_tft();
init_clock();
init_led();
Expand Down Expand Up @@ -532,6 +535,11 @@ void loop() {
#endif
tft.fillScreen(bruceConfig.bgColor);

if (deviceLocked) {
lockScreen();
deviceLocked = false;
}

mainMenu.begin();
delay(1);
}
Expand Down