mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-28 07:48:13 +00:00
fix(keyboard): joystick-free Compact mode for external keyboards
Compact mode (Settings > Keyboard's "Ext. KB" row) is meant to guarantee operation with no joystick at all, but it was still half-tied to the on-screen grid it hides: - arrows now move the text cursor directly instead of a grid selection nobody can see, and Tab opens the placeholder picker directly instead of the row/col-dependent Hold-Enter dispatch - plain Enter submits the field (there's no grid cell to have deliberately landed on), same as Fn+Enter - Fn+letter's accent popup no longer gates on the grid's script/T9 settings -- CardKB always types plain Latin regardless of them, so the gate only made the gesture silently stop working - the whole status line is gone: nothing it showed (script, T9-vs-ABC, caps) is actionable from an external keyboard. The freed height goes to message-preview lines, floored at the smallest grid's footprint so cursor mode's own hint block still fits - the accent popup gets a fixed slot instead of anchoring on a `row` that is never deliberately navigated to in this mode Also fixes a text-corrupting invariant break: moveCursorDirect() and openPlaceholders() move the cursor without finalizing a pending T9 multi-tap cycle, so a later tap on the same cell within the timeout overwrote an unrelated character. Every other cursor-moving path already cleared it. Fn+Tab is dropped as a separate shortcut -- plain Tab already covered every case it did. Fn+Enter no longer reads as a dead key in cursor mode. Direct typing moves into insertTyped(), one translation point documenting what a future relabelled-keycap layout (Cyrillic/Greek) would need. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -296,6 +296,14 @@ struct KeyboardWidget {
|
||||
void beginFrame() { _visible = false; }
|
||||
bool isVisible() const { return _visible; }
|
||||
|
||||
// True while the plain letter/symbol grid is the active input surface --
|
||||
// showing, no placeholder/accent popup open, not mid cursor-reposition.
|
||||
// Used by CardKB's Compact-mode handling (UITask::pollCardKB()) to tell
|
||||
// "grid navigation" apart from every other state arrows/Enter already mean
|
||||
// something else in (those all render their own visible feedback, so they
|
||||
// don't need Compact's special-casing).
|
||||
bool inPlainGridState() const { return isVisible() && !_ph_menu.active && !cursor_mode && !accent_active; }
|
||||
|
||||
char _ph_buf[KB_PH_MAX][KB_PH_LEN];
|
||||
int _ph_count;
|
||||
PopupMenu _ph_menu;
|
||||
@@ -443,6 +451,29 @@ struct KeyboardWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Insert one character typed literally on an external keyboard (CardKB or
|
||||
// similar), as opposed to committed off the on-screen grid. The source
|
||||
// already sends the correct case, so no Shift/caps-lock is re-applied.
|
||||
//
|
||||
// This is the single translation point for external-keyboard input: today
|
||||
// it's the identity mapping (CardKB is a Latin QWERTY, so what it sends is
|
||||
// what gets typed, regardless of the on-screen grid's script/T9 settings --
|
||||
// those only govern grid navigation). To support relabelled keycaps
|
||||
// (Cyrillic/Greek/...) later, map `c` to that layout's codepoint here and
|
||||
// hand the resulting UTF-8 to insertGlyph() -- everything downstream already
|
||||
// works in codepoints, not bytes. Such a layout belongs on its own setting,
|
||||
// not on keyboard_main_alphabet: it describes the physical keycaps, which
|
||||
// are independent of what the on-screen grid shows. Digits/punctuation
|
||||
// should keep passing through unmapped, and Fn+letter accents
|
||||
// (openAccentFor()) stay Latin-only -- they're meaningless under non-Latin
|
||||
// keycaps.
|
||||
void insertTyped(char c) {
|
||||
t9_cell = -1; // otherwise a same-cell T9 tap within KB_T9_TIMEOUT_MS would
|
||||
// overwrite this character instead of inserting a new one
|
||||
char one[2] = { c, '\0' };
|
||||
insertGlyph(one, false);
|
||||
}
|
||||
|
||||
void clearPlaceholders() { _ph_count = 0; }
|
||||
|
||||
void addPlaceholder(const char* ph) {
|
||||
@@ -453,6 +484,36 @@ struct KeyboardWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Opens the placeholder picker directly -- same as navigating the grid to
|
||||
// the special row's {} cell and pressing Enter (see handleInput's KEY_ENTER
|
||||
// special-row case 3, which now calls this too). Used by CardKB's Compact
|
||||
// mode (UITask::pollCardKB(), plain Tab) so a placeholder is reachable
|
||||
// without ever seeing or navigating the grid.
|
||||
void openPlaceholders() {
|
||||
t9_cell = -1; // finalize any pending multi-tap cycle -- the pick below moves the
|
||||
// cursor, so a later same-cell tap must not "continue" onto it
|
||||
if (_ph_refresh) _ph_refresh(*this, _ph_refresh_ctx); // contextual repopulate, if wired up
|
||||
_ph_menu.begin(_ph_title, KB_PH_VISIBLE);
|
||||
for (int i = 0; i < _ph_count; i++) _ph_menu.addItem(_ph_buf[i]);
|
||||
}
|
||||
|
||||
// Moves the text cursor directly (LEFT/RIGHT one codepoint, UP/DOWN to
|
||||
// start/end) without engaging cursor_mode's grid-boundary wrap (continuing
|
||||
// into the special row / row 0 once already at an end) -- that wrap exists
|
||||
// so a physical-button user can see where they land back on the grid, which
|
||||
// doesn't apply here: CardKB's Compact mode never navigates the grid at
|
||||
// all, so there's no grid position to wrap into. See pollCardKB().
|
||||
void moveCursorDirect(char key) {
|
||||
t9_cell = -1; // same invariant every other cursor-moving path keeps: a pending
|
||||
// multi-tap cycle must not resume against a moved cursor and
|
||||
// overwrite an unrelated character. (cursor_mode gets this for
|
||||
// free -- its KEY_UP entry point already clears t9_cell.)
|
||||
if (key == KEY_LEFT) { if (cursor_pos > 0) cursor_pos -= kbUtf8LastCharBytes(buf, cursor_pos); }
|
||||
else if (key == KEY_RIGHT) { if (cursor_pos < len) cursor_pos += kbUtf8CharBytesAt(buf, cursor_pos, len); }
|
||||
else if (key == KEY_UP) { cursor_pos = 0; }
|
||||
else if (key == KEY_DOWN) { cursor_pos = len; }
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) {
|
||||
_visible = true;
|
||||
// A stale mid-cycle T9 press (no further input since) finalizes on its own —
|
||||
@@ -470,8 +531,15 @@ struct KeyboardWidget {
|
||||
const int lh = display.getLineHeight();
|
||||
const int cw = display.getCharWidth();
|
||||
const int cell_w = display.width() / cols;
|
||||
// compact: don't stretch cells beyond lh; freed vertical space goes to preview lines
|
||||
const int kb_h = (rows + 1) * lh;
|
||||
bool compact_ui = prefs && prefs->keyboard_cardkb_compact;
|
||||
// compact: don't stretch cells beyond lh; freed vertical space goes to preview lines.
|
||||
// Compact mode only ever draws 2 short hint lines (no grid, no status line
|
||||
// -- see below), but reserves at least as much height as the smallest real
|
||||
// grid (T9's 3 rows) would need, rather than shrinking to just those 2
|
||||
// lines: cursor_mode's own 3-line hint (drawn in this same region,
|
||||
// regardless of Compact, for a physical-button user) already relies on
|
||||
// that floor and would otherwise get clipped.
|
||||
const int kb_h = compact_ui ? (KB_T9_ROWS + 1) * lh : (rows + 1) * lh;
|
||||
const int preview_h = display.height() - kb_h - display.sepH();
|
||||
const int prev_lines = (preview_h / lh) > 1 ? (preview_h / lh) : 1;
|
||||
const int sep_y = prev_lines * lh;
|
||||
@@ -535,27 +603,21 @@ struct KeyboardWidget {
|
||||
|
||||
// Compact mode (Settings > Keyboard's "Ext. KB" row): an external-keyboard
|
||||
// typist never looks at the letter grid or special-row icons, so skip
|
||||
// drawing them and show a one-line status (current script/page, caps)
|
||||
// instead. row/col/page keep updating exactly as before even while this
|
||||
// is on (arrows and Fn+letter both still work; the accent popup below
|
||||
// still anchors on `row`), so nothing breaks if physical buttons get used
|
||||
// meanwhile -- it just won't be visible which cell is selected.
|
||||
if (prefs && prefs->keyboard_cardkb_compact) {
|
||||
const int hh = lh + 2;
|
||||
// drawing them entirely -- no status line either, since nothing it could
|
||||
// show (script/page, T9-vs-ABC, caps) is actually actionable from CardKB:
|
||||
// typing is always plain Latin ASCII regardless of Main/Additional
|
||||
// alphabet or keyboard_type (direct-typing passthrough, see
|
||||
// UITask::pollCardKB()), Fn+letter's accent popup now works the same way
|
||||
// regardless of them too (see openAccentFor()), and caps-lock has no
|
||||
// CardKB gesture to toggle it at all. Just the two shortcuts that still do
|
||||
// something here (arrows/Enter are self-explanatory -- cursor movement and
|
||||
// submit -- so they get no hint of their own). Physical buttons (if used
|
||||
// instead of/alongside CardKB) still drive row/col/page as normal; it
|
||||
// just won't be visible on this screen which cell is selected.
|
||||
if (compact_ui) {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.fillRect(0, chars_y, display.width(), hh);
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
const char* script_name = pageIsSymbols(page) ? "Symbols"
|
||||
: (scriptAt(page) == NodePrefs::KB_ALPHABET_CYRILLIC) ? "Cyrillic"
|
||||
: (scriptAt(page) == NodePrefs::KB_ALPHABET_GREEK) ? "Greek"
|
||||
: "Latin";
|
||||
char status[32];
|
||||
if (pageIsSymbols(page)) snprintf(status, sizeof(status), "%s%s", script_name, caps ? " CAPS" : "");
|
||||
else snprintf(status, sizeof(status), "%s %s%s", script_name, isT9() ? "T9" : "ABC", caps ? " CAPS" : "");
|
||||
display.drawTextCentered(display.width() / 2, chars_y + 1, status);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawTextCentered(display.width() / 2, chars_y + hh + 2, "Fn+Tab menu");
|
||||
display.drawTextCentered(display.width() / 2, chars_y + hh + 2 + lh, "Fn+letter accent");
|
||||
display.drawTextCentered(display.width() / 2, chars_y, "Tab: placeholders");
|
||||
display.drawTextCentered(display.width() / 2, chars_y + lh, "Fn+letter: accent");
|
||||
} else {
|
||||
// character grid
|
||||
if (isT9()) {
|
||||
@@ -639,6 +701,10 @@ struct KeyboardWidget {
|
||||
// placeholder overlay just below), anchored on the held letter's own row
|
||||
// so it reads as "popping out of" that key instead of taking over the
|
||||
// whole keyboard. LEFT/RIGHT picks, Enter commits, Cancel dismisses.
|
||||
// In Compact there's no grid drawn and `row` was never deliberately
|
||||
// navigated to, so anchoring on it would just park the popup at an
|
||||
// arbitrary height (and possibly over a hint line) -- it gets a fixed slot
|
||||
// under the two hints instead.
|
||||
if (accent_active) {
|
||||
const char* group = KB_ACCENT_VARIANTS[accent_group];
|
||||
int n = kbUtf8Len(group);
|
||||
@@ -648,8 +714,8 @@ struct KeyboardWidget {
|
||||
int max_bw = display.width() - 4;
|
||||
if (bw > max_bw) { bw = max_bw; seg_w = bw / n; }
|
||||
int bx = (display.width() - bw) / 2;
|
||||
int by = chars_y + row * cell_h - 1;
|
||||
int bh = cell_h + 1;
|
||||
int by = compact_ui ? (chars_y + 2 * lh + 2) : (chars_y + row * cell_h - 1);
|
||||
int bh = compact_ui ? (lh + 2) : (cell_h + 1);
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
display.fillRect(bx, by, bw, bh);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
@@ -680,15 +746,18 @@ struct KeyboardWidget {
|
||||
// CardKB's Fn+letter ("alt") gesture, see UITask::pollCardKB(): open the
|
||||
// accent popup for this base Latin letter directly, skipping the
|
||||
// arrow-hunt to find its cell first. `base` always comes from CardKB's own
|
||||
// physical QWERTY layout, so this only makes sense -- same gating as the
|
||||
// physical Hold-Enter-on-a-letter-cell path just below -- while the
|
||||
// keyboard is idle on its plain Latin page: on T9, symbols, or a non-Latin
|
||||
// main/alt script (Cyrillic, Greek, ...), the on-screen grid isn't showing
|
||||
// Latin letters at all, so it's a no-op rather than inserting a stray
|
||||
// Latin accent into unrelated text.
|
||||
// physical QWERTY layout -- CardKB is a Latin keyboard, so it always types
|
||||
// plain ASCII regardless of the on-screen grid's current page/script/T9
|
||||
// setting (those only govern what the *grid* shows for physical-button
|
||||
// navigation, a completely separate input path with its own copy of this
|
||||
// same gate at the Hold-Enter-on-a-letter-cell site in handleInput()).
|
||||
// Gating this one on the grid's page/script/T9 state would make Fn+letter
|
||||
// silently stop working whenever Main alphabet is set to Cyrillic/Greek or
|
||||
// keyboard_type to T9, even though CardKB is still typing plain Latin text
|
||||
// just fine -- so this only checks that no other exclusive input mode
|
||||
// (popup/cursor-move) is already in progress, same as inPlainGridState().
|
||||
bool openAccentFor(char base) {
|
||||
if (!isVisible() || _ph_menu.active || cursor_mode || accent_active) return false;
|
||||
if (isT9() || pageIsSymbols(page) || scriptAt(page) != NodePrefs::KB_ALPHABET_LATIN_ONLY) return false;
|
||||
int gi = findAccentGroup(base);
|
||||
if (gi < 0) return false;
|
||||
accent_active = true;
|
||||
@@ -752,7 +821,11 @@ struct KeyboardWidget {
|
||||
row = 0;
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_ENTER || c == KEY_CANCEL) { cursor_mode = false; return NONE; }
|
||||
// KEY_KB_ENTER (external keyboard's Fn+Enter) leaves the mode too rather
|
||||
// than being silently eaten -- it's the one key an external-keyboard
|
||||
// typist would reach for here, and cursor mode is only ever entered from
|
||||
// a physical button, so without this it looks like a dead key.
|
||||
if (c == KEY_ENTER || c == KEY_CANCEL || c == KEY_KB_ENTER) { cursor_mode = false; return NONE; }
|
||||
return NONE;
|
||||
}
|
||||
|
||||
@@ -778,11 +851,13 @@ struct KeyboardWidget {
|
||||
// source, see UITask::pollCardKB()). Printable characters insert
|
||||
// straight at the cursor, bypassing the on-screen grid entirely -- no
|
||||
// caps re-application, the source already sends the correct case.
|
||||
// Backspace deletes the previous character. KEY_KB_ENTER (only ever
|
||||
// emitted for CardKB's Fn+Enter, see pollCardKB()) submits the field
|
||||
// directly -- plain Enter (byte-identical to a physical button's
|
||||
// KEY_ENTER) always acts on the grid instead, same as a physical-button
|
||||
// user, so there's no ambiguity to resolve here.
|
||||
// Backspace deletes the previous character. KEY_KB_ENTER submits the
|
||||
// field directly -- pollCardKB() emits it for Fn+Enter always, and for
|
||||
// plain Enter too when Compact mode's plain grid state applies (there's
|
||||
// no grid cell to commit there). Every other plain Enter (Full mode, or
|
||||
// Compact but mid popup/cursor-move) arrives here as ordinary KEY_ENTER
|
||||
// and falls through to the grid dispatch below instead, so there's still
|
||||
// no ambiguity to resolve at this layer -- pollCardKB() already decided.
|
||||
if (c == KEY_KB_ENTER) return DONE;
|
||||
if (c == 0x08) {
|
||||
t9_cell = -1; // invalidate any pending T9 cycle -- see the grid paths below
|
||||
@@ -795,10 +870,7 @@ struct KeyboardWidget {
|
||||
return NONE;
|
||||
}
|
||||
if (c >= 0x20 && c <= 0x7E) {
|
||||
t9_cell = -1; // otherwise a same-cell T9 tap within KB_T9_TIMEOUT_MS would
|
||||
// overwrite this character instead of inserting a new one
|
||||
char one[2] = { c, '\0' };
|
||||
insertGlyph(one, false);
|
||||
insertTyped(c);
|
||||
return NONE;
|
||||
}
|
||||
|
||||
@@ -879,10 +951,11 @@ struct KeyboardWidget {
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_ENTER) {
|
||||
// Plain Enter always acts on the grid (repeat a letter, cycle T9,
|
||||
// switch page/script, reach the special row's DONE cell) -- same as a
|
||||
// physical button. CardKB submits via the separate KEY_KB_ENTER
|
||||
// (Fn+Enter, see pollCardKB()) instead, so there's no ambiguity here.
|
||||
// By the time a plain KEY_ENTER reaches here, it's a real grid
|
||||
// interaction -- repeat a letter, cycle T9, switch page/script, reach
|
||||
// the special row's DONE cell -- same as a physical button. CardKB's
|
||||
// own Enter is only ever KEY_ENTER when that's true (see pollCardKB()'s
|
||||
// KEY_KB_ENTER cases just above), so there's no ambiguity here.
|
||||
if (row < rows && isT9()) {
|
||||
int cell = row * cols + col;
|
||||
const char* group = t9GroupStr(cell);
|
||||
@@ -955,9 +1028,7 @@ struct KeyboardWidget {
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (_ph_refresh) _ph_refresh(*this, _ph_refresh_ctx); // contextual repopulate, if wired up
|
||||
_ph_menu.begin(_ph_title, KB_PH_VISIBLE);
|
||||
for (int i = 0; i < _ph_count; i++) _ph_menu.addItem(_ph_buf[i]);
|
||||
openPlaceholders();
|
||||
break;
|
||||
case 4:
|
||||
page = (page + 1) % totalPages(); // cycle letters -> [alt alphabet] -> symbols
|
||||
|
||||
@@ -2109,16 +2109,23 @@ static const char CARDKB_FN_BASE[48] = {
|
||||
// translation at all: CardKB's own arrow/Enter/Esc byte codes are already
|
||||
// identical to this UI's KEY_LEFT/UP/DOWN/RIGHT/ENTER/CANCEL (0xB4-0xB7, 13,
|
||||
// 27), and Backspace (0x08) / printable ASCII (0x20-0x7E) collide with
|
||||
// nothing that existed before. Plain Enter now always acts like the physical
|
||||
// centre button (commit/repeat a grid cell) -- no more ambiguity to resolve,
|
||||
// since Fn gives two clean, stateless modifiers instead:
|
||||
// - Fn+Enter (0xA3) submits the field (KEY_KB_ENTER) from anywhere, without
|
||||
// needing to navigate to the special row's DONE cell.
|
||||
// - Fn+Tab (0x8C) opens the Hold-Enter equivalent (shift-lock, clear-all,
|
||||
// accent popup on whatever cell is selected) -- plain Tab (otherwise
|
||||
// unused) still does this for the ~30 non-keyboard Hold-Enter menus
|
||||
// (message reply/navigate, Bot/Admin/Repeater, ...) but is inert while the
|
||||
// on-screen keyboard itself is showing, where Fn+Tab/Fn+letter cover it.
|
||||
// nothing that existed before. Plain Enter/arrows act like the physical
|
||||
// centre button/joystick (grid commit/navigate) -- except in Compact mode's
|
||||
// plain grid state (see below), which is designed to need no joystick at all.
|
||||
// Tab (0x09, otherwise unused) is the Hold-Enter equivalent everywhere,
|
||||
// including the ~30 non-keyboard Hold-Enter menus (message reply/navigate,
|
||||
// Bot/Admin/Repeater, ...) and inside the on-screen keyboard itself (shift-
|
||||
// lock, clear-all, accent popup on whatever cell is selected) -- it used to
|
||||
// need a separate Fn+Tab for the latter, but that was pure redundancy: plain
|
||||
// Tab already covered every case Fn+Tab did, just not while the keyboard was
|
||||
// showing, so the carve-out was dropped instead of the shortcut. In Compact
|
||||
// mode's plain grid state Tab means something more useful instead (opens the
|
||||
// placeholder picker directly -- see below). Fn still gives two other clean,
|
||||
// stateless modifiers:
|
||||
// - Fn+Enter (0xA3) submits the field (KEY_KB_ENTER) without needing to
|
||||
// navigate to the special row's DONE cell. The placeholder/accent popups
|
||||
// are modal and consume it first (dismiss them with Enter/Esc), same as
|
||||
// they consume every other key.
|
||||
// - Fn+<letter> opens the accent popup for that base letter directly
|
||||
// (KeyboardWidget::openAccentFor()) -- no arrow-hunting across the grid.
|
||||
// CardKB is level-triggered (it keeps returning the held key's byte, not just
|
||||
@@ -2143,13 +2150,34 @@ void UITask::pollCardKB() {
|
||||
_cardkb_last_raw = raw;
|
||||
if (raw == 0) return; // key just released, nothing to enqueue
|
||||
|
||||
// Compact mode (Settings > Keyboard's "Ext. KB" row) hides the letter grid
|
||||
// entirely, and is meant to guarantee joystick-free operation: while it's
|
||||
// the active surface (KeyboardWidget::inPlainGridState() -- showing, no
|
||||
// popup open, not already mid cursor-move) arrows drive the text cursor
|
||||
// directly instead of a grid selection nobody could see anyway, and plain
|
||||
// Tab opens the placeholder picker directly instead of the row/col-dependent
|
||||
// Hold-Enter dispatch (which would be meaningless here -- row/col are never
|
||||
// deliberately navigated to in this mode). Cursor mode / the accent /
|
||||
// placeholder popups all render their own visible feedback regardless of
|
||||
// Compact, so none of this applies once inPlainGridState() is false --
|
||||
// arrows/Tab fall through to their normal meaning there (e.g. arrows drive
|
||||
// the placeholder/accent popup's own selection).
|
||||
bool compact_grid = _node_prefs && _node_prefs->keyboard_cardkb_compact && _kb.inPlainGridState();
|
||||
|
||||
char key;
|
||||
if (raw == 0xA3) { // Fn+Enter -- submit the field
|
||||
key = KEY_KB_ENTER;
|
||||
} else if (raw == 0x8C) { // Fn+Tab -- Hold-Enter equivalent, unconditionally
|
||||
key = KEY_CONTEXT_MENU;
|
||||
} else if (raw == 0x09) { // plain Tab -- same, but only outside the keyboard
|
||||
if (_kb.isVisible()) return;
|
||||
} else if (compact_grid && (raw == (uint8_t)KEY_LEFT || raw == (uint8_t)KEY_UP ||
|
||||
raw == (uint8_t)KEY_DOWN || raw == (uint8_t)KEY_RIGHT)) {
|
||||
char woke = checkDisplayOn((char)raw); // already sets _next_refresh=0 when the display was on
|
||||
if (woke && !_locked) _kb.moveCursorDirect((char)raw);
|
||||
return;
|
||||
} else if (raw == 0x09) { // Tab -- Hold-Enter equivalent, always (single shortcut: there used to
|
||||
if (compact_grid) { // also be a separate Fn+Tab for this, but plain Tab already covers every
|
||||
char woke = checkDisplayOn((char)raw); // case Fn+Tab did -- outside the keyboard, and now inside it
|
||||
if (woke && !_locked) _kb.openPlaceholders(); // too -- so the modifier was pure redundancy)
|
||||
return;
|
||||
}
|
||||
key = KEY_CONTEXT_MENU;
|
||||
} else if (raw == 0x80) {
|
||||
// Fn+Esc -- CardKB's lock/unlock gesture: a single press toggles _locked
|
||||
@@ -2186,8 +2214,11 @@ void UITask::pollCardKB() {
|
||||
if (woke && !_locked) _kb.openAccentFor(base);
|
||||
return;
|
||||
} else {
|
||||
key = (char)raw; // plain Enter/arrows/backspace/ASCII -- byte-identical
|
||||
// to a physical keystroke, no CardKB-specific state
|
||||
// Plain Enter would otherwise commit whatever grid cell row/col happen to
|
||||
// be frozen at (there's no grid navigation to have deliberately landed on
|
||||
// one in Compact) -- submit instead, same as Fn+Enter. Backspace/ASCII
|
||||
// passthrough is unaffected by Compact either way.
|
||||
key = (compact_grid && raw == (uint8_t)KEY_ENTER) ? KEY_KB_ENTER : (char)raw;
|
||||
}
|
||||
enqueueKey(checkDisplayOn(key));
|
||||
#endif
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
### What's new
|
||||
|
||||
- **Auto-Reply Bot is renamed to Remote Bot.** It's grown beyond auto-replies into remote device control (Actions, below), so the Tools screen entry and docs now say "Remote Bot" — same screen, same settings, nothing to reconfigure.
|
||||
- **Remote Bot gains Actions: `!buzz`, `!gps`, `!advert`.** A new **Actions** toggle (per target — Direct/Channel/Room, nested under each target's existing **Commands** toggle) enables three commands that change the device's own behaviour instead of just reporting on it: `!buzz [seconds]` sounds the buzzer as a find-me signal (default 5s, capped at 30s, sounds even if the buzzer is muted), `!gps on`/`!gps off` toggles GPS, and `!advert` sends an immediate advert. Off by default; combines with the existing query commands in one message the same way (`!batt !gps on` → `4.10V | GPS: on`).
|
||||
- **Remote Bot gains Actions: `!buzz`, `!gps`, `!advert`.** A new **Actions** toggle (per target — Direct/Channel/Room, nested under each target's existing **Commands** toggle) enables three commands that change the device's own behaviour instead of just reporting on it: `!buzz [seconds]` sounds the buzzer as a find-me signal (default 5s, capped at 30s, sounds even if the buzzer is muted), `!gps on`/`!gps off` toggles GPS, and `!advert` sends an immediate advert (0 hop). Off by default; combines with the existing query commands in one message the same way (`!batt !gps on` → `4.10V | GPS: on`).
|
||||
- **`!gps fix [seconds]` — single-shot GPS location, no need to leave GPS running.** Turns GPS on (if it wasn't already), waits for a stabilised fix (HDOP ≤ 2.0 — falls back to ≥8 satellites on GPS hardware that doesn't report HDOP — then averages for 10s), sends the position, and restores GPS to whatever state it was in before — up to a 90s timeout by default (override with an optional argument, clamped to 15-300s, for a poor sky view where 90s isn't always enough), after which it reports a partial fix (if it got at least some samples) or failure. Replies in two parts: an immediate "acquiring fix..." ack, then the actual position as a follow-up message once ready. Only one fix request can be in flight at a time (a second one gets "fix already pending"); same Actions toggle as `!buzz`/`!gps`/`!advert`.
|
||||
- **Bot Trigger fields accept multiple phrases.** Pack several trigger words into one Trigger field, comma-separated (`hi,hello there,yo`) — matching any one of them fires the reply, same as before for a single phrase.
|
||||
- **Remote Bot Actions gain `!gpio1`..`!gpio4`** (Wio Tracker L1 only — 4 otherwise-unused pins). Each pin is independently set to Off/Input/Output (GPIO1/GPIO2 also offer Analog) from a new **Tools › GPIO** screen; `!gpio1 on`/`!gpio1 off` drives an Output pin remotely, a bare `!gpio1` reports the current mode and reading (including a millivolt value in Analog mode). Gated by the same per-target Actions toggle as `!buzz`/`!gps`/`!advert`.
|
||||
- **Optional CardKB support (Wio Tracker L1, Grove I2C) — full keyboard-only navigation.** Plug an M5Stack CardKB into the Grove connector and it's auto-detected at boot — no setting to flip. Typing goes straight into the message/name field instead of navigating the on-screen keyboard grid; arrows and Esc work as expected everywhere else too (including inside the placeholder and accent popups). Enter acts like the physical centre button (advances the on-screen grid selection) rather than submitting, so **Fn+Enter** sends the message/confirms the field from anywhere, **Fn+`<letter>`** opens that letter's accent popup directly (no arrow-hunting needed — handy for Polish/Czech/etc. diacritics), and **Fn+Tab** opens the Hold-Enter equivalent (Shift-lock, clear field) for whatever's selected; plain **Tab** still opens the Hold-Enter context menu everywhere else (message reply/navigate, Bot/Admin/Repeater menus, …).
|
||||
- **Settings › Keyboard gets an "Ext. KB" row** (boards that support CardKB, above). Switching it to **Compact** hides the on-screen letter grid and special-row icons — a CardKB typist never looks at them — replacing them with a one-line status (current script/page, caps) plus a reminder of the Fn shortcuts; the accent and placeholder popups still show normally. Off (**Full**) by default.
|
||||
- **Optional CardKB support (Wio Tracker L1, Grove I2C) — full keyboard-only navigation.** Plug an M5Stack CardKB into the Grove connector and it's auto-detected at boot — no setting to flip. Typing goes straight into the message/name field instead of navigating the on-screen keyboard grid, always as plain Latin text regardless of the Settings › Keyboard language/type — arrows and Esc work as expected everywhere else too (including inside the placeholder and accent popups). Enter acts like the physical centre button (advances the on-screen grid selection) rather than submitting, so **Fn+Enter** sends the message/confirms the field from anywhere, and **Fn+`<letter>`** opens that letter's accent popup directly (no arrow-hunting needed — handy for Polish/Czech/etc. diacritics — and works no matter what language/keyboard type is configured, since CardKB always types Latin either way). **Tab** is the Hold-Enter equivalent everywhere, including message reply/navigate and the Bot/Admin/Repeater menus — same single shortcut whether or not the on-screen keyboard is showing.
|
||||
- **Settings › Keyboard gets an "Ext. KB" row** (boards that support CardKB, above). Switching it to **Compact** hides the on-screen letter grid, special-row icons, and status line entirely — a CardKB typist never looks at any of it, and none of it (script/page, T9-vs-ABC, caps) is actually actionable from CardKB anyway — replacing them with just a reminder of the Tab/Fn+letter shortcuts; the accent and placeholder popups still show normally. With the grid hidden, arrows and Tab take on more useful direct meanings instead of driving an invisible grid selection: **arrows** move the text cursor immediately (no more entering cursor mode first), **plain Enter** submits the message/field (same as Fn+Enter — there's no grid cell to commit), and **plain Tab** opens the placeholder picker directly. Compact is designed to need no joystick/physical button at all, and reclaims the freed space for extra message-preview lines. Off (**Full**) by default.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user