mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-09-30 19:38:49 +00:00
fix m9 navigation
This commit is contained in:
+134
-61
@@ -28005,16 +28005,99 @@ static void serviceLockingCountdown(unsigned long now) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAS_M9_KEYBOARD)
|
||||
// ThinkNode M9 has no touchscreen and no navPump() (that's Tanmatsu-only) — the
|
||||
// d-pad through handleHwKey() is its ONLY input, including during first-boot
|
||||
// setup. T-Deck's handleHwKey() assumes touch drives the wizard and swallows
|
||||
// every non-editing key while s_setup_root is up (see the check just below
|
||||
// this function); M9 can't inherit that assumption, so its nav keys are
|
||||
// handled HERE, in their own function, called before that swallow — not
|
||||
// wedged into T-Deck's CAP_TRACKBALL chain as an #elif. Returns true if the
|
||||
// key was consumed.
|
||||
static bool m9HandleNavKey(int key) {
|
||||
if (!s_kbd_nav) return false;
|
||||
switch (key) {
|
||||
case M9_KEY_ENTER:
|
||||
if (navOnTabBar()) navSwitchTab(+1); else navPushTap(LV_KEY_ENTER);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return true;
|
||||
case M9_KEY_HW_BACK:
|
||||
if (anyPopupOpen()) hwKeyDismissTopPopup();
|
||||
else if (LvChatPanel* cp = navOpenChatPanel()) closeChatPanel(cp);
|
||||
else navPushTap(LV_KEY_ESC);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return true;
|
||||
case M9_KEY_HOME:
|
||||
if (s_setup_root) return true;
|
||||
// Close everything on top first (same idiom navGoToMainTab() already uses), THEN still
|
||||
// check whether we need to jump tabs — dismissing a popup isn't a reason to stop early;
|
||||
// Settings > Auto-add (reached via the Settings TAB, not the Home-tab drawer) needs both
|
||||
// steps: close the overlay, then actually jump back to Home.
|
||||
for (int i = 0; i < 8 && anyPopupOpen(); i++) hwKeyDismissTopPopup();
|
||||
if (getActiveTab() == HOME_TAB_INDEX) setHomeDrawer(!s_home_drawer_mode);
|
||||
else navGoToMainTab(HOME_TAB_INDEX);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return true;
|
||||
if (getActiveTab() == HOME_TAB_INDEX) setHomeDrawer(!s_home_drawer_mode);
|
||||
else navGoToMainTab(HOME_TAB_INDEX);
|
||||
if (g_lv.task) g_lv.task->noteUserInput(); return true;
|
||||
case M9_KEY_LEFT_MESSAGE:
|
||||
case M9_KEY_SUB_MESSAGE:
|
||||
if (s_setup_root) return true;
|
||||
navGoToMainTab(CHAT_INBOX_TAB_INDEX);
|
||||
if (g_lv.task) g_lv.task->noteUserInput(); return true;
|
||||
case M9_KEY_MAP:
|
||||
case M9_KEY_SUB_MAP:
|
||||
if (s_setup_root) return true;
|
||||
navGoToMainTab(MAP_TAB_INDEX);
|
||||
if (g_lv.task) g_lv.task->noteUserInput(); return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAS_M9_KEYBOARD)
|
||||
static bool m9HandleArrowKey(int key, lv_obj_t* ta) {
|
||||
if (!s_kbd_nav) return false;
|
||||
switch (key) {
|
||||
case M9_KEY_UP:
|
||||
navMoveDir(NAV_UP);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return true;
|
||||
case M9_KEY_DOWN:
|
||||
navMoveDir(NAV_DOWN);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return true;
|
||||
case M9_KEY_LEFT:
|
||||
if (ta) lv_textarea_cursor_left(ta);
|
||||
else if (navOnTabBar()) navSwitchTab(-1);
|
||||
else navMoveDir(NAV_LEFT);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return true;
|
||||
case M9_KEY_RIGHT:
|
||||
if (ta) lv_textarea_cursor_right(ta);
|
||||
else if (navOnTabBar()) navSwitchTab(+1);
|
||||
else navMoveDir(NAV_RIGHT);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Route a physical-keyboard character into the textarea currently being edited
|
||||
// — the on-screen keyboard's target, which is the chat composer directly or the
|
||||
// settings mirror textarea. No field open -> the key is ignored.
|
||||
static void handleHwKey(int key) {
|
||||
if (!g_lv.keyboard) return;
|
||||
// Hard-locked: any key just reveals the lock screen (lights the panel). It
|
||||
// never types or switches tabs — only a trackball *hold* unlocks.
|
||||
if (g_lv.task && g_lv.task->isManualLock()) { g_lv.task->lockscreenReveal(); return; }
|
||||
#if defined(HAS_M9_KEYBOARD)
|
||||
// M9 has no touch to wake the screen the way T-Deck/Heltec V4 do — mirror Tanmatsu's
|
||||
// navPump() pattern: any key wakes it (noteUserInput() wakes internally when the screen
|
||||
// is off), then THIS keypress is swallowed rather than also acted on, so waking doesn't
|
||||
// also navigate or type.
|
||||
if (g_lv.task && g_lv.task->isScreenOff()) { g_lv.task->noteUserInput(); return; }
|
||||
#else
|
||||
// Idle-dimmed (not hard-locked): ignore keys; a touch/click wakes into the UI.
|
||||
if (g_lv.task && g_lv.task->isScreenOff()) return;
|
||||
#endif
|
||||
#if CAP_TRACKBALL
|
||||
// Remapping a tab hotkey (Settings → Keyboard): capture the next key press.
|
||||
if (s_navkey_capture >= 0) { navKeyCaptureApply(key); return; }
|
||||
@@ -28034,21 +28117,21 @@ static void handleHwKey(int key) {
|
||||
lv_obj_t* ta = (ta_focused && (!s_kbd_nav || s_nav_ta_editing)) ? ta_focused : nullptr;
|
||||
#else
|
||||
lv_obj_t* ta = ta_focused;
|
||||
#endif
|
||||
#if defined(HAS_M9_KEYBOARD)
|
||||
if (m9HandleArrowKey(key, ta)) return; // ← runs BEFORE the if(!ta) split
|
||||
#endif
|
||||
if (!ta) {
|
||||
#if CAP_KEYPAD_NAV
|
||||
// A field is focused but we're in navigate mode: select/Enter starts editing it, so the
|
||||
// nav keys keep navigating until you explicitly enter the field (matches navPump).
|
||||
// M9_KEY_ENTER == '\r' (0x0D) — the M9 d-pad's centre/Enter byte already satisfies this
|
||||
// check without any extra case.
|
||||
if (ta_focused && s_kbd_nav && (key == '\r' || key == '\n' || navDirForKey(key) == 4)) {
|
||||
if (navFocusedTextarea() && s_kbd_nav && (key == '\r' || key == '\n' || navDirForKey(key) == 4)) {
|
||||
s_nav_ta_editing = true;
|
||||
if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// First-boot setup owns the screen: with no field focused, swallow the key
|
||||
// so it can't switch the (hidden) tabs behind the wizard or arm the lock.
|
||||
#if defined(HAS_M9_KEYBOARD)
|
||||
if (m9HandleNavKey(key)) return; // ← runs INSIDE the if(!ta) block only
|
||||
#endif
|
||||
if (s_setup_root) return;
|
||||
#if defined(HAS_TDECK_KEYBOARD) || defined(HAS_M9_KEYBOARD)
|
||||
// Spacebar (while NOT editing a text field) locks the screen: backlight off
|
||||
@@ -28106,56 +28189,6 @@ static void handleHwKey(int key) {
|
||||
}
|
||||
if (on_textfield) { if (g_lv.task) g_lv.task->noteUserInput(); return; } // field focused: never tab-jump on a letter
|
||||
}
|
||||
#elif defined(HAS_M9_KEYBOARD)
|
||||
// ThinkNode M9: the d-pad and dedicated function keys are FIXED hardware
|
||||
// buttons (not remappable letters), so this drives navMoveDir/navSwitchTab
|
||||
// directly off the raw keycodes (M9Keyboard.h) instead of going through
|
||||
// navDirForKey's programmable letter table — same destination primitives
|
||||
// the T-Deck's WASDZ block and Tanmatsu's navPump() use, just a different
|
||||
// (fixed) source mapping. s_kbd_nav is always true on this board (no touch
|
||||
// to fall back to — set at boot, see UITask.cpp's indev-registration block).
|
||||
if (s_kbd_nav) {
|
||||
switch (key) {
|
||||
case M9_KEY_UP: navMoveDir(NAV_UP); s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
case M9_KEY_DOWN: navMoveDir(NAV_DOWN); s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
case M9_KEY_LEFT:
|
||||
if (navOnTabBar()) navSwitchTab(-1); else navMoveDir(NAV_LEFT);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
case M9_KEY_RIGHT:
|
||||
if (navOnTabBar()) navSwitchTab(+1); else navMoveDir(NAV_RIGHT);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
// M9_KEY_ENTER (0x0D) when a field is focused-but-not-editing is handled above
|
||||
// (== '\r', starts editing). Reaching this switch means no field is focused —
|
||||
// "select" on whatever's focused: tab bar -> next tab, else activate it.
|
||||
case M9_KEY_ENTER:
|
||||
if (navOnTabBar()) navSwitchTab(+1); else navPushTap(LV_KEY_ENTER);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
case M9_KEY_HW_BACK: // back: popup → chat → ESC
|
||||
if (anyPopupOpen()) hwKeyDismissTopPopup();
|
||||
else if (LvChatPanel* cp = navOpenChatPanel()) closeChatPanel(cp); // close an open chat/channel first
|
||||
else navPushTap(LV_KEY_ESC);
|
||||
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
// Dedicated function keys jump straight to a main tab — mirrors the T-Deck's
|
||||
// programmable hotkeys, just fixed to these specific physical buttons instead
|
||||
// of remappable letters. HOME mirrors tapping the Home tab (toggles the app
|
||||
// drawer when already there, like homeKeyActivate on Tanmatsu).
|
||||
case M9_KEY_HOME:
|
||||
if (getActiveTab() == HOME_TAB_INDEX) setHomeDrawer(!s_home_drawer_mode);
|
||||
else navGoToMainTab(HOME_TAB_INDEX);
|
||||
if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
case M9_KEY_LEFT_MESSAGE:
|
||||
case M9_KEY_SUB_MESSAGE:
|
||||
navGoToMainTab(CHAT_INBOX_TAB_INDEX);
|
||||
if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
case M9_KEY_MAP:
|
||||
case M9_KEY_SUB_MAP:
|
||||
navGoToMainTab(MAP_TAB_INDEX);
|
||||
if (g_lv.task) g_lv.task->noteUserInput(); return;
|
||||
// M9_KEY_MIC / M9_KEY_CTRL: no action bound yet — fall through and get
|
||||
// swallowed below (anyPopupOpen()/tabForKey() won't match these bytes either).
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Not editing a field. If a popup is up, the dismiss keys close it; on a
|
||||
// bare tab, the navigation keys jump between the bottom tabs.
|
||||
@@ -28174,6 +28207,22 @@ static void handleHwKey(int key) {
|
||||
return;
|
||||
}
|
||||
txtMenuHide(); // any keypress while editing dismisses an open edit menu
|
||||
#if defined(HAS_M9_KEYBOARD)
|
||||
// M9 has a dedicated hardware Back key — unlike the CAP_TRACKBALL board's backspace-on-
|
||||
// empty-field escape below, this doesn't require emptying the field first: Back always
|
||||
// drops out of edit mode back to navigate mode, the same job HW_BACK does everywhere else
|
||||
// on this board (m9HandleNavKey). Without this, M9's d-pad bytes (0xB4-0xB7, 0x86) don't
|
||||
// match backspace/space/Enter/printable-ASCII below and are silently swallowed — there was
|
||||
// no way out of edit mode at all except an empty-field backspace, which M9 doesn't get
|
||||
// (that escape is CAP_TRACKBALL-gated).
|
||||
if (key == M9_KEY_HW_BACK) {
|
||||
s_nav_ta_editing = false;
|
||||
s_nav_show = true;
|
||||
accentBoxHide();
|
||||
if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (key == 0x08 || key == 0x7F) { // backspace / delete
|
||||
uint32_t bs_s, bs_e;
|
||||
if (taHasSelection(ta, &bs_s, &bs_e)) { // highlighted text -> delete the whole selection
|
||||
@@ -28261,6 +28310,24 @@ static void handleHwKey(int key) {
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
#if CAP_KEYPAD_NAV
|
||||
// Keyboard/d-pad nav: Enter in a generic field ADVANCES rather than submits — with
|
||||
// multiple fields on one page (e.g. the wizard's Wi-Fi step: SSID + password) there's
|
||||
// no single "the field" to submit from, so Enter walks the focus group forward one
|
||||
// step (next field, or the Next/Finish button once fields run out), exactly like
|
||||
// Tanmatsu's navPump() already does elsewhere in this file
|
||||
// (`navFifoPush(ta ? LV_KEY_NEXT : LV_KEY_ENTER, down)`). Landing on that button with
|
||||
// s_nav_ta_editing now false means the NEXT Enter goes through the normal focused-
|
||||
// button click path instead of back into this typing branch — that's what submits.
|
||||
if (s_kbd_nav) {
|
||||
hideKb(); // still confirm: sync the mirror into the real field
|
||||
s_nav_ta_editing = false;
|
||||
s_nav_show = true;
|
||||
navPushTap(LV_KEY_NEXT);
|
||||
if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
hideKb(); // settings/other field: confirm (syncs into the real field) + unfocus
|
||||
}
|
||||
} else if (key >= 0x20 && key < 0x7F) { // printable ASCII
|
||||
@@ -31683,9 +31750,15 @@ static void setupFillRegionList() {
|
||||
|
||||
static void setupShowStep(int step) {
|
||||
if (!s_setup_root) return;
|
||||
#if defined(HAS_TANMATSU)
|
||||
// Unconditional, like every other navMarkDirty() call site in this file — it's a real
|
||||
// rebuild-flag on any CAP_KEYPAD_NAV board (Tanmatsu, M9) and a no-op stub otherwise
|
||||
// (see the two definitions near navMaybeRebuild). Previously gated to HAS_TANMATSU only,
|
||||
// which meant M9 skipped it: lv_obj_clean() below frees the step's buttons/textarea, and
|
||||
// LVGL's allocator can hand the freshly-created replacements the SAME addresses — so
|
||||
// navMaybeRebuild()'s address-based tree signature can come out unchanged and skip the
|
||||
// rebuild, leaving s_nav_group holding stale objects. That's the "Back/Next does nothing,
|
||||
// then navigation is dead" symptom.
|
||||
navMarkDirty(); // wizard step content swaps in place — force the keypad focus group to rebuild
|
||||
#endif
|
||||
hideKb();
|
||||
// The scan popup writes the chosen SSID through these borrowed pointers; drop
|
||||
// them before the Wi-Fi fields are freed by the clean below.
|
||||
|
||||
Reference in New Issue
Block a user