From ab8820274e8818c7476b6b043c4951cece62679d Mon Sep 17 00:00:00 2001 From: AndrewIsFat Date: Mon, 27 Jul 2026 00:02:30 +1000 Subject: [PATCH] fix(nrf24): support hold-back exit in active tools --- src/modules/NRF24/nrf_common.cpp | 32 +++++++++++++ src/modules/NRF24/nrf_common.h | 11 +++++ src/modules/NRF24/nrf_jammer.cpp | 20 +++++++- src/modules/NRF24/nrf_mousejack.cpp | 71 ++++++++++++++++++++++------- src/modules/NRF24/nrf_spectrum.cpp | 3 +- 5 files changed, 118 insertions(+), 19 deletions(-) diff --git a/src/modules/NRF24/nrf_common.cpp b/src/modules/NRF24/nrf_common.cpp index 7578344f6..88487621f 100644 --- a/src/modules/NRF24/nrf_common.cpp +++ b/src/modules/NRF24/nrf_common.cpp @@ -1,11 +1,43 @@ #include "nrf_common.h" #include "../../core/bus_HAL.h" +#include "../../core/display.h" #include "../../core/mykeyboard.h" RF24 NRFradio(bruceConfigPins.NRF24_bus.io0, bruceConfigPins.NRF24_bus.cs); HardwareSerial NRFSerial = HardwareSerial(2); // Uses UART2 for External NRF's SPIClass *NRFSPI; +NRF_BACK_ACTION nrf_checkBackButton() { +#ifdef HAS_3_BUTTONS + if (PrevPress) { + const uint32_t pressStarted = millis(); + LongPress = true; + while (PrevPress) { + const uint32_t elapsed = millis() - pressStarted; + if (elapsed > 200) { + int sweep = 360 * (elapsed - 200) / 500; + if (sweep > 360) sweep = 360; + tft.drawArc( + tftWidth / 2, + tftHeight / 2, + 25, + 15, + 0, + sweep, + getColorVariation(bruceConfig.priColor), + bruceConfig.bgColor + ); + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + tft.drawArc(tftWidth / 2, tftHeight / 2, 25, 15, 0, 360, bruceConfig.bgColor, bruceConfig.bgColor); + LongPress = false; + return millis() - pressStarted > 700 ? NRF_BACK_EXIT : NRF_BACK_SHORT; + } +#endif + return check(EscPress) ? NRF_BACK_EXIT : NRF_BACK_NONE; +} + void nrf_info() { tft.fillScreen(bruceConfig.bgColor); tft.setTextSize(FM); diff --git a/src/modules/NRF24/nrf_common.h b/src/modules/NRF24/nrf_common.h index 3673ac01e..f631b6126 100644 --- a/src/modules/NRF24/nrf_common.h +++ b/src/modules/NRF24/nrf_common.h @@ -17,6 +17,12 @@ enum NRF24_MODE { NRF_MODE_UART, // 0b10 NRF_MODE_BOTH // 0b11 }; + +enum NRF_BACK_ACTION : uint8_t { + NRF_BACK_NONE, + NRF_BACK_SHORT, + NRF_BACK_EXIT, +}; #define CHECK_NRF_SPI(mode) (mode & NRF_MODE_SPI) #define CHECK_NRF_UART(mode) (mode & NRF_MODE_UART) #define CHECK_NRF_BOTH(mode) (mode == NRF_MODE_BOTH) @@ -28,5 +34,10 @@ NRF24_MODE nrf_setMode(); bool nrf_start(NRF24_MODE mode); +// Handles the standard Bruce Back gesture used inside active NRF24 screens. +// On three-button devices, a short Back press is reported separately and a +// 700 ms hold draws the normal progress ring before requesting an exit. +NRF_BACK_ACTION nrf_checkBackButton(); + void nrf_info(); #endif diff --git a/src/modules/NRF24/nrf_jammer.cpp b/src/modules/NRF24/nrf_jammer.cpp index a80bb4f67..1db760a3a 100644 --- a/src/modules/NRF24/nrf_jammer.cpp +++ b/src/modules/NRF24/nrf_jammer.cpp @@ -144,7 +144,11 @@ void nrf_jammer() { padprintln(""); padprintln("> Switch Mode: Next/Prev"); padprintln("> Hop Mode: Sel"); +#ifdef HAS_3_BUTTONS + padprintln("> Exit: Hold Back"); +#else padprintln("> Exit: Esc"); +#endif tft.drawRoundRect(5, 5, tftWidth - 10, tftHeight - 10, 5, bruceConfig.priColor); if ((CHECK_NRF_UART(mode)) || (CHECK_NRF_BOTH(mode))) { @@ -181,7 +185,15 @@ void nrf_jammer() { need_reshuffle = true; redraw = true; } - if (check(PrevPress)) { + bool previousMode = false; +#ifdef HAS_3_BUTTONS + NRF_BACK_ACTION backAction = nrf_checkBackButton(); + if (backAction == NRF_BACK_EXIT) break; + previousMode = backAction == NRF_BACK_SHORT; +#else + previousMode = check(PrevPress); +#endif + if (previousMode) { modeIndex--; if (modeIndex < 0) modeIndex = (sizeof(modes) / sizeof(modes[0])) - 1; hopIndex = 0; @@ -198,7 +210,11 @@ void nrf_jammer() { } } - if (CHECK_NRF_SPI(mode)) NRFradio.stopConstCarrier(); + if (CHECK_NRF_SPI(mode)) { + NRFradio.stopConstCarrier(); + digitalWrite(bruceConfigPins.NRF24_bus.io0, LOW); + NRFradio.powerDown(); + } if ((CHECK_NRF_UART(mode)) || (CHECK_NRF_BOTH(mode))) { NRFSerial.println("OFF"); } } else { diff --git a/src/modules/NRF24/nrf_mousejack.cpp b/src/modules/NRF24/nrf_mousejack.cpp index 4dd6a9dce..181058311 100644 --- a/src/modules/NRF24/nrf_mousejack.cpp +++ b/src/modules/NRF24/nrf_mousejack.cpp @@ -28,6 +28,7 @@ static MjTarget mj_targets[MJ_MAX_TARGETS]; static uint8_t mj_targetCount = 0; static uint16_t mj_msSequence = 0; static NRF24_MODE mj_nrfMode = NRF_MODE_SPI; +static bool mj_scanCancelled = false; // ── ASCII to HID scancode lookup table ────────────────────────── // Maps ASCII 0x20-0x7E to {modifier, keycode} @@ -465,9 +466,9 @@ static void mj_sendKeystroke(const MjTarget &target, uint8_t modifier, uint8_t k } // ── Type a string as keystrokes ───────────────────────────────── -static void mj_typeString(const MjTarget &target, const char *text) { +static bool mj_typeString(const MjTarget &target, const char *text) { for (size_t i = 0; text[i] != '\0'; i++) { - if (check(EscPress)) return; + if (nrf_checkBackButton() == NRF_BACK_EXIT) return false; MjHidKey entry; char c = text[i]; @@ -483,6 +484,7 @@ static void mj_typeString(const MjTarget &target, const char *text) { mj_sendKeystroke(target, entry.modifier, entry.keycode); delay(ATTACK_INTER_KEY_MS); } + return true; } // ── DuckyScript line parser ───────────────────────────────────── @@ -491,7 +493,13 @@ static bool mj_parseDuckyLine(const String &line, const MjTarget &target) { if (line.startsWith("DELAY ") || line.startsWith("DELAY\t")) { int delayMs = line.substring(6).toInt(); - if (delayMs > 0 && delayMs <= 60000) delay(delayMs); + if (delayMs > 0 && delayMs <= 60000) { + const uint32_t delayStarted = millis(); + while (millis() - delayStarted < (uint32_t)delayMs) { + if (nrf_checkBackButton() == NRF_BACK_EXIT) return false; + vTaskDelay(pdMS_TO_TICKS(10)); + } + } return true; } @@ -500,12 +508,11 @@ static bool mj_parseDuckyLine(const String &line, const MjTarget &target) { } if (line.startsWith("STRING ")) { - mj_typeString(target, line.substring(7).c_str()); - return true; + return mj_typeString(target, line.substring(7).c_str()); } if (line.startsWith("STRINGLN ")) { - mj_typeString(target, line.substring(9).c_str()); + if (!mj_typeString(target, line.substring(9).c_str())) return false; mj_sendKeystroke(target, MJ_MOD_NONE, MJ_KEY_ENTER); return true; } @@ -633,11 +640,16 @@ static void mj_drawScanScreen(uint8_t currentCh, bool initial) { int footerY = tftHeight - BORDER_PAD_X - FP * LH - 2; tft.fillRect(7, footerY, tftWidth - 14, FP * LH, bruceConfig.bgColor); tft.setTextColor(TFT_DARKGREY, bruceConfig.bgColor); +#ifdef HAS_3_BUTTONS + tft.drawCentreString("Hold Back: Stop", tftWidth / 2, footerY, 1); +#else tft.drawCentreString("[ESC] Stop", tftWidth / 2, footerY, 1); +#endif } // ── Scanning function ─────────────────────────────────────────── static bool mj_scan() { + mj_scanCancelled = false; mj_targetCount = 0; memset(mj_targets, 0, sizeof(mj_targets)); @@ -677,7 +689,8 @@ static bool mj_scan() { while (scanning) { // Sweep channels 2-84 (ESB range) for (uint8_t ch = 2; ch <= 84 && scanning; ch++) { - if (check(EscPress)) { + if (nrf_checkBackButton() == NRF_BACK_EXIT) { + mj_scanCancelled = true; scanning = false; break; } @@ -784,10 +797,11 @@ static void mj_attackString(int targetIndex) { } } - mj_typeString(target, text.c_str()); + bool completed = mj_typeString(target, text.c_str()); NRFradio.powerDown(); - displaySuccess("Injection complete", true); + displaySuccess(completed ? "Injection complete" : "Injection stopped", completed); + if (!completed) delay(500); } // ── Attack: DuckyScript from SD Card ──────────────────────────── @@ -850,9 +864,13 @@ static void mj_attackDucky(int targetIndex) { uint16_t defaultDelayMs = 0; String lastLine; + bool completed = true; while (file.available()) { - if (check(EscPress)) break; + if (nrf_checkBackButton() == NRF_BACK_EXIT) { + completed = false; + break; + } String line = file.readStringUntil('\n'); line.trim(); @@ -871,21 +889,42 @@ static void mj_attackDucky(int targetIndex) { if (reps < 1) reps = 1; if (reps > 500) reps = 500; for (int r = 0; r < reps; r++) { - if (check(EscPress)) break; - if (lastLine.length() > 0) { mj_parseDuckyLine(lastLine, target); } + if (nrf_checkBackButton() == NRF_BACK_EXIT) { + completed = false; + break; + } + if (lastLine.length() > 0 && !mj_parseDuckyLine(lastLine, target)) { + completed = false; + break; + } } + if (!completed) break; continue; } - mj_parseDuckyLine(line, target); + if (!mj_parseDuckyLine(line, target)) { + completed = false; + break; + } lastLine = line; - if (defaultDelayMs > 0) delay(defaultDelayMs); + if (defaultDelayMs > 0) { + const uint32_t delayStarted = millis(); + while (millis() - delayStarted < defaultDelayMs) { + if (nrf_checkBackButton() == NRF_BACK_EXIT) { + completed = false; + break; + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + if (!completed) break; + } } file.close(); NRFradio.powerDown(); - displaySuccess("Script complete", true); + displaySuccess(completed ? "Script complete" : "Script stopped", completed); + if (!completed) delay(500); } // ══════════════════════════════════════════════════════════════════ @@ -943,7 +982,7 @@ void nrf_mousejack() { [=]() { if (mj_scan()) { mj_targetListMenu(); - } else { + } else if (!mj_scanCancelled) { displayInfo("No devices found", true); } } }, diff --git a/src/modules/NRF24/nrf_spectrum.cpp b/src/modules/NRF24/nrf_spectrum.cpp index 5c9d865ec..f90462558 100644 --- a/src/modules/NRF24/nrf_spectrum.cpp +++ b/src/modules/NRF24/nrf_spectrum.cpp @@ -76,7 +76,8 @@ void nrf_spectrum() { for (uint8_t i = 0; i < 6; ++i) { NRFradio.openReadingPipe(i, noiseAddress[i]); } NRFradio.setDataRate(RF24_1MBPS); - while (!check(EscPress)) { + while (true) { + if (nrf_checkBackButton() == NRF_BACK_EXIT) break; scanChannels(); vTaskDelay(pdMS_TO_TICKS(1)); }