From fb6a212b449e41166e504befe1deda9bea27de60 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 14 Aug 2026 10:05:16 +0000 Subject: [PATCH] fix(graphics): make on-screen keyboard lifecycle safe and RAII-managed (#11460) - VirtualKeyboard::handleLongPress VK_ESC invoked the onTextEntered member std::function directly, but that callback path reaches OnScreenKeyboardModule::stop(), which destroys the keyboard - and with it the std::function whose invocation is still on the stack. handlePress and submitText already deliberately copy-and-clear before invoking for exactly this reason (CannedMessageModule documents the same hazard); do the same here. - OnScreenKeyboardModule's keyboard becomes unique_ptr, replacing the delete-in-destructor / delete-then-new-in-start / delete-in-stop bookkeeping that runs on every keyboard open/close. The NotificationRenderer legacy hook receives a non-owning raw pointer, as before. --- src/graphics/VirtualKeyboard.cpp | 8 +++++++- src/modules/OnScreenKeyboardModule.cpp | 23 +++++------------------ src/modules/OnScreenKeyboardModule.h | 3 ++- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp index fd06e0def..bdc827a0a 100644 --- a/src/graphics/VirtualKeyboard.cpp +++ b/src/graphics/VirtualKeyboard.cpp @@ -666,7 +666,13 @@ void VirtualKeyboard::handleLongPress() break; case VK_ESC: if (onTextEntered) { - onTextEntered(""); + // Copy-and-clear before invoking, like handlePress/submitText: the callback can + // destroy this keyboard (OnScreenKeyboardModule::stop), so the member must not be + // the std::function still executing on the stack. + std::function callback = onTextEntered; + onTextEntered = nullptr; + inputText = ""; + callback(""); } break; default: diff --git a/src/modules/OnScreenKeyboardModule.cpp b/src/modules/OnScreenKeyboardModule.cpp index ae2707cfe..3a9d498ed 100644 --- a/src/modules/OnScreenKeyboardModule.cpp +++ b/src/modules/OnScreenKeyboardModule.cpp @@ -18,22 +18,12 @@ OnScreenKeyboardModule &OnScreenKeyboardModule::instance() return inst; } -OnScreenKeyboardModule::~OnScreenKeyboardModule() -{ - if (keyboard) { - delete keyboard; - keyboard = nullptr; - } -} +OnScreenKeyboardModule::~OnScreenKeyboardModule() = default; void OnScreenKeyboardModule::start(const char *header, const char *initialText, uint32_t durationMs, std::function cb) { - if (keyboard) { - delete keyboard; - keyboard = nullptr; - } - keyboard = new VirtualKeyboard(); + keyboard = std::make_unique(); callback = cb; if (header) keyboard->setHeader(header); @@ -50,7 +40,7 @@ void OnScreenKeyboardModule::start(const char *header, const char *initialText, }); // Maintain legacy compatibility hooks - NotificationRenderer::virtualKeyboard = keyboard; + NotificationRenderer::virtualKeyboard = keyboard.get(); NotificationRenderer::textInputCallback = callback; } @@ -58,10 +48,7 @@ void OnScreenKeyboardModule::stop(bool callEmptyCallback) { auto cb = callback; callback = nullptr; - if (keyboard) { - delete keyboard; - keyboard = nullptr; - } + keyboard.reset(); // Keep NotificationRenderer legacy pointers in sync NotificationRenderer::virtualKeyboard = nullptr; NotificationRenderer::textInputCallback = nullptr; @@ -74,7 +61,7 @@ void OnScreenKeyboardModule::handleInput(const InputEvent &event) if (!keyboard) return; - if (processVirtualKeyboardInput(event, keyboard)) + if (processVirtualKeyboardInput(event, keyboard.get())) return; if (event.inputEvent == INPUT_BROKER_CANCEL) diff --git a/src/modules/OnScreenKeyboardModule.h b/src/modules/OnScreenKeyboardModule.h index 40dc23fae..555da432f 100644 --- a/src/modules/OnScreenKeyboardModule.h +++ b/src/modules/OnScreenKeyboardModule.h @@ -7,6 +7,7 @@ #include "graphics/VirtualKeyboard.h" #include #include +#include #include namespace graphics @@ -34,7 +35,7 @@ class OnScreenKeyboardModule void onSubmit(const std::string &text); void onCancel(); - VirtualKeyboard *keyboard = nullptr; + std::unique_ptr keyboard; std::function callback; };