diff --git a/TLORA_PAGER_SHORTCUTS.md b/TLORA_PAGER_SHORTCUTS.md index bcc33ed..01ed566 100644 --- a/TLORA_PAGER_SHORTCUTS.md +++ b/TLORA_PAGER_SHORTCUTS.md @@ -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 | diff --git a/src/helpers/input/PagerKeyboard.cpp b/src/helpers/input/PagerKeyboard.cpp index 1e591a5..c552d5d 100644 --- a/src/helpers/input/PagerKeyboard.cpp +++ b/src/helpers/input/PagerKeyboard.cpp @@ -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(); diff --git a/src/helpers/input/PagerKeyboard.h b/src/helpers/input/PagerKeyboard.h index c61dffd..6ca810b 100644 --- a/src/helpers/input/PagerKeyboard.h +++ b/src/helpers/input/PagerKeyboard.h @@ -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. */ diff --git a/src/helpers/input/PagerKeyboardState.h b/src/helpers/input/PagerKeyboardState.h index 7305175..f23d535 100644 --- a/src/helpers/input/PagerKeyboardState.h +++ b/src/helpers/input/PagerKeyboardState.h @@ -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; diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index 96d1f2b..d6b9573 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -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(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 diff --git a/test/test_pager_keyboard_state.cpp b/test/test_pager_keyboard_state.cpp index 8d428ae..4c9968c 100644 --- a/test/test_pager_keyboard_state.cpp +++ b/test/test_pager_keyboard_state.cpp @@ -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);