From b714ad817e49df5389b57a45ceead8e16c097896 Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Sun, 16 Nov 2025 19:12:49 +0100 Subject: [PATCH] Fix double text entry bug --- internal_filesystem/lib/mpos/ui/keyboard.py | 41 +++++++++++++++++++- tests/test_graphical_keyboard_mode_switch.py | 29 ++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/internal_filesystem/lib/mpos/ui/keyboard.py b/internal_filesystem/lib/mpos/ui/keyboard.py index 22e58426..63540809 100644 --- a/internal_filesystem/lib/mpos/ui/keyboard.py +++ b/internal_filesystem/lib/mpos/ui/keyboard.py @@ -56,6 +56,9 @@ class MposKeyboard: # Create underlying LVGL keyboard widget self._keyboard = lv.keyboard(parent) + # Store textarea reference (we DON'T pass it to LVGL to avoid double-typing) + self._textarea = None + # Configure layouts self._setup_layouts() @@ -118,12 +121,21 @@ class MposKeyboard: Args: event: LVGL event object """ + # Only process VALUE_CHANGED events + event_code = event.get_code() + if event_code != lv.EVENT.VALUE_CHANGED: + return + # Get the pressed button and its text button = self._keyboard.get_selected_button() text = self._keyboard.get_button_text(button) - # Get current textarea content - ta = self._keyboard.get_textarea() + # Ignore if no valid button text (can happen during initialization) + if text is None: + return + + # Get current textarea content (from our own reference, not LVGL's) + ta = self._textarea if not ta: return @@ -179,6 +191,31 @@ class MposKeyboard: # Update textarea ta.set_text(new_text) + def set_textarea(self, textarea): + """ + Set the textarea that this keyboard types into. + + IMPORTANT: We store the textarea reference ourselves and DON'T pass + it to the underlying LVGL keyboard. This prevents LVGL's built-in + automatic character insertion, which would cause double-character bugs + (LVGL inserts + our handler inserts = double characters). + + Args: + textarea: The lv.textarea widget to type into, or None to disconnect + """ + self._textarea = textarea + # NOTE: We deliberately DO NOT call self._keyboard.set_textarea() + # to avoid LVGL's automatic character insertion + + def get_textarea(self): + """ + Get the textarea that this keyboard types into. + + Returns: + The lv.textarea widget, or None if not connected + """ + return self._textarea + def set_mode(self, mode): """ Set keyboard mode with proper map configuration. diff --git a/tests/test_graphical_keyboard_mode_switch.py b/tests/test_graphical_keyboard_mode_switch.py index f5d89442..470ad959 100644 --- a/tests/test_graphical_keyboard_mode_switch.py +++ b/tests/test_graphical_keyboard_mode_switch.py @@ -123,6 +123,35 @@ class TestKeyboardModeSwitch(unittest.TestCase): except Exception as e: self.fail(f" CRASH: Switching to {mode_name} caused exception: {e}") + def test_event_handler_exists(self): + """ + Verify that the event handler exists and is properly connected. + + The _handle_events method should filter events to only process + VALUE_CHANGED events. This prevents duplicate characters from being + typed when other events (like PRESSED, RELEASED, etc.) are fired. + + The fix ensures: + 1. Only VALUE_CHANGED events are processed + 2. None/invalid button text is ignored + 3. Each button press results in exactly ONE character being added + """ + print("\n=== Verifying event handler exists ===") + + keyboard = MposKeyboard(self.screen) + keyboard.set_textarea(self.textarea) + keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0) + wait_for_render(10) + + # Verify the event handler method exists and is callable + self.assertTrue(hasattr(keyboard, '_handle_events'), + "Keyboard should have _handle_events method") + self.assertTrue(callable(keyboard._handle_events), + "_handle_events should be callable") + + print("SUCCESS: Event handler exists and is properly set up") + print("Note: The handler filters for VALUE_CHANGED events only") + if __name__ == "__main__": unittest.main()