From 2eaaf3d9627ca249d75ba3ef98f19cea345a98cd Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Sun, 2 Aug 2026 01:21:27 -0700 Subject: [PATCH] fix(ui): scope the deferred-BLE request state to the Pager bleRequestedOrEnabled() was guarded on ESP32 && MULTI_TRANSPORT_COMPANION, so it folded wifiConfigGetBleEnabled() into "BLE is on" for T-Deck, M9, V4-R8, Heltec V4 and RAK as well. Only the Pager has pending-request machinery: UITask::enableBle() records intent under #if TLORA_PAGER, and only main.cpp retries after association. Elsewhere that flag is a plain persisted preference that DEFAULTS TO ON and can sit true with no stack begun -- the boot co-init heap guard takes exactly that path. A T-Deck that deferred BLE at boot therefore showed the control-center tile ON with the stack dead, and the first tap called disableBle(), clearing the saved preference instead of starting BLE. The status indicators were untouched and still read isBleEnabled(), so the toggles disagreed with the "Starting..." label right next to them. Narrow the helper and the switch-revert branch to TLORA_PAGER, and build the Bluetooth switch's initial state from the same helper the callback compares against so its want == requested early-out cannot swallow a tap. Signed-off-by: Pixel Perfect --- src/ui-touch/UITask.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index cb4915b..0f6a64d 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -7906,9 +7906,17 @@ static const char* bleEnableFailureText() { return TR("Not enough free memory for Bluetooth. Turn Wi-Fi off first."); } +// "The user wants BLE on", covering the Pager's queued-behind-Wi-Fi request as +// well as a live stack. Scoped to the Pager on purpose: only UITask::enableBle() +// records a pending request (wifiConfigSetBleEnabled(true) on the deferral +// path), and only main.cpp retries one after association. Everywhere else +// wifiConfigGetBleEnabled() is a plain persisted preference that DEFAULTS TO ON +// and can sit true with no stack begun (the boot co-init heap guard, an OTA +// release) — folding that in there would make the toggles read ON while BLE is +// dead, and turn the first tap into "clear my preference" instead of "start it". static bool bleRequestedOrEnabled() { bool requested = g_lv.task && g_lv.task->isBleEnabled(); -#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION) +#if defined(TLORA_PAGER) && defined(MULTI_TRANSPORT_COMPANION) requested = requested || wifiConfigGetBleEnabled(); #endif return requested; @@ -13029,7 +13037,12 @@ static void bleEnableSwitchCb(lv_event_t* e) { if (!g_lv.task->enableBle()) { // A Pager request queued behind Wi-Fi is still ON as user intent. Keep it // checked so a second tap can cancel; only a true refusal reverts to OFF. - if (wifiConfigGetBleEnabled()) + // Other boards have no pending-request state, so a refusal always reverts. + bool still_requested = false; +#if defined(TLORA_PAGER) + still_requested = wifiConfigGetBleEnabled(); +#endif + if (still_requested) lv_obj_add_state(lv_event_get_target(e), LV_STATE_CHECKED); else lv_obj_clear_state(lv_event_get_target(e), LV_STATE_CHECKED); @@ -13146,7 +13159,9 @@ static void buildBluetoothSettings() { lv_obj_set_pos(sw_lbl, 2, y + 6); g_set_modal.wifi_sw = lv_switch_create(body); // reuse the same slot — only one switch lives in a modal at a time lv_obj_align(g_set_modal.wifi_sw, LV_ALIGN_TOP_RIGHT, 0, y); // flush right - if (ble_active || wifiConfigGetBleEnabled()) + // Must be the SAME predicate bleEnableSwitchCb compares against, or its + // `want == requested` early-out swallows the first tap. + if (ble_active || bleRequestedOrEnabled()) lv_obj_add_state(g_set_modal.wifi_sw, LV_STATE_CHECKED); lv_obj_add_event_cb(g_set_modal.wifi_sw, bleEnableSwitchCb, LV_EVENT_VALUE_CHANGED, nullptr); // instant toggle (BLE is live) y += SC(38);