Compare commits

..
2 Commits
Author SHA1 Message Date
twislaandClaude Opus 5.5 34e6714785 Keys: one table file for the device's help panel and the website's key tables (#72)
CI / build (pull_request) Successful in 7m14s
Site / build (pull_request) Successful in 8s
Every screen's keys are constant tables in lib/core/src/app_keys.h (52 of
them, each under an `// id: Title` comment). An App's help() picks the table
of the state it is in. site/tools/gen_dev_docs.py reads the same file and
writes site/data/keys.toml; the `keys` shortcode shows a screen's tables on
its guide page, and /guide/keys/ shows all of them.

The Site job fails when the data file is out of date or a page asks for a
table that doesn't exist, and now also runs when app_keys.h changes. A key
added to an App shows up on the website without anyone editing a page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-07 01:42:48 +02:00
twisla 82023d36b3 Merge pull request 'Help: Fn+h lists the keys of the screen you're on; no screen names its keys any more (#69)' (#73) from help-key into main
CI / build (push) Successful in 1m19s
Site / build (push) Successful in 16s
2026-10-06 23:38:49 +00:00
41 changed files with 1213 additions and 228 deletions
+4 -4
View File
@@ -3,15 +3,15 @@
# #
# It runs when the site, or a document the site is built from, changes (a pull request, or a push to # It runs when the site, or a document the site is built from, changes (a pull request, or a push to
# main); the firmware workflow (ci.yml) skips a change that touches only these files. A change that # main); the firmware workflow (ci.yml) skips a change that touches only these files. A change that
# touches both runs both. src/main.cpp is here too: the site's command reference is generated from the # touches both runs both. src/main.cpp and lib/core/src/app_keys.h are here too: the site's command
# firmware's own `help` text, and this job checks that it is still current. # reference and its key tables are generated from them, and this job checks that they are still current.
name: Site name: Site
on: on:
push: push:
branches: [main] branches: [main]
paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', '.gitea/workflows/site.yml'] paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', 'lib/core/src/app_keys.h', '.gitea/workflows/site.yml']
pull_request: pull_request:
paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', '.gitea/workflows/site.yml'] paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', 'lib/core/src/app_keys.h', '.gitea/workflows/site.yml']
jobs: jobs:
build: build:
+1 -1
View File
@@ -10,7 +10,7 @@ A multi-app firmware for the **M5Stack Cardputer ADV** with the **Cap LoRa-1262*
## On the device: one key ## On the device: one key
**Fn+h, on any screen, lists the keys that work there** (`?` does the same outside a text field). No screen names its keys itself (docs/milestones/U1.md): each App declares them for the state it is in, and the help panel shows them, followed by the ones that work everywhere. **Fn+h, on any screen, lists the keys that work there** (`?` does the same outside a text field). No screen names its keys itself (docs/milestones/U1.md). Every screen's keys are one table in `lib/core/src/app_keys.h`: the help panel shows the table of the state an App is in, and the website's key tables are generated from the same file.
## Requirements ## Requirements
+7 -1
View File
@@ -1,6 +1,6 @@
# U1 — Look and feel # U1 — Look and feel
**Status:** in progress. The help key (issue #69) is built and checked on the device, in a pull request. Screen recording (#17) and the rest of the milestone are not started. **Status:** in progress. The help key (issue #69) is merged; the website's key tables generated from the same lists (issue #72) are in a pull request. Screen recording (#17) and the rest of the milestone are not started.
**Goal:** the interface is consistent and uncrowded: the same thing is done the same way on every screen, and the 135 pixels of height go to content. **Goal:** the interface is consistent and uncrowded: the same thing is done the same way on every screen, and the 135 pixels of height go to content.
@@ -37,4 +37,10 @@ Every screen used to say something about its keys, differently: a footer of abbr
| On the device, `key help` and a screenshot | The Launcher and all nine Apps, and these states: Storage scrolled, System's tasks, a Settings text field, Wi-Fi Tools' networks. The panel is titled with the scope, lists the right keys, scrolls, and closes on Tab | | On the device, `key help` and a screenshot | The Launcher and all nine Apps, and these states: Storage scrolled, System's tasks, a Settings text field, Wi-Fi Tools' networks. The panel is titled with the scope, lists the right keys, scrolls, and closes on Tab |
| The one-time Toast | `notification: Fn+h: the keys of any screen` on the first start after the update | | The one-time Toast | `notification: Fn+h: the keys of any screen` on the first start after the update |
### One source for the device and the website (issue #72)
The lists first lived in each App's `help()`, as code. They are now **data, in one file**: `lib/core/src/app_keys.h`, 52 constant tables, each under a comment `// id: Title`. An App's `help()` picks the table of the state it is in. `site/tools/gen_dev_docs.py` reads the same file and writes `site/data/keys.toml`; the `keys` shortcode shows a screen's tables on its guide page, and `/guide/keys/` shows all of them. The Site job fails when the data file is out of date, or when a page asks for a table that doesn't exist, and it now runs when `app_keys.h` changes. A key added to an App shows up on the website without anyone editing a page.
Three rows lost their second wording on the way, since a table is constant: GNSS's `Tab` and `r`, and the Scanner's `c`, now say both things they do ("record a Track, or stop it") instead of the one that applies. The guide pages keep their written tables too, where they say more than a key list can; those can still drift, and the generated ones under them are the reference.
**Not checked:** the real Fn+h and `?` on the keyboard (the mapper is host-tested; the device was driven with `key help`); the Setup screens, which only a device that was never set up shows, so their new text has not been seen on a screen; and the states that need something to happen first (a dialog, a copy in progress, a Gemini prompt, a packet's details): their lists were read against the key handling, not looked at. **Not checked:** the real Fn+h and `?` on the keyboard (the mapper is host-tested; the device was driven with `key help`); the Setup screens, which only a device that was never set up shows, so their new text has not been seen on a screen; and the states that need something to happen first (a dialog, a copy in progress, a Gemini prompt, a packet's details): their lists were read against the key handling, not looked at.
+434
View File
@@ -0,0 +1,434 @@
#pragma once
#include <cstddef>
#include <vector>
#include "key_help.h"
// Every key of every screen, as data (issues #69 and #72). The help panel (Fn+h) shows the table of
// the state an App is in; site/tools/gen_dev_docs.py reads this file to write the website's key
// tables, so the two can't drift.
//
// The format is read by that script, so keep it: a comment `// id: Title`, then one table,
// inline constexpr KeyHelp kName[] = {
// {"keys", "what they do"},
// };
// with one row a line and plain string literals. `id` is what a guide page asks for.
namespace roro::keys {
template <size_t N>
inline void add(std::vector<KeyHelp>& out, const KeyHelp (&rows)[N]) {
out.insert(out.end(), rows, rows + N);
}
// everywhere: Everywhere
inline constexpr KeyHelp kEverywhere[] = {
{"`", "back"},
{"Fn `", "home, the Launcher"},
{"; . , /", "arrows (Fn+ while typing)"},
{"Fn h ?", "these keys (? not typing)"},
};
// dialog: A question
inline constexpr KeyHelp kDialog[] = {
{", /", "the other answer"},
{"Enter", "choose it"},
{"`", "cancel"},
};
// text: A text field
inline constexpr KeyHelp kText[] = {
{"Enter", "save"},
{"`", "cancel"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
{"opt ' e", "an accent: \xC3\xA9"},
};
// launcher: The Launcher
inline constexpr KeyHelp kLauncher[] = {
{"; .", "up, down"},
{"Enter", "open the App"},
};
// setup: Setup, a step
inline constexpr KeyHelp kSetup[] = {
{"Enter", "continue"},
{"`", "the step before"},
};
// setup-choice: Setup, a choice
inline constexpr KeyHelp kSetupChoice[] = {
{"; .", "up, down"},
{"Enter", "choose it, next step"},
{"`", "the step before"},
};
// setup-text: Setup, a name
inline constexpr KeyHelp kSetupText[] = {
{"Enter", "next step"},
{"`", "the step before"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
{"opt ' e", "an accent: \xC3\xA9"},
};
// irc: IRC
inline constexpr KeyHelp kIrc[] = {
{"Enter", "send the line"},
{"Tab", "the next buffer"},
{"Alt ; .", "scroll back, forward"},
{"Fn ; .", "lines you sent before"},
{"Fn , /", "move the cursor"},
{"Del", "delete backwards"},
{"/settings", "server, nick, passwords"},
{"/join #x", "join a channel"},
{"/part", "leave it"},
{"/msg nick", "a private chat"},
{"/me", "an action"},
{"/nick", "change your nick"},
{"/topic", "see or set the topic"},
{"/names", "who is there"},
{"/quit", "disconnect, and stay so"},
{"/raw", "a line as it is"},
{"`", "leave: IRC stays connected"},
};
// irc-settings: IRC settings
inline constexpr KeyHelp kIrcSettings[] = {
{"; .", "up, down"},
{"Enter", "edit, switch, or save"},
{"`", "leave without saving"},
};
// irc-field: IRC, a setting
inline constexpr KeyHelp kIrcField[] = {
{"Enter", "keep it"},
{"`", "cancel"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
};
// wifi-tools: Wi-Fi Tools
inline constexpr KeyHelp kWifiTools[] = {
{"; .", "up, down"},
{"Enter", "open"},
};
// wifi-networks: Networks nearby
inline constexpr KeyHelp kWifiNetworks[] = {
{"; .", "up, down"},
{"Enter", "track its signal"},
{"s", "sort: signal, channel, name"},
{"o", "open networks only"},
{"h", "hide the hidden ones"},
{"w", "strong ones only"},
{"l", "log the scans to the card"},
};
// wifi-tracker: Signal tracker
inline constexpr KeyHelp kWifiTracker[] = {
{"m", "clicks on or off"},
};
// gnss: GNSS
inline constexpr KeyHelp kGnss[] = {
{"Tab", "the position, or the sky"},
{"r", "record a Track, or stop it"},
};
// gemini: Gemini, a page
inline constexpr KeyHelp kGemini[] = {
{"Tab", "the next link"},
{"Aa Tab", "the link before"},
{"Enter", "follow the link"},
{"` Del", "the page before"},
{"; .", "scroll"},
{"Space", "a page down"},
{", /", "sideways, in wide blocks"},
{"g", "type an address"},
{"b", "bookmark this page"},
{"s", "save the page to the card"},
{"S", "...with the pages it links to"},
};
// gemini-saved: Gemini, a Saved Page
inline constexpr KeyHelp kGeminiSaved[] = {
{"Tab", "the next link"},
{"Aa Tab", "the link before"},
{"Enter", "follow the link"},
{"` Del", "the page before"},
{"; .", "scroll"},
{"Space", "a page down"},
{", /", "sideways, in wide blocks"},
{"g", "type an address"},
{"b", "bookmark this page"},
{"r", "refresh this Saved Page"},
{"d", "delete this Saved Page"},
};
// gemini-address: Gemini, an address
inline constexpr KeyHelp kGeminiAddress[] = {
{"Enter", "go there"},
{"`", "cancel"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
};
// gemini-answer: Gemini, an answer to a page
inline constexpr KeyHelp kGeminiAnswer[] = {
{"Enter", "send it"},
{"`", "cancel"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
};
// lora: LoRa Scanner, the packets
inline constexpr KeyHelp kLora[] = {
{"; .", "up, down"},
{"Enter", "the packet's details"},
{"p", "pick a Meshtastic preset"},
{"c", "start a Capture, or stop it"},
{"Tab", "the Sweep"},
};
// lora-packet: LoRa Scanner, a packet
inline constexpr KeyHelp kLoraPacket[] = {
{"; .", "scroll"},
{"Enter", "back to the list"},
};
// lora-presets: LoRa Scanner, the presets
inline constexpr KeyHelp kLoraPresets[] = {
{"; .", "up, down"},
{"Enter", "listen with this preset"},
};
// lora-sweep: LoRa Scanner, the Sweep
inline constexpr KeyHelp kLoraSweep[] = {
{"Tab", "the Sniffer"},
};
// storage: Storage, a folder
inline constexpr KeyHelp kStorage[] = {
{"; .", "up, down"},
{"Enter", "open the folder or the file"},
{", /", "a page up, down"},
{"c x", "copy, cut"},
{"v", "paste here"},
{"r", "rename"},
{"d Del", "delete, after asking"},
{"n", "a new folder"},
{"i", "details: size, date, type"},
{"s", "sort: name, date, size"},
{"m", "Maintenance: clean-up, erase"},
{"`", "the folder above"},
};
// storage-details: Storage, an item's details
inline constexpr KeyHelp kStorageDetails[] = {
{"; .", "scroll"},
{"Enter", "back to the folder"},
};
// storage-name: Storage, a name
inline constexpr KeyHelp kStorageName[] = {
{"Enter", "rename it, or make the folder"},
{"`", "cancel"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
};
// storage-busy: Storage, while it copies or deletes
inline constexpr KeyHelp kStorageBusy[] = {
{"`", "stop the copy or the delete"},
};
// maintenance: Storage, Maintenance
inline constexpr KeyHelp kMaintenance[] = {
{"; .", "up, down"},
{"Enter", "open, or choose"},
};
// viewer-text: A file, as text
inline constexpr KeyHelp kViewerText[] = {
{"; .", "a line up, down"},
{", /", "a page up, down"},
{"t b", "the top, the end"},
{"e", "edit it (up to 16 KB)"},
{"Tab", "the file as hex, or back"},
};
// viewer-hex: A file, as hex
inline constexpr KeyHelp kViewerHex[] = {
{"; .", "a line up, down"},
{", /", "a page up, down"},
{"t b", "the top, the end"},
{"Tab", "the file as text, or back"},
};
// viewer-pcap: A Capture
inline constexpr KeyHelp kViewerPcap[] = {
{"; .", "up, down"},
{"Enter", "the packet"},
{", /", "a page up, down"},
{"Tab", "the file as hex"},
};
// viewer-packet: A Capture's packet
inline constexpr KeyHelp kViewerPacket[] = {
{"; .", "scroll"},
{"Enter", "back to the packets"},
};
// viewer-gpx: A Track
inline constexpr KeyHelp kViewerGpx[] = {
{"Tab", "the file as text"},
};
// viewer-ota: An Update File
inline constexpr KeyHelp kViewerOta[] = {
{"Enter", "install it, if it's genuine"},
{"Tab", "the file as hex"},
};
// notes: Notes, the list
inline constexpr KeyHelp kNotes[] = {
{"; .", "up, down"},
{"Enter", "open the note"},
{", /", "a page up, down"},
{"n", "a new note"},
{"r", "rename its file"},
{"d Del", "delete it"},
{"s", "sort: newest, or by name"},
};
// notes-editor: Notes, the editor
inline constexpr KeyHelp kNotesEditor[] = {
{"Enter", "a new line"},
{"Del", "delete backwards"},
{"Tab", "two spaces"},
{"Fn ; . , /", "move the cursor"},
{"Alt Fn ; .", "a page up, down"},
{"Ctrl a e", "start, end of the line"},
{"opt ' e", "an accent: \xC3\xA9"},
{"`", "done: it saves by itself"},
};
// notes-name: Notes, a file name
inline constexpr KeyHelp kNotesName[] = {
{"Enter", "rename the file"},
{"`", "cancel"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
};
// system: System, any view
inline constexpr KeyHelp kSystem[] = {
{"Tab", "the next view"},
{"Aa Tab", "the view before"},
};
// system-tasks: System, the tasks
inline constexpr KeyHelp kSystemTasks[] = {
{"Tab", "the next view"},
{"Aa Tab", "the view before"},
{"; .", "scroll"},
{"s", "sort: cpu, stack, name"},
};
// system-system: System, the system view
inline constexpr KeyHelp kSystemSystem[] = {
{"Tab", "the next view"},
{"Aa Tab", "the view before"},
{"; .", "scroll"},
};
// settings: Settings
inline constexpr KeyHelp kSettings[] = {
{"; .", "up, down"},
{"Enter", "edit, or open the page"},
{", /", "change a switch or a slider"},
};
// settings-choice: Settings, a choice
inline constexpr KeyHelp kSettingsChoice[] = {
{"; .", "up, down"},
{"Enter", "choose it"},
};
// wifi: Settings, Wi-Fi
inline constexpr KeyHelp kWifi[] = {
{"; .", "up, down"},
{"Enter", "open, or change"},
{", /", "switch Wi-Fi on or off"},
};
// wifi-servers: Settings, DNS and NTP
inline constexpr KeyHelp kWifiServers[] = {
{"; .", "up, down"},
{"Enter", "edit"},
{", /", "Always use my DNS: on, off"},
};
// wifi-network: Settings, a saved network
inline constexpr KeyHelp kWifiNetwork[] = {
{"; .", "up, down"},
{"Enter", "edit, or forget"},
{", /", "Automatic or Fixed"},
};
// wifi-status: Settings, the Wi-Fi status
inline constexpr KeyHelp kWifiStatus[] = {
{"Enter", "back"},
};
// wifi-scan: Settings, adding a network
inline constexpr KeyHelp kWifiScan[] = {
{"; .", "up, down"},
{"Enter", "choose this network"},
};
// wifi-name: Settings, a hidden network's name
inline constexpr KeyHelp kWifiName[] = {
{"Enter", "next: the password"},
{"`", "cancel"},
{"Del", "delete backwards"},
{"Fn , /", "move the cursor"},
};
// firmware: Settings, Firmware
inline constexpr KeyHelp kFirmware[] = {
{"; .", "up, down"},
{"Enter", "check, open, or install"},
{"c", "look for a newer release"},
};
// firmware-release: Settings, a release
inline constexpr KeyHelp kFirmwareRelease[] = {
{"; .", "scroll"},
{"Enter", "install it"},
{"c", "check again"},
};
// firmware-older: Settings, older releases
inline constexpr KeyHelp kFirmwareOlder[] = {
{"; .", "up, down"},
{"Enter", "its details"},
{"c", "read the list again"},
};
// debug-console: Settings, Debug Console
inline constexpr KeyHelp kDebugConsole[] = {
{"; .", "up, down"},
{"Enter", "switch, or open"},
{", /", "switch the console on or off"},
};
// demo: The widget demo
inline constexpr KeyHelp kDemo[] = {
{"; .", "up, down"},
{"Enter", "try the widget"},
};
} // namespace roro::keys
+4 -1
View File
@@ -1,5 +1,7 @@
#include "app_manager.h" #include "app_manager.h"
#include "app_keys.h"
#include <cstring> #include <cstring>
namespace roro { namespace roro {
@@ -60,7 +62,8 @@ void AppManager::handleKey(const KeyEvent& event) {
if (event.key == Key::Help) { if (event.key == Key::Help) {
std::vector<KeyHelp> rows; std::vector<KeyHelp> rows;
foreground_->help(rows); foreground_->help(rows);
help::everywhere(rows); rows.push_back({"Everywhere", nullptr});
keys::add(rows, keys::kEverywhere);
const char* scope = foreground_->helpTitle(); const char* scope = foreground_->helpTitle();
const char* app = foregroundTitle(); const char* app = foregroundTitle();
help_.open(scope ? scope : app ? app : "Launcher", std::move(rows)); help_.open(scope ? scope : app ? app : "Launcher", std::move(rows));
-29
View File
@@ -16,35 +16,6 @@ struct KeyHelp {
const char* action; const char* action;
}; };
namespace help {
// Lists that several screens share.
inline void list(std::vector<KeyHelp>& out, const char* enter = "open") {
out.push_back({"; .", "up, down"});
out.push_back({"Enter", enter});
}
inline void dialog(std::vector<KeyHelp>& out) {
out.push_back({", /", "the other answer"});
out.push_back({"Enter", "choose it"});
out.push_back({"`", "cancel"});
}
inline void textEntry(std::vector<KeyHelp>& out, const char* enter = "save") {
out.push_back({"Enter", enter});
out.push_back({"`", "cancel"});
out.push_back({"Del", "delete backwards"});
out.push_back({"Fn , /", "move the cursor"});
out.push_back({"opt ' e", "an accent: \xC3\xA9"});
}
// What works on every screen: the end of every panel.
inline void everywhere(std::vector<KeyHelp>& out) {
out.push_back({"Everywhere", nullptr});
out.push_back({"`", "back"});
out.push_back({"Fn `", "home, the Launcher"});
out.push_back({"; . , /", "arrows (Fn+ while typing)"});
out.push_back({"Fn h ?", "these keys (? not typing)"});
}
} // namespace help
// The panel itself: what it lists, and how far it's scrolled. The keys it takes while open are the // The panel itself: what it lists, and how far it's scrolled. The keys it takes while open are the
// arrows; any other key closes it, and none reaches the App. // arrows; any other key closes it, and none reaches the App.
+7 -1
View File
@@ -8,7 +8,7 @@ docs = true
source = "docs/milestones/U1.md" source = "docs/milestones/U1.md"
tag = "U1" tag = "U1"
+++ +++
**Status:** in progress. The help key (issue #69) is built and checked on the device, in a pull request. Screen recording (#17) and the rest of the milestone are not started. **Status:** in progress. The help key (issue #69) is merged; the website's key tables generated from the same lists (issue #72) are in a pull request. Screen recording (#17) and the rest of the milestone are not started.
**Goal:** the interface is consistent and uncrowded: the same thing is done the same way on every screen, and the 135 pixels of height go to content. **Goal:** the interface is consistent and uncrowded: the same thing is done the same way on every screen, and the 135 pixels of height go to content.
@@ -45,4 +45,10 @@ Every screen used to say something about its keys, differently: a footer of abbr
| On the device, `key help` and a screenshot | The Launcher and all nine Apps, and these states: Storage scrolled, System's tasks, a Settings text field, Wi-Fi Tools' networks. The panel is titled with the scope, lists the right keys, scrolls, and closes on Tab | | On the device, `key help` and a screenshot | The Launcher and all nine Apps, and these states: Storage scrolled, System's tasks, a Settings text field, Wi-Fi Tools' networks. The panel is titled with the scope, lists the right keys, scrolls, and closes on Tab |
| The one-time Toast | `notification: Fn+h: the keys of any screen` on the first start after the update | | The one-time Toast | `notification: Fn+h: the keys of any screen` on the first start after the update |
### One source for the device and the website (issue #72)
The lists first lived in each App's `help()`, as code. They are now **data, in one file**: `lib/core/src/app_keys.h`, 52 constant tables, each under a comment `// id: Title`. An App's `help()` picks the table of the state it is in. `site/tools/gen_dev_docs.py` reads the same file and writes `site/data/keys.toml`; the `keys` shortcode shows a screen's tables on its guide page, and `/guide/keys/` shows all of them. The Site job fails when the data file is out of date, or when a page asks for a table that doesn't exist, and it now runs when `app_keys.h` changes. A key added to an App shows up on the website without anyone editing a page.
Three rows lost their second wording on the way, since a table is constant: GNSS's `Tab` and `r`, and the Scanner's `c`, now say both things they do ("record a Track, or stop it") instead of the one that applies. The guide pages keep their written tables too, where they say more than a key list can; those can still drift, and the generated ones under them are the reference.
**Not checked:** the real Fn+h and `?` on the keyboard (the mapper is host-tested; the device was driven with `key help`); the Setup screens, which only a device that was never set up shows, so their new text has not been seen on a screen; and the states that need something to happen first (a dialog, a copy in progress, a Gemini prompt, a packet's details): their lists were read against the key handling, not looked at. **Not checked:** the real Fn+h and `?` on the keyboard (the mapper is host-tested; the device was driven with `key help`); the Setup screens, which only a device that was never set up shows, so their new text has not been seen on a screen; and the states that need something to happen first (a dialog, a copy in progress, a Gemini prompt, a packet's details): their lists were read against the key handling, not looked at.
+6
View File
@@ -71,3 +71,9 @@ On a new device a short Setup asks four things, then never appears again. It als
Put a microSD card in the Cardputer. Without one the radio, GNSS, Wi-Fi and IRC still work, but nothing can be saved: no notes, IRC logs, Wi-Fi scan logs, GPX tracks, LoRa captures or saved Gemini pages. The [Storage App](/guide/storage/) shows what is on the card. Put a microSD card in the Cardputer. Without one the radio, GNSS, Wi-Fi and IRC still work, but nothing can be saved: no notes, IRC logs, Wi-Fi scan logs, GPX tracks, LoRa captures or saved Gemini pages. The [Storage App](/guide/storage/) shows what is on the card.
The firmware keeps its own folders at the top of the card (`captures`, `gemini`, `gnss`, `irc`, `updates`, `wifi`, plus `notes`). You can use the card in a computer too, but those names are the firmware's. The firmware keeps its own folders at the top of the card (`captures`, `gemini`, `gnss`, `irc`, `updates`, `wifi`, plus `notes`). You can use the card in a computer too, but those names are the firmware's.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["launcher", "everywhere", "dialog", "text"]) }}
+6
View File
@@ -40,3 +40,9 @@ Most capsules sign their own certificate. The first certificate seen for a host
## Big pages and memory ## Big pages and memory
With a card, every page streams to the card first, so a page larger than the device's memory still arrives whole and is read from the card as you scroll. Without a card, a page is limited to what fits in memory. If there is not enough free memory to open a secure connection, the fetch says so instead of failing silently; stopping IRC frees the most. With a card, every page streams to the card first, so a page larger than the device's memory still arrives whole and is read from the card as you scroll. Without a card, a page is limited to what fits in memory. If there is not enough free memory to open a secure connection, the fetch says so instead of failing silently; stopping IRC frees the most.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["gemini", "gemini-saved", "gemini-address", "gemini-answer"]) }}
+6
View File
@@ -28,3 +28,9 @@ Once there is a fix, the device's clock follows it.
## Tracks ## Tracks
<kbd>r</kbd> starts recording a **Track**: the route is saved as a **GPX** file on the SD card, in `/gnss/tracks` (named by date and time), and the Status Bar shows `REC`. Press <kbd>r</kbd> again to stop. A Track keeps recording with the App closed. It needs a card and a clock (a fix or Wi-Fi sets it); if it cannot start, the App says why: `GNSS is off`, `No SD card` or `Waiting for the time`. The [Storage App](/guide/storage/) opens a `.gpx` file and shows its points, start, duration and distance. <kbd>r</kbd> starts recording a **Track**: the route is saved as a **GPX** file on the SD card, in `/gnss/tracks` (named by date and time), and the Status Bar shows `REC`. Press <kbd>r</kbd> again to stop. A Track keeps recording with the App closed. It needs a card and a clock (a fix or Wi-Fi sets it); if it cannot start, the App says why: `GNSS is off`, `No SD card` or `Waiting for the time`. The [Storage App](/guide/storage/) opens a `.gpx` file and shows its points, start, duration and distance.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["gnss"]) }}
+6
View File
@@ -49,3 +49,9 @@ Every buffer is logged to the SD card, one file per day, under `/irc`. Logs stop
- IRC **pauses** while Wi-Fi is monitoring (the Status Bar shows `MON`) and while the device **installs an update**. It reconnects and rejoins its channels afterwards; the App says `paused` in the meantime. - IRC **pauses** while Wi-Fi is monitoring (the Status Bar shows `MON`) and while the device **installs an update**. It reconnects and rejoins its channels afterwards; the App says `paused` in the meantime.
- A secure connection costs memory: IRC's takes about 40 KB of the 107 KB the device has, and an update's download needs about 52 KB more. That is why IRC steps aside for an update, and why the daily update check waits for IRC to be disconnected (see [Updates](/guide/updates/)). - A secure connection costs memory: IRC's takes about 40 KB of the 107 KB the device has, and an update's download needs about 52 KB more. That is why IRC steps aside for an update, and why the daily update check waits for IRC to be disconnected (see [Updates](/guide/updates/)).
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["irc", "irc-settings", "irc-field"]) }}
+13
View File
@@ -0,0 +1,13 @@
+++
title = "Every key"
description = "The keys of every screen of the firmware, as the help panel lists them on the device: one table for each screen and state."
weight = 12
[extra]
tag = "Reference"
+++
On the device, <kbd>Fn</kbd> + <kbd>h</kbd> lists the keys of the screen you are on (and <kbd>?</kbd> does, when you are not typing). This page is all of those lists at once, generated from the same source the firmware reads: `lib/core/src/app_keys.h`.
In the tables, `; . , /` are the arrow keys (up, down, left, right): alone when you are not typing, with <kbd>Fn</kbd> when you are. `` ` `` is Back, `Aa` is Shift, and two keys separated by spaces are two keys that do the two things listed.
{{ keys(all=true) }}
+6
View File
@@ -33,3 +33,9 @@ A capture records packets into a **pcap** file with LoRaTap headers in `/capture
## The GNSS receiver raises the noise ## The GNSS receiver raises the noise
The GNSS receiver on the same Cap makes the radio's noise floor about **8 dB** worse while it runs. **Settings → Pause GNSS for LoRa** (off by default) puts the receiver on standby while the radio listens, except while a Track is being recorded. The GNSS receiver on the same Cap makes the radio's noise floor about **8 dB** worse while it runs. **Settings → Pause GNSS for LoRa** (off by default) puts the receiver on standby while the radio listens, except while a Track is being recorded.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["lora", "lora-packet", "lora-presets", "lora-sweep"]) }}
+6
View File
@@ -35,3 +35,9 @@ A new note has no file until you type something. The file is then named after it
## Limits ## Limits
A note holds up to **16 KB** while it is edited. A bigger text file opens read-only in the [Storage App](/guide/storage/); editing a file of any size is planned. In Storage, <kbd>e</kbd> on a text file opens it in the same editor, anywhere on the card, unless the file is read-only. Notes are never offered for deletion by the clean-up. A note holds up to **16 KB** while it is edited. A bigger text file opens read-only in the [Storage App](/guide/storage/); editing a file of any size is planned. In Storage, <kbd>e</kbd> on a text file opens it in the same editor, anywhere on the card, unless the file is read-only. Notes are never offered for deletion by the clean-up.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["notes", "notes-editor", "notes-name"]) }}
+6
View File
@@ -41,3 +41,9 @@ A network normally gives the device its address by itself (DHCP, **Automatic**).
### DNS and NTP ### DNS and NTP
Two DNS servers (9.9.9.9 and 1.1.1.1 by default), used on Fixed networks, or on every network if **Always use my DNS** is on; and two NTP servers (pool.ntp.org and time.cloudflare.com), used after any that the network's DHCP offers. <kbd>Enter</kbd> on **Status** shows what is in use and where each value came from. Two DNS servers (9.9.9.9 and 1.1.1.1 by default), used on Fixed networks, or on every network if **Always use my DNS** is on; and two NTP servers (pool.ntp.org and time.cloudflare.com), used after any that the network's DHCP offers. <kbd>Enter</kbd> on **Status** shows what is in use and where each value came from.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["settings", "settings-choice", "wifi", "wifi-servers", "wifi-network", "wifi-status", "wifi-scan", "wifi-name", "debug-console"]) }}
+6
View File
@@ -45,3 +45,9 @@ The App says why when it refuses.
At the top of the card, the last row, **Maintenance** (or <kbd>m</kbd>), shows the card's usage and holds **Storage clean-up** and **Erase SD card**. They delete for good, so they sit behind a warning. Clean-up deletes old logs and captures by category and age, showing the space it would free first. Notes and Saved Pages are never offered. At the top of the card, the last row, **Maintenance** (or <kbd>m</kbd>), shows the card's usage and holds **Storage clean-up** and **Erase SD card**. They delete for good, so they sit behind a warning. Clean-up deletes old logs and captures by category and age, showing the space it would free first. Notes and Saved Pages are never offered.
The firmware warns once per start when the card passes **80%** full; past **90%**, logs stop being written so that the rest is kept for captures. The firmware warns once per start when the card passes **80%** full; past **90%**, logs stop being written so that the rest is kept for captures.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["storage", "storage-details", "storage-name", "storage-busy", "maintenance", "viewer-text", "viewer-hex", "viewer-pcap", "viewer-packet", "viewer-gpx", "viewer-ota"]) }}
+6
View File
@@ -18,3 +18,9 @@ System changes nothing: it shows. It samples once a second and keeps its history
## Why it exists ## Why it exists
The device has about 107 KB of free memory and no PSRAM, so memory is the resource that decides what can run together: a secure connection takes about 52 KB at its peak. System makes that visible, and is how the project measures its own changes. The device has about 107 KB of free memory and no PSRAM, so memory is the resource that decides what can run together: a secure connection takes about 52 KB at its peak. System makes that visible, and is how the project measures its own changes.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["system", "system-tasks", "system-system"]) }}
+6
View File
@@ -42,3 +42,9 @@ The connection to the project's server is checked against the two root certifica
## For developers ## For developers
Updates can also be pushed from a PC over Wi-Fi, with `scripts/flash.sh --ota <ip>`, or put on the card with `scripts/sd_put.sh`: see the [README](https://git.twis.la/twisla/roro9stack#firmware-updates-over-wi-fi-ota). The firmware's console can be reached over Wi-Fi too: see [The Debug Console](/dev/debug/). Updates can also be pushed from a PC over Wi-Fi, with `scripts/flash.sh --ota <ip>`, or put on the card with `scripts/sd_put.sh`: see the [README](https://git.twis.la/twisla/roro9stack#firmware-updates-over-wi-fi-ota). The firmware's console can be reached over Wi-Fi too: see [The Debug Console](/dev/debug/).
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["firmware", "firmware-release", "firmware-older"]) }}
+6
View File
@@ -34,3 +34,9 @@ One bar for each of the 13 Wi-Fi channels shows how busy it is: how many network
## Signal tracker ## Signal tracker
Follows one network: its name, address and channel, then the signal in dBm in large type, a strength bar, and a graph of the recent readings, so you can walk towards the strongest signal. <kbd>m</kbd> turns audible clicks on and off. If the network is no longer heard the screen says `lost`. <kbd>`</kbd> (Back) returns to the list. Follows one network: its name, address and channel, then the signal in dBm in large type, a strength bar, and a graph of the recent readings, so you can walk towards the strongest signal. <kbd>m</kbd> turns audible clicks on and off. If the network is no longer heard the screen says `lost`. <kbd>`</kbd> (Back) returns to the list.
## The keys, as the device lists them
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["wifi-tools", "wifi-networks", "wifi-tracker"]) }}
+516
View File
@@ -0,0 +1,516 @@
# Generated by site/tools/gen_dev_docs.py from lib/core/src/app_keys.h: the keys of every screen, as the
# help panel (Fn+h) lists them on the device. Edit that header, not this file.
[[scope]]
id = "everywhere"
title = "Everywhere"
rows = [
["`", "back"],
["Fn `", "home, the Launcher"],
["; . , /", "arrows (Fn+ while typing)"],
["Fn h ?", "these keys (? not typing)"],
]
[[scope]]
id = "dialog"
title = "A question"
rows = [
[", /", "the other answer"],
["Enter", "choose it"],
["`", "cancel"],
]
[[scope]]
id = "text"
title = "A text field"
rows = [
["Enter", "save"],
["`", "cancel"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
["opt ' e", "an accent: é"],
]
[[scope]]
id = "launcher"
title = "The Launcher"
rows = [
["; .", "up, down"],
["Enter", "open the App"],
]
[[scope]]
id = "setup"
title = "Setup, a step"
rows = [
["Enter", "continue"],
["`", "the step before"],
]
[[scope]]
id = "setup-choice"
title = "Setup, a choice"
rows = [
["; .", "up, down"],
["Enter", "choose it, next step"],
["`", "the step before"],
]
[[scope]]
id = "setup-text"
title = "Setup, a name"
rows = [
["Enter", "next step"],
["`", "the step before"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
["opt ' e", "an accent: é"],
]
[[scope]]
id = "irc"
title = "IRC"
rows = [
["Enter", "send the line"],
["Tab", "the next buffer"],
["Alt ; .", "scroll back, forward"],
["Fn ; .", "lines you sent before"],
["Fn , /", "move the cursor"],
["Del", "delete backwards"],
["/settings", "server, nick, passwords"],
["/join #x", "join a channel"],
["/part", "leave it"],
["/msg nick", "a private chat"],
["/me", "an action"],
["/nick", "change your nick"],
["/topic", "see or set the topic"],
["/names", "who is there"],
["/quit", "disconnect, and stay so"],
["/raw", "a line as it is"],
["`", "leave: IRC stays connected"],
]
[[scope]]
id = "irc-settings"
title = "IRC settings"
rows = [
["; .", "up, down"],
["Enter", "edit, switch, or save"],
["`", "leave without saving"],
]
[[scope]]
id = "irc-field"
title = "IRC, a setting"
rows = [
["Enter", "keep it"],
["`", "cancel"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
]
[[scope]]
id = "wifi-tools"
title = "Wi-Fi Tools"
rows = [
["; .", "up, down"],
["Enter", "open"],
]
[[scope]]
id = "wifi-networks"
title = "Networks nearby"
rows = [
["; .", "up, down"],
["Enter", "track its signal"],
["s", "sort: signal, channel, name"],
["o", "open networks only"],
["h", "hide the hidden ones"],
["w", "strong ones only"],
["l", "log the scans to the card"],
]
[[scope]]
id = "wifi-tracker"
title = "Signal tracker"
rows = [
["m", "clicks on or off"],
]
[[scope]]
id = "gnss"
title = "GNSS"
rows = [
["Tab", "the position, or the sky"],
["r", "record a Track, or stop it"],
]
[[scope]]
id = "gemini"
title = "Gemini, a page"
rows = [
["Tab", "the next link"],
["Aa Tab", "the link before"],
["Enter", "follow the link"],
["` Del", "the page before"],
["; .", "scroll"],
["Space", "a page down"],
[", /", "sideways, in wide blocks"],
["g", "type an address"],
["b", "bookmark this page"],
["s", "save the page to the card"],
["S", "...with the pages it links to"],
]
[[scope]]
id = "gemini-saved"
title = "Gemini, a Saved Page"
rows = [
["Tab", "the next link"],
["Aa Tab", "the link before"],
["Enter", "follow the link"],
["` Del", "the page before"],
["; .", "scroll"],
["Space", "a page down"],
[", /", "sideways, in wide blocks"],
["g", "type an address"],
["b", "bookmark this page"],
["r", "refresh this Saved Page"],
["d", "delete this Saved Page"],
]
[[scope]]
id = "gemini-address"
title = "Gemini, an address"
rows = [
["Enter", "go there"],
["`", "cancel"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
]
[[scope]]
id = "gemini-answer"
title = "Gemini, an answer to a page"
rows = [
["Enter", "send it"],
["`", "cancel"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
]
[[scope]]
id = "lora"
title = "LoRa Scanner, the packets"
rows = [
["; .", "up, down"],
["Enter", "the packet's details"],
["p", "pick a Meshtastic preset"],
["c", "start a Capture, or stop it"],
["Tab", "the Sweep"],
]
[[scope]]
id = "lora-packet"
title = "LoRa Scanner, a packet"
rows = [
["; .", "scroll"],
["Enter", "back to the list"],
]
[[scope]]
id = "lora-presets"
title = "LoRa Scanner, the presets"
rows = [
["; .", "up, down"],
["Enter", "listen with this preset"],
]
[[scope]]
id = "lora-sweep"
title = "LoRa Scanner, the Sweep"
rows = [
["Tab", "the Sniffer"],
]
[[scope]]
id = "storage"
title = "Storage, a folder"
rows = [
["; .", "up, down"],
["Enter", "open the folder or the file"],
[", /", "a page up, down"],
["c x", "copy, cut"],
["v", "paste here"],
["r", "rename"],
["d Del", "delete, after asking"],
["n", "a new folder"],
["i", "details: size, date, type"],
["s", "sort: name, date, size"],
["m", "Maintenance: clean-up, erase"],
["`", "the folder above"],
]
[[scope]]
id = "storage-details"
title = "Storage, an item's details"
rows = [
["; .", "scroll"],
["Enter", "back to the folder"],
]
[[scope]]
id = "storage-name"
title = "Storage, a name"
rows = [
["Enter", "rename it, or make the folder"],
["`", "cancel"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
]
[[scope]]
id = "storage-busy"
title = "Storage, while it copies or deletes"
rows = [
["`", "stop the copy or the delete"],
]
[[scope]]
id = "maintenance"
title = "Storage, Maintenance"
rows = [
["; .", "up, down"],
["Enter", "open, or choose"],
]
[[scope]]
id = "viewer-text"
title = "A file, as text"
rows = [
["; .", "a line up, down"],
[", /", "a page up, down"],
["t b", "the top, the end"],
["e", "edit it (up to 16 KB)"],
["Tab", "the file as hex, or back"],
]
[[scope]]
id = "viewer-hex"
title = "A file, as hex"
rows = [
["; .", "a line up, down"],
[", /", "a page up, down"],
["t b", "the top, the end"],
["Tab", "the file as text, or back"],
]
[[scope]]
id = "viewer-pcap"
title = "A Capture"
rows = [
["; .", "up, down"],
["Enter", "the packet"],
[", /", "a page up, down"],
["Tab", "the file as hex"],
]
[[scope]]
id = "viewer-packet"
title = "A Capture's packet"
rows = [
["; .", "scroll"],
["Enter", "back to the packets"],
]
[[scope]]
id = "viewer-gpx"
title = "A Track"
rows = [
["Tab", "the file as text"],
]
[[scope]]
id = "viewer-ota"
title = "An Update File"
rows = [
["Enter", "install it, if it's genuine"],
["Tab", "the file as hex"],
]
[[scope]]
id = "notes"
title = "Notes, the list"
rows = [
["; .", "up, down"],
["Enter", "open the note"],
[", /", "a page up, down"],
["n", "a new note"],
["r", "rename its file"],
["d Del", "delete it"],
["s", "sort: newest, or by name"],
]
[[scope]]
id = "notes-editor"
title = "Notes, the editor"
rows = [
["Enter", "a new line"],
["Del", "delete backwards"],
["Tab", "two spaces"],
["Fn ; . , /", "move the cursor"],
["Alt Fn ; .", "a page up, down"],
["Ctrl a e", "start, end of the line"],
["opt ' e", "an accent: é"],
["`", "done: it saves by itself"],
]
[[scope]]
id = "notes-name"
title = "Notes, a file name"
rows = [
["Enter", "rename the file"],
["`", "cancel"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
]
[[scope]]
id = "system"
title = "System, any view"
rows = [
["Tab", "the next view"],
["Aa Tab", "the view before"],
]
[[scope]]
id = "system-tasks"
title = "System, the tasks"
rows = [
["Tab", "the next view"],
["Aa Tab", "the view before"],
["; .", "scroll"],
["s", "sort: cpu, stack, name"],
]
[[scope]]
id = "system-system"
title = "System, the system view"
rows = [
["Tab", "the next view"],
["Aa Tab", "the view before"],
["; .", "scroll"],
]
[[scope]]
id = "settings"
title = "Settings"
rows = [
["; .", "up, down"],
["Enter", "edit, or open the page"],
[", /", "change a switch or a slider"],
]
[[scope]]
id = "settings-choice"
title = "Settings, a choice"
rows = [
["; .", "up, down"],
["Enter", "choose it"],
]
[[scope]]
id = "wifi"
title = "Settings, Wi-Fi"
rows = [
["; .", "up, down"],
["Enter", "open, or change"],
[", /", "switch Wi-Fi on or off"],
]
[[scope]]
id = "wifi-servers"
title = "Settings, DNS and NTP"
rows = [
["; .", "up, down"],
["Enter", "edit"],
[", /", "Always use my DNS: on, off"],
]
[[scope]]
id = "wifi-network"
title = "Settings, a saved network"
rows = [
["; .", "up, down"],
["Enter", "edit, or forget"],
[", /", "Automatic or Fixed"],
]
[[scope]]
id = "wifi-status"
title = "Settings, the Wi-Fi status"
rows = [
["Enter", "back"],
]
[[scope]]
id = "wifi-scan"
title = "Settings, adding a network"
rows = [
["; .", "up, down"],
["Enter", "choose this network"],
]
[[scope]]
id = "wifi-name"
title = "Settings, a hidden network's name"
rows = [
["Enter", "next: the password"],
["`", "cancel"],
["Del", "delete backwards"],
["Fn , /", "move the cursor"],
]
[[scope]]
id = "firmware"
title = "Settings, Firmware"
rows = [
["; .", "up, down"],
["Enter", "check, open, or install"],
["c", "look for a newer release"],
]
[[scope]]
id = "firmware-release"
title = "Settings, a release"
rows = [
["; .", "scroll"],
["Enter", "install it"],
["c", "check again"],
]
[[scope]]
id = "firmware-older"
title = "Settings, older releases"
rows = [
["; .", "up, down"],
["Enter", "its details"],
["c", "read the list again"],
]
[[scope]]
id = "debug-console"
title = "Settings, Debug Console"
rows = [
["; .", "up, down"],
["Enter", "switch, or open"],
[", /", "switch the console on or off"],
]
[[scope]]
id = "demo"
title = "The widget demo"
rows = [
["; .", "up, down"],
["Enter", "try the widget"],
]
+9
View File
@@ -254,3 +254,12 @@ footer small { display: block; max-width: 760px; }
.source { max-width: 820px; font-size: 14px; line-height: 20px; } .source { max-width: 820px; font-size: 14px; line-height: 20px; }
.source code { background: var(--s2); padding: 0 6px; } .source code { background: var(--s2); padding: 0 6px; }
.card.featured { flex-basis: 100%; } .card.featured { flex-basis: 100%; }
/* The keys of a screen, as the device's help panel lists them (the `keys` shortcode) */
.keys { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px 32px; }
.keys-scope { background: var(--s2); padding: 16px 20px; }
.keys-scope h4 { font: 500 12px/16px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--cyantext); margin: 0 0 8px; }
.prose .keys table { width: 100%; }
.prose .keys td { padding: 4px 8px 4px 0; border-bottom: 0; font-size: 15px; }
.prose .keys td:first-child { white-space: nowrap; width: 1%; padding-right: 16px; }
.prose .keys kbd { white-space: pre; }
+18
View File
@@ -0,0 +1,18 @@
{#- The keys of one or more screens, as the help panel (Fn+h) lists them on the device:
{{ keys(scopes=["storage", "storage-details"]) }}, or {{ keys(all=true) }} for every screen.
The tables come from site/data/keys.toml, generated from lib/core/src/app_keys.h (issue #72):
a key added to an App shows up here without anyone editing a page. -#}
{%- set data = load_data(path="data/keys.toml", format="toml") -%}
{%- set everything = all is defined and all -%}
<div class="keys">
{%- for s in data.scope %}{% if everything or (scopes is defined and s.id in scopes) %}
<div class="keys-scope">
<h4 id="keys-{{ s.id }}">{{ s.title }}</h4>
<table>
{%- for r in s.rows %}
<tr><td><kbd>{{ r.0 }}</kbd></td><td>{{ r.1 }}</td></tr>
{%- endfor %}
</table>
</div>
{%- endif %}{% endfor %}
</div>
+52 -4
View File
@@ -9,6 +9,8 @@ Zola cannot read a file outside its own folder, so the pages are generated and c
milestones/ one page for each docs/milestones/*.md milestones/ one page for each docs/milestones/*.md
build/build-and-test, build/flash sections of README.md build/build-and-test, build/flash sections of README.md
debug/commands the commands the firmware's `help` prints (parsed from src/main.cpp), then README's table of them debug/commands the commands the firmware's `help` prints (parsed from src/main.cpp), then README's table of them
site/data/keys.toml every screen's keys, from lib/core/src/app_keys.h: what the help panel (Fn+h) shows on the
device, for the `keys` shortcode of the user guide (issue #72)
Every generated page says where it comes from; edit that file, not the page. Hand-written pages sit next to them. Every generated page says where it comes from; edit that file, not the page. Hand-written pages sit next to them.
""" """
import json import json
@@ -139,8 +141,38 @@ def safe_mode_commands():
return exact, prefix return exact, prefix
def key_tables():
"""Every table of lib/core/src/app_keys.h: [(id, title, [(keys, action), ...])], in the file's order."""
src = (REPO / "lib" / "core" / "src" / "app_keys.h").read_text()
def text(literal): # a C string literal's contents: \xHH bytes are UTF-8
raw = re.sub(r"\\x([0-9A-Fa-f]{2})", lambda m: chr(int(m.group(1), 16)), literal).replace('\\"', '"')
return raw.encode("latin-1").decode("utf-8")
tables = []
for m in re.finditer(r"// ([a-z0-9-]+): ([^\n]+)\ninline constexpr KeyHelp k\w+\[\] = \{\n(.*?)\n\};", src, re.S):
rows = [(text(k), text(a)) for k, a in re.findall(r'\{"((?:[^"\\]|\\.)*)", "((?:[^"\\]|\\.)*)"\},', m.group(3))]
if len(rows) != len([l for l in m.group(3).splitlines() if l.strip()]):
sys.exit(f"gen_dev_docs: a row of `{m.group(1)}` in app_keys.h isn't in the form {{\"keys\", \"action\"}},")
tables.append((m.group(1), m.group(2).strip(), rows))
declared = len(re.findall(r"^inline constexpr KeyHelp k\w+\[\]", src, re.M))
if len(tables) != declared or len({t[0] for t in tables}) != len(tables):
sys.exit(f"gen_dev_docs: app_keys.h has {declared} tables, {len(tables)} with an `// id: Title` comment and a unique id")
return tables
def keys_toml():
out = ["# Generated by site/tools/gen_dev_docs.py from lib/core/src/app_keys.h: the keys of every screen, as the",
"# help panel (Fn+h) lists them on the device. Edit that header, not this file.", ""]
for ident, title, rows in key_tables():
out += ["[[scope]]", f"id = {json.dumps(ident)}", f"title = {json.dumps(title, ensure_ascii=False)}", "rows = ["]
out += [f" [{json.dumps(k, ensure_ascii=False)}, {json.dumps(a, ensure_ascii=False)}]," for k, a in rows]
out += ["]", ""]
return "\n".join(out)
def build(): def build():
pages = {} pages = {"../../data/keys.toml": keys_toml()}
for path in sorted((REPO / "docs" / "adr").glob("*.md")): for path in sorted((REPO / "docs" / "adr").glob("*.md")):
title, body = title_and_body(path.read_text()) title, body = title_and_body(path.read_text())
@@ -179,9 +211,25 @@ def build():
return pages return pages
def unknown_scopes():
"""Scopes a page asks the `keys` shortcode for that app_keys.h doesn't have."""
known = {t[0] for t in key_tables()}
bad = []
for path in sorted((SITE / "content").rglob("*.md")):
for call in re.findall(r"keys\(scopes=\[([^\]]*)\]", path.read_text()):
for ident in re.findall(r'"([^"]+)"', call):
if ident not in known:
bad.append(f"{path.relative_to(SITE)}: no key table `{ident}` in lib/core/src/app_keys.h")
return bad
def main(): def main():
check = "--check" in sys.argv check = "--check" in sys.argv
pages = build() pages = build()
for problem in unknown_scopes():
print("gen_dev_docs:", problem)
if unknown_scopes():
sys.exit(1)
stale = [] stale = []
for rel, text in sorted(pages.items()): for rel, text in sorted(pages.items()):
path = OUT / rel path = OUT / rel
@@ -193,10 +241,10 @@ def main():
path.write_text(text) path.write_text(text)
if check: if check:
for rel in stale: for rel in stale:
print(f"gen_dev_docs: content/dev/{rel} is out of date: run site/tools/gen_dev_docs.py and commit the result") print(f"gen_dev_docs: {os.path.normpath(os.path.join('site/content/dev', rel))} is out of date: run site/tools/gen_dev_docs.py and commit the result")
print(f"gen_dev_docs: {len(pages)} pages, {len(stale)} out of date") print(f"gen_dev_docs: {len(pages)} files, {len(stale)} out of date")
sys.exit(1 if stale else 0) sys.exit(1 if stale else 0)
print(f"gen_dev_docs: {len(pages)} pages, {len(stale)} written") print(f"gen_dev_docs: {len(pages)} files, {len(stale)} written")
if __name__ == "__main__": if __name__ == "__main__":
+4 -4
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "apps/debug_console_page.h" #include "apps/debug_console_page.h"
#include "debug_auth.h" #include "debug_auth.h"
@@ -75,10 +76,9 @@ bool DebugConsolePage::onKey(const KeyEvent& e) {
} }
void DebugConsolePage::help(std::vector<KeyHelp>& out) const { void DebugConsolePage::help(std::vector<KeyHelp>& out) const {
if (confirm_) return help::dialog(out); if (confirm_) return keys::add(out, keys::kDialog);
if (typing_) return help::textEntry(out); if (typing_) return keys::add(out, keys::kText);
help::list(out, "switch, or open"); keys::add(out, keys::kDebugConsole);
out.push_back({", /", "switch the console on or off"});
} }
void DebugConsolePage::draw(Canvas& c) { void DebugConsolePage::draw(Canvas& c) {
+4 -6
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "demo_app.h" #include "demo_app.h"
#include "ui/widgets.h" #include "ui/widgets.h"
@@ -26,12 +27,9 @@ void DemoApp::notify(const char* text, NotificationLevel level) {
} }
void DemoApp::help(std::vector<KeyHelp>& out) const { void DemoApp::help(std::vector<KeyHelp>& out) const {
if (dialog_) return help::dialog(out); if (dialog_) return keys::add(out, keys::kDialog);
switch (page_) { if (page_ == Page::Editor) return keys::add(out, keys::kText);
case Page::Menu: help::list(out, "try the widget"); break; if (page_ == Page::Menu) keys::add(out, keys::kDemo);
case Page::Text: out.push_back({"; .", "scroll"}); break;
case Page::Editor: help::textEntry(out, "show the text as a Toast"); break;
}
} }
bool DemoApp::onKey(const KeyEvent& e) { bool DemoApp::onKey(const KeyEvent& e) {
+8 -19
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "file_viewer.h" #include "file_viewer.h"
#include <Arduino.h> #include <Arduino.h>
@@ -266,27 +267,15 @@ void FileViewer::openPacket(int index) {
} }
void FileViewer::help(std::vector<KeyHelp>& out) const { void FileViewer::help(std::vector<KeyHelp>& out) const {
if (confirm_) return help::dialog(out); if (confirm_) return keys::add(out, keys::kDialog);
switch (mode_) { switch (mode_) {
case Mode::Packet: case Mode::Text: keys::add(out, keys::kViewerText); break;
out.push_back({"; .", "scroll"}); case Mode::Hex: keys::add(out, keys::kViewerHex); break;
out.push_back({"Enter", "back to the packets"}); case Mode::Pcap: keys::add(out, keys::kViewerPcap); break;
return; case Mode::Packet: keys::add(out, keys::kViewerPacket); break;
case Mode::Text: case Mode::Gpx: keys::add(out, keys::kViewerGpx); break;
case Mode::Hex: case Mode::Ota: keys::add(out, keys::kViewerOta); break;
out.push_back({"; .", "a line up, down"});
out.push_back({", /", "a page up, down"});
out.push_back({"t b", "the top, the end"});
if (mode_ == Mode::Text) out.push_back({"e", "edit it (up to 16 KB)"});
break;
case Mode::Pcap:
help::list(out, "the packet");
out.push_back({", /", "a page up, down"});
break;
case Mode::Ota: out.push_back({"Enter", "install it, if it's genuine"}); break;
case Mode::Gpx: break;
} }
out.push_back({"Tab", mode_ != base_ ? "back to the file's own view" : base_ == Mode::Hex || base_ == Mode::Gpx ? "the file as text" : "the file as hex"});
} }
bool FileViewer::onKey(const KeyEvent& e) { bool FileViewer::onKey(const KeyEvent& e) {
+5 -14
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "firmware_page.h" #include "firmware_page.h"
#include <SD.h> #include <SD.h>
@@ -235,21 +236,11 @@ void FirmwarePage::drawOlder(Canvas& c) {
} }
void FirmwarePage::help(std::vector<KeyHelp>& out) const { void FirmwarePage::help(std::vector<KeyHelp>& out) const {
if (confirm_) return help::dialog(out); if (confirm_) return keys::add(out, keys::kDialog);
switch (view_) { switch (view_) {
case View::Main: case View::Main: keys::add(out, keys::kFirmware); break;
help::list(out, "check, open, or install"); case View::Release: keys::add(out, keys::kFirmwareRelease); break;
out.push_back({"c", "look for a newer release"}); case View::Older: keys::add(out, keys::kFirmwareOlder); break;
break;
case View::Release:
out.push_back({"; .", "scroll"});
out.push_back({"Enter", "install it"});
out.push_back({"c", "check again"});
break;
case View::Older:
help::list(out, "its details");
out.push_back({"c", "read the list again"});
break;
} }
} }
+6 -19
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "gemini_app.h" #include "gemini_app.h"
#include <Arduino.h> #include <Arduino.h>
@@ -303,25 +304,11 @@ void GeminiApp::selectLink(int direction) {
} }
void GeminiApp::help(std::vector<KeyHelp>& out) const { void GeminiApp::help(std::vector<KeyHelp>& out) const {
if (certDialog_ || deleteDialog_) return help::dialog(out); if (certDialog_ || deleteDialog_) return keys::add(out, keys::kDialog);
if (inputOpen_) return help::textEntry(out, "send it"); if (inputOpen_) return keys::add(out, keys::kGeminiAnswer);
if (addressOpen_) return help::textEntry(out, "go there"); if (addressOpen_) return keys::add(out, keys::kGeminiAddress);
out.push_back({"Tab", "the next link"}); if (isSaved()) keys::add(out, keys::kGeminiSaved);
out.push_back({"Aa Tab", "the link before"}); else keys::add(out, keys::kGemini);
out.push_back({"Enter", "follow the link"});
out.push_back({"` Del", "the page before"});
out.push_back({"; .", "scroll"});
out.push_back({"Space", "a page down"});
out.push_back({", /", "sideways, in wide blocks"});
out.push_back({"g", "type an address"});
out.push_back({"b", "bookmark this page"});
if (isSaved()) {
out.push_back({"r", "refresh this Saved Page"});
out.push_back({"d", "delete this Saved Page"});
} else {
out.push_back({"s", "save the page to the card"});
out.push_back({"S", "...with the pages it links to"});
}
} }
const char* GeminiApp::helpTitle() const { const char* GeminiApp::helpTitle() const {
+2 -2
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "gnss_app.h" #include "gnss_app.h"
#include <Arduino.h> #include <Arduino.h>
@@ -83,8 +84,7 @@ void GnssApp::draw(Canvas& c) {
} }
void GnssApp::help(std::vector<KeyHelp>& out) const { void GnssApp::help(std::vector<KeyHelp>& out) const {
out.push_back({"Tab", sky_ ? "the position" : "the sky"}); keys::add(out, keys::kGnss);
out.push_back({"r", gnss_.tracking() ? "stop the Track" : "record a Track"});
} }
void GnssApp::drawPosition(Canvas& c) { void GnssApp::drawPosition(Canvas& c) {
+4 -23
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "irc_app.h" #include "irc_app.h"
#include "ui/fonts.h" #include "ui/fonts.h"
@@ -300,29 +301,9 @@ void IrcApp::drawChat(Canvas& c) {
} }
void IrcApp::help(std::vector<KeyHelp>& out) const { void IrcApp::help(std::vector<KeyHelp>& out) const {
if (page_ == Page::Settings) { if (page_ == Page::Chat) return keys::add(out, keys::kIrc);
if (editing_) return help::textEntry(out, "keep it"); if (editing_) return keys::add(out, keys::kIrcField);
help::list(out, "edit, switch, or save"); keys::add(out, keys::kIrcSettings);
out.push_back({"`", "leave without saving"});
return;
}
out.push_back({"Enter", "send the line"});
out.push_back({"Tab", "the next buffer"});
out.push_back({"Alt ; .", "scroll back, forward"});
out.push_back({"Fn ; .", "lines you sent before"});
out.push_back({"Fn , /", "move the cursor"});
out.push_back({"Del", "delete backwards"});
out.push_back({"/settings", "server, nick, passwords"});
out.push_back({"/join #x", "join a channel"});
out.push_back({"/part", "leave it"});
out.push_back({"/msg nick", "a private chat"});
out.push_back({"/me", "an action"});
out.push_back({"/nick", "change your nick"});
out.push_back({"/topic", "see or set the topic"});
out.push_back({"/names", "who is there"});
out.push_back({"/quit", "disconnect, and stay so"});
out.push_back({"/raw", "a line as it is"});
out.push_back({"`", "leave: IRC stays connected"});
} }
const char* IrcApp::helpTitle() const { const char* IrcApp::helpTitle() const {
+2 -1
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "app.h" #include "app.h"
#include "app_keys.h"
#include "app_manager.h" #include "app_manager.h"
#include "list_model.h" #include "list_model.h"
#include "ui/theme.h" #include "ui/theme.h"
@@ -14,7 +15,7 @@ class LauncherApp : public App {
void onEnter() override; void onEnter() override;
bool onKey(const KeyEvent& e) override; bool onKey(const KeyEvent& e) override;
void draw(Canvas& c) override; void draw(Canvas& c) override;
void help(std::vector<KeyHelp>& out) const override { help::list(out, "open the App"); } void help(std::vector<KeyHelp>& out) const override { keys::add(out, keys::kLauncher); }
private: private:
AppManager* manager_ = nullptr; AppManager* manager_ = nullptr;
+5 -12
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "lora_scanner_app.h" #include "lora_scanner_app.h"
#include <Arduino.h> #include <Arduino.h>
@@ -167,18 +168,10 @@ void LoraScannerApp::update(uint32_t nowMs) {
void LoraScannerApp::help(std::vector<KeyHelp>& out) const { void LoraScannerApp::help(std::vector<KeyHelp>& out) const {
switch (view_) { switch (view_) {
case View::Packets: case View::Packets: keys::add(out, keys::kLora); break;
help::list(out, "the packet's details"); case View::Details: keys::add(out, keys::kLoraPacket); break;
out.push_back({"p", "pick a Meshtastic preset"}); case View::Presets: keys::add(out, keys::kLoraPresets); break;
out.push_back({"c", capture_.capturing() ? "stop the Capture" : "start a Capture (pcap)"}); case View::Sweep: keys::add(out, keys::kLoraSweep); break;
out.push_back({"Tab", "the Sweep"});
break;
case View::Details:
out.push_back({"; .", "scroll"});
out.push_back({"Enter", "back to the list"});
break;
case View::Presets: help::list(out, "listen with this preset"); break;
case View::Sweep: out.push_back({"Tab", "the Sniffer"}); break;
} }
} }
+3 -2
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "maintenance_page.h" #include "maintenance_page.h"
#include "ui/widgets.h" #include "ui/widgets.h"
@@ -36,8 +37,8 @@ CleanupPlan MaintenancePage::planFor(int age) const {
} }
void MaintenancePage::help(std::vector<KeyHelp>& out) const { void MaintenancePage::help(std::vector<KeyHelp>& out) const {
if (dialog_) return help::dialog(out); if (dialog_) return keys::add(out, keys::kDialog);
help::list(out, view_ == View::Main ? "open" : view_ == View::Categories ? "choose what to clean" : "choose how old"); keys::add(out, keys::kMaintenance);
} }
bool MaintenancePage::onKey(const KeyEvent& e) { bool MaintenancePage::onKey(const KeyEvent& e) {
+3 -9
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "note_editor.h" #include "note_editor.h"
#include <Arduino.h> #include <Arduino.h>
@@ -183,15 +184,8 @@ bool NoteEditor::save() {
} }
void NoteEditor::help(std::vector<KeyHelp>& out) const { void NoteEditor::help(std::vector<KeyHelp>& out) const {
if (dialog_) return help::dialog(out); if (dialog_) return keys::add(out, keys::kDialog);
out.push_back({"Enter", "a new line"}); keys::add(out, keys::kNotesEditor);
out.push_back({"Del", "delete backwards"});
out.push_back({"Tab", "two spaces"});
out.push_back({"Fn ; . , /", "move the cursor"});
out.push_back({"Alt Fn ; .", "a page up, down"});
out.push_back({"Ctrl a e", "start, end of the line"});
out.push_back({"opt ' e", "an accent: \xC3\xA9"});
out.push_back({"`", "done: it saves by itself"});
} }
bool NoteEditor::onKey(const KeyEvent& e) { bool NoteEditor::onKey(const KeyEvent& e) {
+4 -8
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "notes_app.h" #include "notes_app.h"
#include <Arduino.h> #include <Arduino.h>
@@ -175,15 +176,10 @@ std::string NotesApp::titleOf(int row) {
void NotesApp::help(std::vector<KeyHelp>& out) const { void NotesApp::help(std::vector<KeyHelp>& out) const {
if (view_ == View::Edit) return editor_.help(out); if (view_ == View::Edit) return editor_.help(out);
if (dialog_) return help::dialog(out); if (dialog_) return keys::add(out, keys::kDialog);
if (view_ == View::Name) return help::textEntry(out, "rename the file"); if (view_ == View::Name) return keys::add(out, keys::kNotesName);
if (view_ == View::NoMemory) return; if (view_ == View::NoMemory) return;
help::list(out, "open the note"); keys::add(out, keys::kNotes);
out.push_back({", /", "a page up, down"});
out.push_back({"n", "a new note"});
out.push_back({"r", "rename its file"});
out.push_back({"d Del", "delete it"});
out.push_back({"s", "sort: newest, or by name"});
} }
const char* NotesApp::helpTitle() const { const char* NotesApp::helpTitle() const {
+4 -6
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "settings_app.h" #include "settings_app.h"
#include <Arduino.h> #include <Arduino.h>
@@ -131,12 +132,9 @@ bool SettingsApp::onAboutKey(const KeyEvent& e) {
void SettingsApp::help(std::vector<KeyHelp>& out) const { void SettingsApp::help(std::vector<KeyHelp>& out) const {
switch (page_) { switch (page_) {
case Page::Menu: case Page::Menu: keys::add(out, keys::kSettings); break;
help::list(out, "edit, or open the page"); case Page::Text: keys::add(out, keys::kText); break;
out.push_back({", /", "change a switch or a slider"}); case Page::Choice: keys::add(out, keys::kSettingsChoice); break;
break;
case Page::Text: help::textEntry(out); break;
case Page::Choice: help::list(out, "choose it"); break;
case Page::About: break; case Page::About: break;
case Page::Wifi: wifiPage_.help(out); break; case Page::Wifi: wifiPage_.help(out); break;
case Page::Firmware: firmwarePage_.help(out); break; case Page::Firmware: firmwarePage_.help(out); break;
+4 -9
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "setup_app.h" #include "setup_app.h"
#include "platform/identity.h" #include "platform/identity.h"
@@ -47,16 +48,10 @@ bool SetupApp::onKey(const KeyEvent& e) {
void SetupApp::help(std::vector<KeyHelp>& out) const { void SetupApp::help(std::vector<KeyHelp>& out) const {
switch (wizard_.step()) { switch (wizard_.step()) {
case Step::LongName: case Step::LongName:
case Step::ShortName: help::textEntry(out, "next step"); break; case Step::ShortName: keys::add(out, keys::kSetupText); break;
case Step::Region: case Step::Region:
case Step::Timezone: case Step::Timezone: keys::add(out, keys::kSetupChoice); break;
help::list(out, "choose it, next step"); default: keys::add(out, keys::kSetup); break;
out.push_back({"`", "the step before"});
break;
default:
out.push_back({"Enter", "continue"});
out.push_back({"`", "the step before"});
break;
} }
} }
+6 -18
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "storage_app.h" #include "storage_app.h"
#include <Arduino.h> #include <Arduino.h>
@@ -279,26 +280,13 @@ void StorageApp::help(std::vector<KeyHelp>& out) const {
case View::Maintenance: return maintenance_.help(out); case View::Maintenance: return maintenance_.help(out);
case View::Editor: return noteEditor_.help(out); case View::Editor: return noteEditor_.help(out);
case View::Viewer: return viewer_.help(out); case View::Viewer: return viewer_.help(out);
case View::Details: case View::Details: return keys::add(out, keys::kStorageDetails);
out.push_back({"; .", "scroll"});
out.push_back({"Enter", "back to the folder"});
return;
default: break; default: break;
} }
if (dialog_) return help::dialog(out); if (dialog_) return keys::add(out, keys::kDialog);
if (wantList_ || wait_ != Wait::None) return (void)out.push_back({"`", "stop the copy or the delete"}); if (wantList_ || wait_ != Wait::None) return keys::add(out, keys::kStorageBusy);
if (view_ == View::Name) return help::textEntry(out, renaming_ ? "rename it" : "make the folder"); if (view_ == View::Name) return keys::add(out, keys::kStorageName);
help::list(out, "open the folder or the file"); keys::add(out, keys::kStorage);
out.push_back({", /", "a page up, down"});
out.push_back({"c x", "copy, cut"});
out.push_back({"v", "paste here"});
out.push_back({"r", "rename"});
out.push_back({"d Del", "delete, after asking"});
out.push_back({"n", "a new folder"});
out.push_back({"i", "details: size, date, type"});
out.push_back({"s", "sort: name, date, size"});
out.push_back({"m", "Maintenance: clean-up, erase"});
out.push_back({"`", "the folder above"});
} }
const char* StorageApp::helpTitle() const { const char* StorageApp::helpTitle() const {
+4 -7
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "system_app.h" #include "system_app.h"
#include <Arduino.h> #include <Arduino.h>
@@ -74,13 +75,9 @@ void SystemApp::onExit() { // nothing is kept while it's closed (Q120)
} }
void SystemApp::help(std::vector<KeyHelp>& out) const { void SystemApp::help(std::vector<KeyHelp>& out) const {
out.push_back({"Tab", "the next view"}); if (view_ == View::Tasks) keys::add(out, keys::kSystemTasks);
out.push_back({"Aa Tab", "the view before"}); else if (view_ == View::System) keys::add(out, keys::kSystemSystem);
if (view_ == View::Tasks) { else keys::add(out, keys::kSystem);
out.push_back({"; .", "scroll"});
out.push_back({"s", "sort: cpu, stack, name"});
}
if (view_ == View::System) out.push_back({"; .", "scroll"});
} }
const char* SystemApp::helpTitle() const { const char* SystemApp::helpTitle() const {
+9 -17
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "wifi_settings_page.h" #include "wifi_settings_page.h"
#include "ipv4.h" #include "ipv4.h"
@@ -449,25 +450,16 @@ void WifiSettingsPage::draw(Canvas& c) {
} }
void WifiSettingsPage::help(std::vector<KeyHelp>& out) const { void WifiSettingsPage::help(std::vector<KeyHelp>& out) const {
if (forgetDialog_) return help::dialog(out); if (forgetDialog_) return keys::add(out, keys::kDialog);
switch (view_) { switch (view_) {
case View::Main: case View::Main: keys::add(out, keys::kWifi); break;
help::list(out, "open, or change"); case View::Servers: keys::add(out, keys::kWifiServers); break;
out.push_back({", /", "switch Wi-Fi on or off"}); case View::Network: keys::add(out, keys::kWifiNetwork); break;
break; case View::Details: keys::add(out, keys::kWifiStatus); break;
case View::Servers: case View::Scan: keys::add(out, keys::kWifiScan); break;
help::list(out, "edit"); case View::Ssid: keys::add(out, keys::kWifiName); break;
out.push_back({", /", "Always use my DNS: on, off"});
break;
case View::Network:
help::list(out, "edit, or forget");
out.push_back({", /", "Automatic or Fixed"});
break;
case View::Details: out.push_back({"Enter", "back"}); break;
case View::Scan: help::list(out, "choose this network"); break;
case View::Ssid: help::textEntry(out, "next: the password"); break;
case View::Password: case View::Password:
case View::Edit: help::textEntry(out); break; case View::Edit: keys::add(out, keys::kText); break;
} }
} }
+5 -11
View File
@@ -1,3 +1,4 @@
#include "app_keys.h"
#include "wifi_tools_app.h" #include "wifi_tools_app.h"
#include <M5Cardputer.h> #include <M5Cardputer.h>
@@ -155,17 +156,10 @@ void WifiToolsApp::draw(Canvas& c) {
void WifiToolsApp::help(std::vector<KeyHelp>& out) const { void WifiToolsApp::help(std::vector<KeyHelp>& out) const {
switch (view_) { switch (view_) {
case View::Menu: help::list(out); break; case View::Menu: keys::add(out, keys::kWifiTools); break;
case View::Networks: case View::Networks: keys::add(out, keys::kWifiNetworks); break;
help::list(out, "track its signal"); case View::Channels: break; // nothing but the keys that work everywhere
out.push_back({"s", "sort: signal, channel, name"}); case View::Tracker: keys::add(out, keys::kWifiTracker); break;
out.push_back({"o", "open networks only"});
out.push_back({"h", "hide the hidden ones"});
out.push_back({"w", "strong ones only"});
out.push_back({"l", "log the scans to the card"});
break;
case View::Channels: break;
case View::Tracker: out.push_back({"m", "clicks on or off"}); break;
} }
} }