Screenshots: Fn+p on every screen (#83)
CI / build (pull_request) Successful in 1m44s
Site / build (pull_request) Successful in 9s

Fn+p saves the screen as it is to /screenshots, as the Shell's `screenshot`
does, from anywhere: text fields, dialogs and the help panel included. The
key never reaches an App.

It refuses on Settings > Debug Console, which shows the token: a picture of
that page is a copy of the token in a file (App::showsSecret). `key shot`
presses it over the consoles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-07 21:39:16 +02:00
co-authored by Claude Opus 5.5
parent 1c9f90f92e
commit 4cdb4c342f
16 changed files with 93 additions and 10 deletions
+24 -4
View File
@@ -656,7 +656,7 @@ static const char* const kHelp =
"gnss status | gnss restart | gnss track start|stop | gnss nmea on|off | gnss send <sentence without $ and checksum>\n"
"crash the last crash: firmware, reason, task, backtrace\n"
"coredump erase forget the core dump in flash\n"
"key <name|char> press a key: up down left right select back home del tab space help, or one character; ctrl- alt- shift- before it (key ctrl-down)\n"
"key <name|char> press a key: up down left right select back home del tab space help shot, or one character; ctrl- alt- shift- before it (key ctrl-down)\n"
"wifi status | wifi add <ssid><TAB><password>\n"
"wifi ip <ssid> dhcp | wifi ip <ssid> <address>/<prefix> [gateway] a Saved Network's IP setting\n"
"wifi dns <a> [b] | wifi dns always on|off | wifi ntp <a> [b] DNS and NTP servers\n"
@@ -725,6 +725,26 @@ static void saveScreenshot() {
});
}
// Fn+p (issue #83): the screen as it is, dialog, help panel or Toast included. Not the page that
// shows the Debug Console's token: a picture of it is a copy of the token in a file.
static void screenshotKey() {
const char* why = nullptr;
if (apps->foreground().showsSecret()) why = "No screenshot here: it shows the token";
else if (!storageService || !storageService->state().present) why = "No SD card for the screenshot";
if (why) {
bus.publish(Event::withText(EventType::Notification, why, static_cast<int32_t>(NotificationLevel::Warning)));
return;
}
shotFrom = Console::Origin::System;
saveScreenshot();
}
// Every key goes through here, from the keyboard or from a console's `key`.
static void handleKey(const KeyEvent& e) {
if (e.key == Key::Screenshot) return screenshotKey();
apps->handleKey(e);
}
static void screenshotStep() {
if (shotPending && static_cast<int32_t>(millis() - shotDueMs) >= 0) {
shotPending = false;
@@ -1039,14 +1059,14 @@ static void runCommand(String line, bool fromSerial = false) {
}
Key key = k == "up" ? Key::Up : k == "down" ? Key::Down : k == "left" ? Key::Left
: k == "right" ? Key::Right : k == "back" ? Key::Back : k == "home" ? Key::Home
: k == "del" ? Key::Delete : k == "tab" ? Key::Tab : k == "help" ? Key::Help : Key::Select;
: k == "del" ? Key::Delete : k == "tab" ? Key::Tab : k == "help" ? Key::Help : k == "shot" ? Key::Screenshot : Key::Select;
KeyEvent ev = k == "space" ? KeyEvent::character(' ')
: k.length() == 1 && k[0] > ' ' ? KeyEvent::character((unsigned char)k[0])
: KeyEvent::of(key);
ev.ctrl = ctrl;
ev.alt = alt;
ev.shift = shift;
if (!power->onKey(millis())) apps->handleKey(ev);
if (!power->onKey(millis())) handleKey(ev);
}
if (line.startsWith("wifi add ")) { // wifi add <ssid>\t<password>: credentials never touch the repo
int tab = line.indexOf('\t');
@@ -1385,7 +1405,7 @@ static void loopPass() {
keyMapper.setTextEntry(apps->foreground().textEntryActive());
auto events = keyMapper.update(readKeys()); // always, so held keys are tracked
if (!swallow)
for (auto& e : events) apps->handleKey(e);
for (auto& e : events) handleKey(e);
}
// Issue #20: the GNSS receiver costs the LoRa radio 8 dB while it runs. If the user chose so,