Pager: Fn+V pastes the clipboard (no touch path to the edit menu)
On the T-LoRa Pager there was no way to paste: the in-RAM clipboard is only consumed by the Cut/Copy/Paste edit menu, which opens solely from a touch long-press on a textarea, and the Pager's encoder never reaches LVGL's press events. Copy from a message bubble worked; nothing could paste the result -- the 64-hex contact key and 32-hex channel secret fields being the painful cases. Physical Fn(Alt) held + V now pastes into the field being edited: - PagerKeyboardState: V_POS; a held-Alt V press is resolved by matrix position before the symbol map, swallowed, and reported through alt_v_chord_pending_/consumeAltVChord() (same shape as Alt+Backspace and the Pro's Alt+B). discardAlt() clears it. !HAS_TDECK_PRO only. - PagerKeyboard.h/.cpp: pagerKeyboardConsumeAltVChord(). - UITask: the Paste cell's logic is factored into txtPasteInto(ta) (selection replaced, pasteTextFor hex-key extraction kept) and reused by updatePagerAltVChord(), which uses the same "actually editing" test as the Alt+Shift Caps chord, hides any open edit menu/accent popup, and toasts "Pasted" / "Clipboard empty". Drained at the remote-mirror and screen-off discard sites like the other chords. - TLORA_PAGER_SHORTCUTS.md: documented. - test_pager_keyboard_state: chord fires once and swallows V; tap-Fn-then-V still returns '?'; discardAlt() drops a pending chord. Trade-off: Fn+V is '?' on this layout, so only a HELD Fn chords. The one-shot (tap Fn, then V) and locked (double-tap Fn) symbol layers still type '?', the same compromise the Pro's physical Alt+B makes with '!'. Tested on tlora_pager_sx1262_companion_radio_touch: Copy from a bubble then Fn+V in the composer pastes; Fn+V in a key field pastes just the key out of a copied message; empty clipboard toasts; tap-Fn-then-V still types '?'. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VKtRiAcddFBAUDogBWmZk4
This commit is contained in:
1 parent
c230820945
commit
e3baa774f5
6 files changed
+109
-5
No files matched your search
@@ -165,6 +165,7 @@ While any text field is focused, including a Wi-Fi or repeater password:
|
||||
| **Space** (double-tap, within 250 ms) | Switches between English and your configured secondary keyboard layout | — |
|
||||
| **Space** (hold ~1 s) | — | Locks the screen (shows a "Locking…" progress bar; tapping any key cancels) |
|
||||
| **Fn (Alt)** (tap / double-tap) | Next key uses symbols / lock symbols until tapped again | Same |
|
||||
| **Fn (Alt) held + V** | **Paste** the clipboard at the cursor (replaces a highlighted range). The clipboard is filled by **Copy** in a message bubble's action menu. Only a *held* Fn pastes — tap Fn then V still types `?` | — |
|
||||
|
||||
The **BOOT** button (top of the device) instantly wakes the screen from
|
||||
idle-dim. It does *not* unlock a screen you've manually locked with the
|
||||
@@ -264,6 +265,7 @@ that changes keyboard language.
|
||||
| Fn + Shift (editing a field) | Toggle Caps Lock |
|
||||
| Fn + Shift (not editing a field) | Nothing |
|
||||
| Fn + Backspace (anywhere) | Jump to Home |
|
||||
| Fn held + V (editing a field) | Paste the clipboard (tap-Fn-then-V still types `?`) |
|
||||
| Fn + Space | Enter accent picker |
|
||||
| @ + letters (composer) | Auto-focuses the mention list — no Fn+Space needed |
|
||||
| Enter | Select / send / message action menu |
|
||||
|
||||
@@ -147,6 +147,11 @@ void pagerKeyboardToggleCaps() { s_state.toggleCaps(); }
|
||||
bool pagerKeyboardConsumeAltBackspaceChord() {
|
||||
return s_state.consumeAltBackspaceChord();
|
||||
}
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
bool pagerKeyboardConsumeAltVChord() {
|
||||
return s_state.consumeAltVChord();
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_TDECK_PRO) && !defined(HAS_TDECK_MAX)
|
||||
bool pagerKeyboardConsumeAltBChord() {
|
||||
return s_state.consumeAltBChord();
|
||||
|
||||
@@ -86,6 +86,14 @@ void pagerKeyboardToggleCaps();
|
||||
* reports held for this press, so it can't also fire the plain-Backspace
|
||||
* hold-to-back/hold-to-unlock gestures. Consumes the pending flag on read. */
|
||||
bool pagerKeyboardConsumeAltBackspaceChord();
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
/** One-shot: true exactly once after physical Alt(Fn)+V is chorded (V pressed
|
||||
* while Fn is physically held). The V press is consumed rather than emitted
|
||||
* as the symbol-layer '?'; tap-Fn-then-V and the locked symbol layer still
|
||||
* type '?'. UITask pastes the in-RAM clipboard into the field being edited.
|
||||
* Consumes the pending flag on read. */
|
||||
bool pagerKeyboardConsumeAltVChord();
|
||||
#endif
|
||||
#if defined(HAS_TDECK_PRO) && !defined(HAS_TDECK_MAX)
|
||||
/** One-shot: true after physical Alt+B is pressed on T-Deck Pro. The B press
|
||||
* is consumed rather than emitted as the symbol-layer exclamation mark. */
|
||||
|
||||
@@ -24,6 +24,7 @@ class PagerKeyboardState {
|
||||
static constexpr uint8_t SHIFT_POS = 2 * COLS + 8;
|
||||
static constexpr uint8_t BACKSPACE_POS = 2 * COLS + 9;
|
||||
static constexpr uint8_t SPACE_POS = 3 * COLS;
|
||||
static constexpr uint8_t V_POS = 2 * COLS + 4; // physical Alt(Fn)+V = paste chord
|
||||
#endif
|
||||
|
||||
uint8_t event(uint8_t code, bool pressed, uint32_t now_ms) {
|
||||
@@ -126,6 +127,17 @@ class PagerKeyboardState {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
// Physical Alt(Fn)+V: paste chord, resolved by matrix position before the
|
||||
// symbol map (same shape as the Pro's Alt+B). Only a HELD Fn chords; the
|
||||
// one-shot (tap Fn, then V) and locked (double-tap Fn) symbol layers still
|
||||
// type '?' from this key, so the question mark is not lost.
|
||||
if (code == V_POS && alt_.held()) {
|
||||
alt_.markHeldUsed();
|
||||
alt_v_chord_pending_ = true;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint8_t row = code / COLS;
|
||||
const uint8_t col = code % COLS;
|
||||
@@ -146,6 +158,9 @@ class PagerKeyboardState {
|
||||
#if defined(HAS_TDECK_PRO) && !defined(HAS_TDECK_MAX)
|
||||
alt_b_chord_pending_ = false;
|
||||
#endif
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
alt_v_chord_pending_ = false;
|
||||
#endif
|
||||
}
|
||||
bool backspaceHeld() const { return backspace_held_; }
|
||||
bool spaceHeld() const { return space_held_; }
|
||||
@@ -167,6 +182,13 @@ class PagerKeyboardState {
|
||||
return pending;
|
||||
}
|
||||
#endif
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
bool consumeAltVChord() {
|
||||
const bool pending = alt_v_chord_pending_;
|
||||
alt_v_chord_pending_ = false;
|
||||
return pending;
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_TDECK_MAX)
|
||||
bool consumeBothShiftChord() {
|
||||
const bool pending = both_shift_chord_pending_;
|
||||
@@ -187,6 +209,9 @@ class PagerKeyboardState {
|
||||
bool alt_key_held_ = false;
|
||||
bool alt_b_chord_pending_ = false;
|
||||
#endif
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
bool alt_v_chord_pending_ = false;
|
||||
#endif
|
||||
#if defined(HAS_TDECK_MAX)
|
||||
bool shift_l_held_ = false;
|
||||
bool shift_r_held_ = false;
|
||||
|
||||
+45
-5
@@ -11947,6 +11947,19 @@ static const char* pasteTextFor(lv_obj_t* ta, const char* clip, char* buf, size_
|
||||
return PasteHexKey::extract(clip, want, buf, cap);
|
||||
}
|
||||
|
||||
// Paste the clipboard into `ta` at the cursor, replacing any highlighted
|
||||
// range. Shared by the edit menu's Paste cell and the Pager's Fn+V chord.
|
||||
// Returns false when the clipboard is empty (nothing was changed).
|
||||
static bool txtPasteInto(lv_obj_t* ta) {
|
||||
if (!ta || !s_clipboard[0]) return false;
|
||||
uint32_t s_cp = 0, e_cp = 0;
|
||||
if (taHasSelection(ta, &s_cp, &e_cp)) taDeleteRange(ta, s_cp, e_cp);
|
||||
char key[65];
|
||||
lv_textarea_add_text(ta, pasteTextFor(ta, s_clipboard, key, sizeof key));
|
||||
taClearSelection(ta);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void txtMenuCellCb(lv_event_t* e) {
|
||||
if (lv_event_get_code(e) != LV_EVENT_CLICKED) return;
|
||||
intptr_t act = reinterpret_cast<intptr_t>(lv_event_get_user_data(e));
|
||||
@@ -11987,11 +12000,7 @@ static void txtMenuCellCb(lv_event_t* e) {
|
||||
else lv_textarea_set_text(ta, "");
|
||||
}
|
||||
} else if (act == TXT_PASTE) {
|
||||
if (s_clipboard[0]) {
|
||||
if (sel) taDeleteRange(ta, s_cp, e_cp);
|
||||
char key[65];
|
||||
lv_textarea_add_text(ta, pasteTextFor(ta, s_clipboard, key, sizeof key));
|
||||
}
|
||||
txtPasteInto(ta);
|
||||
}
|
||||
taClearSelection(ta);
|
||||
txtMenuHide();
|
||||
@@ -43095,6 +43104,28 @@ static void updateProAltBChord() {
|
||||
if (g_lv.task) g_lv.task->noteUserInput();
|
||||
}
|
||||
#endif
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
// Physical Fn(Alt)+V: paste the in-RAM clipboard into the field being edited.
|
||||
// The Pager has no touchscreen, so the long-press Cut/Copy/Paste menu
|
||||
// (txtMenuShow) is unreachable there and the clipboard -- filled by a
|
||||
// message bubble's Copy action -- had no consumer. Same "actually editing"
|
||||
// test as the Alt+Shift Caps chord above; a deliberate no-op otherwise (the
|
||||
// V press is already swallowed by the driver, so nothing leaks through).
|
||||
// Only a HELD Fn chords: tap-Fn-then-V still types '?'.
|
||||
static void updatePagerAltVChord() {
|
||||
if (!pagerKeyboardConsumeAltVChord()) return;
|
||||
lv_obj_t* ta_focused = lv_keyboard_get_textarea(g_lv.keyboard);
|
||||
lv_obj_t* ta = (ta_focused && s_nav_group && lv_group_get_focused(s_nav_group) == ta_focused) ? ta_focused : nullptr;
|
||||
if (!ta || !lv_obj_is_valid(ta)) return;
|
||||
txtMenuHide();
|
||||
accentBoxHide(); // a pasted word must not leave a stale accent popup for the last typed letter
|
||||
const bool ok = txtPasteInto(ta);
|
||||
if (g_lv.task) {
|
||||
g_lv.task->showAlert(ok ? TR("Pasted") : TR("Clipboard empty"), ok ? 900 : 1500);
|
||||
g_lv.task->noteUserInput();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
static void updatePagerAltBackspaceChord() {
|
||||
if (!pagerKeyboardConsumeAltBackspaceChord()) return;
|
||||
// The accent/@-mention pickers live on lv_layer_top(), outside the tab
|
||||
@@ -74276,6 +74307,9 @@ void UITask::loop() {
|
||||
pagerKeyboardDiscardAlt();
|
||||
pagerKeyboardConsumeAltShiftChord();
|
||||
pagerKeyboardConsumeAltBackspaceChord();
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
pagerKeyboardConsumeAltVChord();
|
||||
#endif
|
||||
#if defined(HAS_TDECK_MAX)
|
||||
pagerKeyboardConsumeBothShiftChord();
|
||||
#endif
|
||||
@@ -74301,6 +74335,9 @@ void UITask::loop() {
|
||||
pagerKeyboardDiscardAlt();
|
||||
pagerKeyboardConsumeAltShiftChord();
|
||||
pagerKeyboardConsumeAltBackspaceChord();
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
pagerKeyboardConsumeAltVChord();
|
||||
#endif
|
||||
#if defined(HAS_TDECK_MAX)
|
||||
pagerKeyboardConsumeBothShiftChord();
|
||||
#endif
|
||||
@@ -74339,6 +74376,9 @@ void UITask::loop() {
|
||||
}
|
||||
updatePagerAltShiftChord();
|
||||
updatePagerAltBackspaceChord();
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
updatePagerAltVChord();
|
||||
#endif
|
||||
#if defined(HAS_TDECK_PRO) && !defined(HAS_TDECK_MAX)
|
||||
updateProAltBChord();
|
||||
#endif
|
||||
|
||||
@@ -63,6 +63,30 @@ int main() {
|
||||
assert(keyboard.event(0, true, 740) == 'q');
|
||||
keyboard.event(0, false, 750);
|
||||
|
||||
// Physical Fn+V = paste chord: V is swallowed, the chord is reported once,
|
||||
// and nothing leaks into the next plain keypress.
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 760);
|
||||
assert(keyboard.event(PagerKeyboardState::V_POS, true, 770) == 0);
|
||||
assert(keyboard.consumeAltVChord());
|
||||
assert(!keyboard.consumeAltVChord());
|
||||
keyboard.event(PagerKeyboardState::V_POS, false, 780);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 790); // used-while-held: no one-shot armed
|
||||
assert(keyboard.event(0, true, 792) == 'q');
|
||||
keyboard.event(0, false, 794);
|
||||
// Tap Fn, then V (one-shot symbol layer) still types '?' -- not a chord.
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 795);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 796);
|
||||
assert(keyboard.event(PagerKeyboardState::V_POS, true, 797) == '?');
|
||||
assert(!keyboard.consumeAltVChord());
|
||||
keyboard.event(PagerKeyboardState::V_POS, false, 798);
|
||||
// discardAlt() drops a pending chord too.
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 799);
|
||||
keyboard.event(PagerKeyboardState::V_POS, true, 799);
|
||||
keyboard.discardAlt();
|
||||
assert(!keyboard.consumeAltVChord());
|
||||
keyboard.event(PagerKeyboardState::V_POS, false, 799);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 799);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 800);
|
||||
keyboard.discardAlt();
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 810);
|
||||
|
||||
Reference in new issue
Block a user