diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index 153ca9f3e..d91607a7d 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -2,7 +2,7 @@ extends = arduino_base platform = # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 - platformio/ststm32@19.2.0 + platformio/ststm32@19.3.0 platform_packages = # TODO renovate platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip diff --git a/bin/device-update.sh b/bin/device-update.sh index ce0b5e434..2196d3af9 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash PYTHON=${PYTHON:-$(which python3 python|head -n 1)} CHANGE_MODE=false diff --git a/debian/meshtasticd.postinst b/debian/meshtasticd.postinst index fe0dbc332..d569cb43e 100755 --- a/debian/meshtasticd.postinst +++ b/debian/meshtasticd.postinst @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # postinst script for meshtasticd # # see: dh_installdeb(1) diff --git a/debian/meshtasticd.postrm b/debian/meshtasticd.postrm index bb2c32a5b..dc25680a8 100755 --- a/debian/meshtasticd.postrm +++ b/debian/meshtasticd.postrm @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # postrm script for meshtasticd # # see: dh_installdeb(1) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 9fa2bd612..b66bd36a6 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -14,7 +14,9 @@ #include "modules/AdminModule.h" #include "modules/CannedMessageModule.h" #include "modules/KeyVerificationModule.h" + #include "modules/TraceRouteModule.h" +#include extern uint16_t TFT_MESH; @@ -136,6 +138,22 @@ void menuHandler::TwelveHourPicker() screen->showOverlayBanner(bannerOptions); } +// Reusable confirmation prompt function +void menuHandler::showConfirmationBanner(const char *message, std::function onConfirm) +{ + static const char *confirmOptions[] = {"No", "Yes"}; + BannerOverlayOptions confirmBanner; + confirmBanner.message = message; + confirmBanner.optionsArrayPtr = confirmOptions; + confirmBanner.optionsCount = 2; + confirmBanner.bannerCallback = [onConfirm](int confirmSelected) -> void { + if (confirmSelected == 1) { + onConfirm(); + } + }; + screen->showOverlayBanner(confirmBanner); +} + void menuHandler::ClockFacePicker() { static const char *optionsArray[] = {"Back", "Digital", "Analog"}; @@ -312,7 +330,7 @@ void menuHandler::messageResponseMenu() void menuHandler::homeBaseMenu() { - enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep, enumEnd }; + enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Sleep, enumEnd }; static const char *optionsArray[enumEnd] = {"Back"}; static int optionsEnumArray[enumEnd] = {Back}; @@ -334,8 +352,6 @@ void menuHandler::homeBaseMenu() optionsArray[options] = "New Freetext Msg"; optionsEnumArray[options++] = Freetext; } - optionsArray[options] = "Bluetooth Toggle"; - optionsEnumArray[options++] = Bluetooth; BannerOverlayOptions bannerOptions; bannerOptions.message = "Home Action"; @@ -360,9 +376,6 @@ void menuHandler::homeBaseMenu() cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); } else if (selected == Freetext) { cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST); - } else if (selected == Bluetooth) { - menuQueue = bluetooth_toggle_menu; - screen->runNow(); } }; screen->showOverlayBanner(bannerOptions); @@ -399,7 +412,7 @@ void menuHandler::textMessageBaseMenu() void menuHandler::systemBaseMenu() { - enum optionsNumbers { Back, Notifications, ScreenOptions, PowerMenu, FrameToggles, Test, enumEnd }; + enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd }; static const char *optionsArray[enumEnd] = {"Back"}; static int optionsEnumArray[enumEnd] = {Back}; int options = 1; @@ -415,6 +428,9 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Frame Visiblity Toggle"; optionsEnumArray[options++] = FrameToggles; + optionsArray[options] = "Bluetooth Toggle"; + optionsEnumArray[options++] = Bluetooth; + optionsArray[options] = "Reboot/Shutdown"; optionsEnumArray[options++] = PowerMenu; @@ -444,6 +460,9 @@ void menuHandler::systemBaseMenu() } else if (selected == Test) { menuHandler::menuQueue = menuHandler::test_menu; screen->runNow(); + } else if (selected == Bluetooth) { + menuQueue = bluetooth_toggle_menu; + screen->runNow(); } else if (selected == Back && !test_enabled) { test_count++; if (test_count > 4) { diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 7f069493b..0efaa5618 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -45,6 +45,7 @@ class menuHandler static void LoraRegionPicker(uint32_t duration = 30000); static void loraMenu(); static void handleMenuSwitch(OLEDDisplay *display); + static void showConfirmationBanner(const char *message, std::function onConfirm); static void clockMenu(); static void TZPicker(); static void TwelveHourPicker(); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index ed930db41..b6cb1b0e3 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -40,6 +40,9 @@ extern ScanI2C::DeviceAddress cardkb_found; extern bool graphics::isMuted; static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto"; +static NodeNum lastDest = NODENUM_BROADCAST; +static uint8_t lastChannel = 0; +static bool lastDestSet = false; meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig; @@ -63,8 +66,18 @@ CannedMessageModule::CannedMessageModule() void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChannel) { + // Use the requested destination, unless it's "broadcast" and we have a previous node/channel + if (newDest == NODENUM_BROADCAST && lastDestSet) { + newDest = lastDest; + newChannel = lastChannel; + } dest = newDest; channel = newChannel; + lastDest = dest; + lastChannel = channel; + lastDestSet = true; + + // Rest of function unchanged... // Always select the first real canned message on activation int firstRealMsgIdx = 0; for (int i = 0; i < messagesCount; ++i) { @@ -84,10 +97,28 @@ void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChan notifyObservers(&e); } +void CannedMessageModule::LaunchRepeatDestination() +{ + if (!lastDestSet) { + LaunchWithDestination(NODENUM_BROADCAST, 0); + } else { + LaunchWithDestination(lastDest, lastChannel); + } +} + void CannedMessageModule::LaunchFreetextWithDestination(NodeNum newDest, uint8_t newChannel) { + // Use the requested destination, unless it's "broadcast" and we have a previous node/channel + if (newDest == NODENUM_BROADCAST && lastDestSet) { + newDest = lastDest; + newChannel = lastChannel; + } dest = newDest; channel = newChannel; + lastDest = dest; + lastChannel = channel; + lastDestSet = true; + runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; requestFocus(); UIFrameEvent e; @@ -479,6 +510,9 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event if (destIndex < static_cast(activeChannelIndices.size())) { dest = NODENUM_BROADCAST; channel = activeChannelIndices[destIndex]; + lastDest = dest; + lastChannel = channel; + lastDestSet = true; } else { int nodeIndex = destIndex - static_cast(activeChannelIndices.size()); if (nodeIndex >= 0 && nodeIndex < static_cast(filteredNodes.size())) { @@ -486,6 +520,10 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event if (selectedNode) { dest = selectedNode->num; channel = selectedNode->channel; + // Already saves here, but for clarity, also: + lastDest = dest; + lastChannel = channel; + lastDestSet = true; } } } @@ -595,8 +633,27 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // Normal canned message selection if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) { } else { + // Show confirmation dialog before sending canned message + NodeNum destNode = dest; + ChannelIndex chan = channel; +#if CANNED_MESSAGE_ADD_CONFIRMATION + graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() { + this->sendText(destNode, chan, current, false); + payload = runState; + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + currentMessageIndex = -1; + + // Notify UI to regenerate frame set and redraw + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + screen->forceDisplay(); + }); +#else payload = runState; runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; +#endif + // Do not immediately set runState; wait for confirmation handled = true; } } @@ -827,6 +884,9 @@ int CannedMessageModule::handleEmotePickerInput(const InputEvent *event) void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies) { + lastDest = dest; + lastChannel = channel; + lastDestSet = true; // === Prepare packet === meshtastic_MeshPacket *p = allocDataPacket(); p->to = dest; @@ -1711,7 +1771,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st // Text: split by words and wrap inside word if needed String text = token.second; pos = 0; - while (pos < text.length()) { + while (pos < static_cast(text.length())) { // Find next space (or end) int spacePos = text.indexOf(' ', pos); int endPos = (spacePos == -1) ? text.length() : spacePos + 1; // Include space diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 55a0a1185..5b0481ac7 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -59,6 +59,7 @@ class CannedMessageModule : public SinglePortModule, public Observable