Public Access
Fn+h on any screen, text fields included, and ? outside Text Entry, open a panel over the content area: the screen's own keys, then the ones that work everywhere. Every App declares its keys for the state it is in (pages, viewers, dialogs and text fields answer for themselves); the App manager opens the panel and takes every key while it is open. About 30 hint lines are gone, from every App. What stays on a screen is state. The first-start Setup keeps its hints and teaches the key; a device set up before gets one Toast, once. The guide and the FAQ open with it. `key help` over the consoles. 476 host tests (8 new). Checked on the device with key help and screenshots: the Launcher, all nine Apps and several of their states. 8.5 KB of flash and 40 bytes of static RAM. Decisions Q196 to Q203 in docs/milestones/U1.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
150 lines
5.8 KiB
C++
150 lines
5.8 KiB
C++
#include "apps/debug_console_page.h"
|
|
|
|
#include "debug_auth.h"
|
|
#include "services/debug_console.h"
|
|
#include "ui/fonts.h"
|
|
#include "ui/widgets.h"
|
|
|
|
namespace roro {
|
|
|
|
void DebugConsolePage::enter() {
|
|
list_.setCount(kRows);
|
|
confirm_.reset();
|
|
typing_ = false;
|
|
refusal_.clear();
|
|
}
|
|
|
|
bool DebugConsolePage::onKey(const KeyEvent& e) {
|
|
if (confirm_) {
|
|
confirm_->onKey(e);
|
|
if (confirm_->result() == 1) {
|
|
if (ask_ == Ask::SwitchOn) DebugConsole::switchOn(settings_);
|
|
else settings_.setString(Setting::DebugToken, DebugConsole::freshToken());
|
|
}
|
|
if (confirm_->result() != DialogModel::kPending) confirm_.reset();
|
|
return true;
|
|
}
|
|
if (typing_) {
|
|
switch (e.key) {
|
|
case Key::Char: editor_.insert(e.ch); break;
|
|
case Key::Delete: editor_.backspace(); break;
|
|
case Key::Left: editor_.left(); break;
|
|
case Key::Right: editor_.right(); break;
|
|
case Key::Back: typing_ = false; break;
|
|
case Key::Select: {
|
|
std::string token = debug::tidyToken(editor_.text());
|
|
if (debug::validToken(token) && settings_.setString(Setting::DebugToken, token)) typing_ = false;
|
|
else refusal_ = "16 to 64 characters, please";
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
return true;
|
|
}
|
|
switch (e.key) {
|
|
case Key::Up: list_.up(); break;
|
|
case Key::Down: list_.down(); break;
|
|
case Key::Back: return false;
|
|
case Key::Left:
|
|
case Key::Right:
|
|
case Key::Select:
|
|
if (e.key != Key::Select && list_.selected() != kSwitch) break;
|
|
switch (list_.selected()) {
|
|
case kSwitch:
|
|
if (settings_.getBool(Setting::DebugConsole)) settings_.setBool(Setting::DebugConsole, false);
|
|
else { // on is the one to think about
|
|
ask_ = Ask::SwitchOn;
|
|
confirm_.reset(new DialogModel({"Cancel", "Switch on"}));
|
|
}
|
|
break;
|
|
case kNewToken:
|
|
ask_ = Ask::NewToken;
|
|
confirm_.reset(new DialogModel({"Cancel", "New token"}));
|
|
break;
|
|
case kTypeToken:
|
|
editor_ = LineEditor(64);
|
|
refusal_.clear();
|
|
typing_ = true;
|
|
break;
|
|
default: break;
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DebugConsolePage::help(std::vector<KeyHelp>& out) const {
|
|
if (confirm_) return help::dialog(out);
|
|
if (typing_) return help::textEntry(out);
|
|
help::list(out, "switch, or open");
|
|
out.push_back({", /", "switch the console on or off"});
|
|
}
|
|
|
|
void DebugConsolePage::draw(Canvas& c) {
|
|
const auto& area = theme::kContent;
|
|
c.setTextDatum(top_left);
|
|
if (typing_) {
|
|
c.setFont(&fonts::body);
|
|
c.setTextColor(theme::kMuted);
|
|
c.drawString("A token of your own", 4, area.y + 4);
|
|
widgets::lineEditor(c, editor_, {4, area.y + 22, area.w - 8, 0});
|
|
c.setTextColor(refusal_.empty() ? theme::kMuted : theme::kWarning);
|
|
c.drawString(refusal_.empty() ? "16 to 64 characters. Capitals or not, it's the same." : refusal_.c_str(), 4, area.y + 44);
|
|
return;
|
|
}
|
|
|
|
bool on = settings_.getBool(Setting::DebugConsole);
|
|
std::string ip = wifi_.ip();
|
|
widgets::list(
|
|
c, list_, {area.x, area.y, area.w, kRows * theme::kLineHeight},
|
|
[](int i) -> std::string {
|
|
switch (i) {
|
|
case kSwitch: return "Debug Console";
|
|
case kAddress: return "Connect to";
|
|
case kNewToken: return "New token";
|
|
default: return "Type a token";
|
|
}
|
|
},
|
|
[&](int i) -> std::string {
|
|
switch (i) {
|
|
case kSwitch: return on ? "On" : "Off";
|
|
case kAddress: return !on ? "-" : ip.empty() ? "Wi-Fi not connected" : ip + ":" + std::to_string(DebugConsole::kPort);
|
|
default: return ">";
|
|
}
|
|
});
|
|
|
|
// The token: here, in full, and nowhere else (it's never printed on a console). One the device
|
|
// made is drawn at twice the size, three groups then two: it has to be read and typed.
|
|
int y = area.y + kRows * theme::kLineHeight + 4;
|
|
c.drawFastHLine(4, y - 2, area.w - 8, theme::kMuted);
|
|
const std::string& token = settings_.getString(Setting::DebugToken);
|
|
std::string shown = debug::groupToken(token);
|
|
c.setFont(&fonts::body);
|
|
if (token.empty()) {
|
|
c.setTextColor(theme::kMuted);
|
|
c.drawString("No token yet: one is made when", 4, y + 4);
|
|
c.drawString("you switch the console on.", 4, y + 4 + theme::kLineHeight);
|
|
} else if (token.size() <= debug::kTokenChars) {
|
|
c.setTextColor(theme::kText);
|
|
c.setTextSize(2);
|
|
c.drawString(shown.substr(0, 14).c_str(), 4, y + 2);
|
|
if (shown.size() > 15) c.drawString(shown.substr(15).c_str(), 4, y + 2 + 2 * theme::kLineHeight - 3);
|
|
c.setTextSize(1);
|
|
} else {
|
|
c.setTextColor(theme::kText);
|
|
for (size_t at = 0; at < shown.size(); at += 35, y += theme::kLineHeight) c.drawString(shown.substr(at, 35).c_str(), 4, y + 2);
|
|
}
|
|
c.setFont(&fonts::small);
|
|
c.setTextColor(on ? theme::kWarning : theme::kMuted);
|
|
c.drawString(on ? "On this Wi-Fi, the token is full control." : "Off: nothing listens.", 4, area.y + area.h - 9);
|
|
|
|
if (confirm_) {
|
|
if (ask_ == Ask::SwitchOn)
|
|
widgets::dialog(c, "Switch it on?", "With the token, anyone on this Wi-Fi can read the console, press keys and copy files.", *confirm_);
|
|
else widgets::dialog(c, "A new token?", "The one in use stops working, and whoever is connected is cut off.", *confirm_);
|
|
}
|
|
}
|
|
|
|
} // namespace roro
|