From d39bf2f8a08d30818d2332645715a8004d56931f Mon Sep 17 00:00:00 2001 From: HarukiToreda Date: Sun, 10 May 2026 20:44:28 -0400 Subject: [PATCH] cleanup --- src/MessageStore.cpp | 57 ++++---- src/MessageStore.h | 3 - src/graphics/Screen.cpp | 133 ------------------ src/graphics/Screen.h | 1 - src/graphics/draw/MenuHandler.cpp | 97 ++++++++++--- .../User/AllMessage/AllMessageApplet.h | 3 +- .../niche/InkHUD/Applets/User/DM/DMApplet.h | 3 +- src/graphics/niche/InkHUD/Events.cpp | 2 +- src/graphics/niche/InkHUD/Persistence.h | 3 +- src/graphics/niche/InkHUD/docs/README.md | 2 +- src/mesh/NodeDB.cpp | 2 - src/modules/TextMessageModule.cpp | 5 +- src/modules/TextMessageModule.h | 6 +- 13 files changed, 109 insertions(+), 208 deletions(-) diff --git a/src/MessageStore.cpp b/src/MessageStore.cpp index 22da418f5..0bd82c40b 100644 --- a/src/MessageStore.cpp +++ b/src/MessageStore.cpp @@ -6,7 +6,6 @@ #include "SPILock.h" #include "SafeFile.h" #include "gps/RTC.h" -#include "graphics/draw/MessageRenderer.h" #include // memcpy #ifndef MESSAGE_TEXT_POOL_SIZE @@ -181,13 +180,8 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa bool isDM = (sm.dest != 0 && sm.dest != NODENUM_BROADCAST); - if (packet.from == 0) { - sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST; - sm.ackStatus = AckStatus::NONE; - } else { - sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST; - sm.ackStatus = AckStatus::ACKED; - } + sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST; + sm.ackStatus = (packet.from == 0) ? AckStatus::NONE : AckStatus::ACKED; addLiveMessage(sm); @@ -372,26 +366,25 @@ void MessageStore::clearAllMessages() #endif } -// Internal helper: erase first or last message matching a predicate -template static void eraseIf(std::deque &deque, Predicate pred, bool fromBack = false) +// Internal helpers for targeted erasure. +template static bool eraseFirstMatch(std::deque &deque, Predicate pred) { - if (fromBack) { - // Iterate from the back and erase all matches from the end - for (auto it = deque.rbegin(); it != deque.rend();) { - if (pred(*it)) { - it = std::deque::reverse_iterator(deque.erase(std::next(it).base())); - } else { - ++it; - } + for (auto it = deque.begin(); it != deque.end(); ++it) { + if (pred(*it)) { + deque.erase(it); + return true; } - } else { - // Manual forward search to erase all matches - for (auto it = deque.begin(); it != deque.end();) { - if (pred(*it)) { - it = deque.erase(it); - } else { - ++it; - } + } + return false; +} + +template static void eraseAllMatches(std::deque &deque, Predicate pred) +{ + for (auto it = deque.begin(); it != deque.end();) { + if (pred(*it)) { + it = deque.erase(it); + } else { + ++it; } } } @@ -399,7 +392,9 @@ template static void eraseIf(std::deque &deq // Delete oldest message (RAM + persisted queue) void MessageStore::deleteOldestMessage() { - eraseIf(liveMessages, [](StoredMessage &) { return true; }); + if (!liveMessages.empty()) { + liveMessages.pop_front(); + } saveToFlash(); } @@ -407,14 +402,14 @@ void MessageStore::deleteOldestMessage() void MessageStore::deleteOldestMessageInChannel(uint8_t channel) { auto pred = [channel](const StoredMessage &m) { return m.type == MessageType::BROADCAST && m.channelIndex == channel; }; - eraseIf(liveMessages, pred); + eraseFirstMatch(liveMessages, pred); saveToFlash(); } void MessageStore::deleteAllMessagesInChannel(uint8_t channel) { auto pred = [channel](const StoredMessage &m) { return m.type == MessageType::BROADCAST && m.channelIndex == channel; }; - eraseIf(liveMessages, pred, false /* delete ALL, not just first */); + eraseAllMatches(liveMessages, pred); saveToFlash(); } @@ -427,7 +422,7 @@ void MessageStore::deleteAllMessagesWithPeer(uint32_t peer) uint32_t other = (m.sender == local) ? m.dest : m.sender; return other == peer; }; - eraseIf(liveMessages, pred, false); + eraseAllMatches(liveMessages, pred); saveToFlash(); } @@ -440,7 +435,7 @@ void MessageStore::deleteOldestMessageWithPeer(uint32_t peer) uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender; return other == peer; }; - eraseIf(liveMessages, pred); + eraseFirstMatch(liveMessages, pred); saveToFlash(); } diff --git a/src/MessageStore.h b/src/MessageStore.h index 77271f1c9..8f25c84f7 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -124,9 +124,6 @@ class MessageStore // Allocate text into pool (used by sender-side code) static uint16_t storeText(const char *src, size_t len); - // Used when loading from flash to rebuild the text pool - static uint16_t rebuildTextFromFlash(const char *src, size_t len); - private: std::deque liveMessages; // Single in-RAM message buffer (also used for persistence) std::string filename; // Flash filename for persistence diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 205e71bcf..80e00ed69 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -65,7 +65,6 @@ along with this program. If not, see . #include "mesh/Default.h" #include "mesh/generated/meshtastic/deviceonly.pb.h" #include "modules/ExternalNotificationModule.h" -#include "modules/TextMessageModule.h" #include "modules/WaypointModule.h" #include "sleep.h" #include "target_specific.h" @@ -1643,138 +1642,6 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) return 0; } -// Handles when message is received; will jump to text message frame. -int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) -{ - if (showingNormalScreen) { - if (packet->from == 0) { - // Outgoing message (likely sent from phone) - devicestate.has_rx_text_message = false; - memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); - hiddenFrames.textMessage = true; - hasUnreadMessage = false; // Clear unread state when user replies - - setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list - } else { - // Incoming message - devicestate.has_rx_text_message = true; // Needed to include the message frame - hasUnreadMessage = true; // Enables mail icon in the header - setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view (no-op during text_input) - - // Only wake/force display if the configuration allows it - if (shouldWakeOnReceivedMessage()) { - setOn(true); // Wake up the screen first - forceDisplay(); // Forces screen redraw - } - // === Prepare banner/popup content === - const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); - const meshtastic_Channel channel = - channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex()); - const char *longName = nodeInfoLiteHasUser(node) ? node->long_name : nullptr; - - const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes); - - char banner[256]; - - bool isAlert = false; - - if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra || - moduleConfig.external_notification.alert_bell_buzzer) - // Check for bell character to determine if this message is an alert - for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { - if (msgRaw[i] == ASCII_BELL) { - isAlert = true; - break; - } - } - - // Unlike generic messages, alerts (when enabled via the ext notif module) ignore any - // 'mute' preferences set to any specific node or channel. - // If on-screen keyboard is active, show a transient popup over keyboard instead of interrupting it - if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { - // Wake and force redraw so popup is visible immediately - if (shouldWakeOnReceivedMessage()) { - setOn(true); - forceDisplay(); - } - - // Build popup: title = message source name, content = message text (sanitized) - // Title - char titleBuf[64] = {0}; - if (longName && longName[0]) { - // Sanitize sender name - std::string t = sanitizeString(longName); - strncpy(titleBuf, t.c_str(), sizeof(titleBuf) - 1); - } else { - strncpy(titleBuf, "Message", sizeof(titleBuf) - 1); - } - - // Content: payload bytes may not be null-terminated, remove ASCII_BELL and sanitize - char content[256] = {0}; - { - std::string raw; - raw.reserve(packet->decoded.payload.size); - for (size_t i = 0; i < packet->decoded.payload.size; ++i) { - char c = msgRaw[i]; - if (c == ASCII_BELL) - continue; // strip bell - raw.push_back(c); - } - std::string sanitized = sanitizeString(raw); - strncpy(content, sanitized.c_str(), sizeof(content) - 1); - } - - NotificationRenderer::showKeyboardMessagePopupWithTitle(titleBuf, content, 3000); - -// Maintain existing buzzer behavior on M5 if applicable -#if defined(M5STACK_UNITC6L) - if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || - (isAlert && moduleConfig.external_notification.alert_bell_buzzer) || - (!isBroadcast(packet->to) && isToUs(packet))) { - playLongBeep(); - } -#endif - } else { - // No keyboard active: use regular banner flow, respecting mute settings - if (isAlert) { - if (longName && longName[0]) { - snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); - } else { - strcpy(banner, "Alert Received"); - } - screen->showSimpleBanner(banner, 3000); - } else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) { - if (longName && longName[0]) { - if (currentResolution == ScreenResolution::UltraLow) { - strcpy(banner, "New Message"); - } else { - snprintf(banner, sizeof(banner), "New Message from\n%s", longName); - } - } else { - strcpy(banner, "New Message"); - } -#if defined(M5STACK_UNITC6L) - screen->setOn(true); - screen->showSimpleBanner(banner, 1500); - if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || - (isAlert && moduleConfig.external_notification.alert_bell_buzzer) || - (!isBroadcast(packet->to) && isToUs(packet))) { - // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either - // - packet contains an alert and alert bell buzzer is enabled - // - packet is a non-broadcast that is addressed to this node - playLongBeep(); - } -#else - screen->showSimpleBanner(banner, 3000); -#endif - } - } - } - } - - return 0; -} - // Triggered by MeshModules int Screen::handleUIFrameEvent(const UIFrameEvent *event) { diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 401cba59e..c2d64e50d 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -609,7 +609,6 @@ class Screen : public concurrency::OSThread // Handle observer events int handleStatusUpdate(const meshtastic::Status *arg); - int handleTextMessage(const meshtastic_MeshPacket *packet); int handleUIFrameEvent(const UIFrameEvent *arg); int handleInputEvent(const InputEvent *arg); int handleAdminMessage(AdminModule_ObserverData *arg); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 382b89f2f..386a4c077 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -57,6 +57,70 @@ BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOp return bannerOptions; } +const StoredMessage *getNewestMessageForActiveThread() +{ + const auto &messages = messageStore.getMessages(); + if (messages.empty()) { + return nullptr; + } + + const auto mode = graphics::MessageRenderer::getThreadMode(); + const int channel = graphics::MessageRenderer::getThreadChannel(); + const uint32_t peer = graphics::MessageRenderer::getThreadPeer(); + const uint32_t localNode = nodeDB->getNodeNum(); + + if (mode == graphics::MessageRenderer::ThreadMode::ALL) { + return &messages.back(); + } + + for (auto it = messages.rbegin(); it != messages.rend(); ++it) { + const StoredMessage &m = *it; + + if (mode == graphics::MessageRenderer::ThreadMode::CHANNEL) { + if (m.type == MessageType::BROADCAST && static_cast(m.channelIndex) == channel) { + return &m; + } + continue; + } + + if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) { + if (m.type != MessageType::DM_TO_US) { + continue; + } + const uint32_t other = (m.sender == localNode) ? m.dest : m.sender; + if (other == peer) { + return &m; + } + } + } + + return nullptr; +} + +void launchReplyForMessage(const StoredMessage &message, bool freetext) +{ + if (message.type == MessageType::BROADCAST || message.dest == NODENUM_BROADCAST) { + if (freetext) { + cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST, message.channelIndex); + } else { + cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, message.channelIndex); + } + return; + } + + const uint32_t localNode = nodeDB->getNodeNum(); + const uint32_t peer = (message.sender == localNode) ? message.dest : message.sender; + if (peer == 0 || peer == NODENUM_BROADCAST) { + return; + } + + if (freetext) { + cannedMessageModule->LaunchFreetextWithDestination(peer); + } else { + cannedMessageModule->LaunchWithDestination(peer); + } +} + } // namespace menuHandler::screenMenus menuHandler::menuQueue = MenuNone; @@ -594,9 +658,12 @@ void menuHandler::messageResponseMenu() #ifdef HAS_I2S } else if (selected == Aloud) { - const meshtastic_MeshPacket &mp = devicestate.rx_text_message; - const char *msg = reinterpret_cast(mp.decoded.payload.bytes); - audioThread->readAloud(msg); + if (const StoredMessage *latest = getNewestMessageForActiveThread()) { + const char *msg = MessageStore::getText(*latest); + if (msg && msg[0]) { + audioThread->readAloud(msg); + } + } #endif } }; @@ -656,20 +723,12 @@ void menuHandler::replyMenu() // Preset reply if (selected == ReplyPreset) { - if (mode == graphics::MessageRenderer::ThreadMode::CHANNEL) { cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, ch); - } else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) { cannedMessageModule->LaunchWithDestination(peer); - - } else { - // Fallback for last received message - if (devicestate.rx_text_message.to == NODENUM_BROADCAST) { - cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel); - } else { - cannedMessageModule->LaunchWithDestination(devicestate.rx_text_message.from); - } + } else if (const StoredMessage *latest = getNewestMessageForActiveThread()) { + launchReplyForMessage(*latest, false); } return; @@ -677,20 +736,12 @@ void menuHandler::replyMenu() // Freetext reply if (selected == ReplyFreetext) { - if (mode == graphics::MessageRenderer::ThreadMode::CHANNEL) { cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST, ch); - } else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) { cannedMessageModule->LaunchFreetextWithDestination(peer); - - } else { - // Fallback for last received message - if (devicestate.rx_text_message.to == NODENUM_BROADCAST) { - cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel); - } else { - cannedMessageModule->LaunchFreetextWithDestination(devicestate.rx_text_message.from); - } + } else if (const StoredMessage *latest = getNewestMessageForActiveThread()) { + launchReplyForMessage(*latest, true); } return; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h index 4aa97e4f1..2ea812580 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h @@ -5,7 +5,6 @@ Shows the latest incoming text message, as well as sender. Both broadcast and direct messages will be shown here, from all channels. -This module doesn't doesn't use the devicestate.rx_text_message,' as this is overwritten to contain outgoing messages This module doesn't collect its own text message. Instead, the WindowManager stores the most recent incoming text message. This is available to any interested modules (SingeMessageApplet, NotificationApplet etc.) via InkHUD::latestMessage @@ -46,4 +45,4 @@ class AllMessageApplet : public Applet } // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h index 4eb0ec704..41765cd2d 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h @@ -5,7 +5,6 @@ Shows the latest incoming *Direct Message* (DM), as well as sender. This compliments the threaded message applets -This module doesn't doesn't use the devicestate.rx_text_message,' as this is overwritten to contain outgoing messages This module doesn't collect its own text message. Instead, the WindowManager stores the most recent incoming text message. This is available to any interested modules (SingeMessageApplet, NotificationApplet etc.) via InkHUD::latestMessage @@ -46,4 +45,4 @@ class DMApplet : public Applet } // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index ddcc6781b..d40cc2ca7 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -525,7 +525,7 @@ int InkHUD::Events::beforeReboot(void *unused) // Callback when a new text message is received // Caches the most recently received message, for use by applets // Rx does not trigger a save to flash, however the data *will* be saved alongside other during shutdown, etc. -// Note: this is different from devicestate.rx_text_message, which may contain an *outgoing* message +// Note: this is intentionally separate from device-state message fields. int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet) { // Short circuit: don't store outgoing messages diff --git a/src/graphics/niche/InkHUD/Persistence.h b/src/graphics/niche/InkHUD/Persistence.h index 187af2129..6f783cb1c 100644 --- a/src/graphics/niche/InkHUD/Persistence.h +++ b/src/graphics/niche/InkHUD/Persistence.h @@ -121,8 +121,7 @@ class Persistence // Most recently received text message // Value is updated by InkHUD::WindowManager, as a courtesy to applets - // Note: different from devicestate.rx_text_message, - // which may contain an *outgoing message* to broadcast + // InkHUD keeps its own latest-message cache for applets. struct LatestMessage { MessageStore::Message broadcast; // Most recent message received broadcast MessageStore::Message dm; // Most recent received DM diff --git a/src/graphics/niche/InkHUD/docs/README.md b/src/graphics/niche/InkHUD/docs/README.md index 7cd468d73..94af04fb2 100644 --- a/src/graphics/niche/InkHUD/docs/README.md +++ b/src/graphics/niche/InkHUD/docs/README.md @@ -464,7 +464,7 @@ Most recently received text message Collected here, so various user applets don't all have to store their own copy of this info. -We are unable to use `devicestate.rx_text_message` for this purpose, because: +We keep this separate latest-message cache for this purpose, because: - it is cleared by an outgoing text message - we want to store both a recent broadcast and a recent DM diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 46aa61b28..f2ba1c32b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1161,7 +1161,6 @@ void NodeDB::resetNodes(bool keepFavorites) std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite()); } (void)ourNum; - devicestate.has_rx_text_message = false; devicestate.has_rx_waypoint = false; saveNodeDatabaseToDisk(); saveDeviceStateToDisk(); @@ -1368,7 +1367,6 @@ void NodeDB::installDefaultDeviceState() devicestate.version = DEVICESTATE_CUR_VER; devicestate.receive_queue_count = 0; // Not yet implemented FIXME devicestate.has_rx_waypoint = false; - devicestate.has_rx_text_message = false; generatePacketId(); // FIXME - ugly way to init current_packet_id; diff --git a/src/modules/TextMessageModule.cpp b/src/modules/TextMessageModule.cpp index d94701c6b..275dec63d 100644 --- a/src/modules/TextMessageModule.cpp +++ b/src/modules/TextMessageModule.cpp @@ -21,9 +21,6 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp textPacketList[textPacketListIndex] = mp.id; textPacketListIndex = (textPacketListIndex + 1) % TEXT_PACKET_LIST_SIZE; - // We only store/display messages destined for us. - devicestate.rx_text_message = mp; - devicestate.has_rx_text_message = true; IF_SCREEN( // Guard against running in MeshtasticUI or with no screen if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { @@ -59,4 +56,4 @@ bool TextMessageModule::recentlySeen(uint32_t id) } } return false; -} \ No newline at end of file +} diff --git a/src/modules/TextMessageModule.h b/src/modules/TextMessageModule.h index 42900a78e..ea7083158 100644 --- a/src/modules/TextMessageModule.h +++ b/src/modules/TextMessageModule.h @@ -7,8 +7,8 @@ * Text message handling for Meshtastic. * * This module is responsible for receiving and storing incoming text messages - * from the mesh. It updates device state and notifies observers so that other - * components (such as the MessageRenderer) can later display or process them. + * from the mesh. It notifies observers so that other components (such as the + * MessageRenderer) can later display or process them. * * Rendering of messages on screen is no longer done here. */ @@ -36,4 +36,4 @@ class TextMessageModule : public SinglePortModule, public Observable