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..c96b3208c 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h @@ -5,9 +5,8 @@ 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 +This is available to any interested modules (SingleMessageApplet, NotificationApplet etc.) via InkHUD::latestMessage We do still receive notifications from the text message module though, to know when a new message has arrived, and trigger the update. @@ -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..f7ddbb4df 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h @@ -3,11 +3,10 @@ /* Shows the latest incoming *Direct Message* (DM), as well as sender. -This compliments the threaded message applets +This complements 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 +This is available to any interested modules (SingleMessageApplet, NotificationApplet etc.) via InkHUD::latestMessage We do still receive notifications from the text message module though, to know when a new message has arrived, and trigger the update. @@ -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 4f514682e..f41800abe 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1171,7 +1171,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(); @@ -1378,7 +1377,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