Files
roro9stack/src/apps/debug_console_page.cpp
T
twislaandClaude Opus 5.5 7fe8b3d22a
CI / build (pull_request) Successful in 7m22s
Site / build (pull_request) Successful in 14s
Help: Fn+h lists the keys of the screen you're on, and no screen names its keys any more (#69)
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
2026-10-07 01:29:51 +02:00

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