update keyboard

This commit is contained in:
Thomas Farstrike
2025-11-17 07:20:47 +01:00
parent c9f6952971
commit b8a61d13b8
2 changed files with 173 additions and 35 deletions
+24 -35
View File
@@ -63,25 +63,12 @@ class MposKeyboard:
# Configure layouts
self._setup_layouts()
# Initialize ALL keyboard mode maps
# Register to BOTH our USER modes AND standard LVGL modes
# This prevents LVGL from using default maps when it internally switches modes
# Our USER modes (what we use in our API)
self._keyboard.set_map(self.MODE_LOWERCASE, self._lowercase_map, self._lowercase_ctrl)
self._keyboard.set_map(self.MODE_UPPERCASE, self._uppercase_map, self._uppercase_ctrl)
self._keyboard.set_map(self.MODE_NUMBERS, self._numbers_map, self._numbers_ctrl)
self._keyboard.set_map(self.MODE_SPECIALS, self._specials_map, self._specials_ctrl)
# ALSO register to standard LVGL modes (what LVGL uses internally)
# This catches cases where LVGL internally calls set_mode(TEXT_LOWER)
self._keyboard.set_map(lv.keyboard.MODE.TEXT_LOWER, self._lowercase_map, self._lowercase_ctrl)
self._keyboard.set_map(lv.keyboard.MODE.TEXT_UPPER, self._uppercase_map, self._uppercase_ctrl)
self._keyboard.set_map(lv.keyboard.MODE.NUMBER, self._numbers_map, self._numbers_ctrl)
self._keyboard.set_map(lv.keyboard.MODE.SPECIAL, self._specials_map, self._specials_ctrl)
# Set default mode to lowercase
self._keyboard.set_mode(self.MODE_LOWERCASE)
# IMPORTANT: We do NOT call set_map() here in __init__.
# Instead, set_mode() will call set_map() immediately before set_mode().
# This matches the proof-of-concept pattern and prevents crashes from
# calling set_map() multiple times which can corrupt button matrix state.
self.set_mode(self.MODE_LOWERCASE)
# Add event handler for custom behavior
# We need to handle ALL events to catch mode changes that LVGL might trigger
@@ -258,25 +245,27 @@ class MposKeyboard:
mode: One of MODE_LOWERCASE, MODE_UPPERCASE, MODE_NUMBERS, MODE_SPECIALS
(can also accept standard LVGL modes)
"""
# Map mode constants to their corresponding map arrays
# Support both our USER modes and standard LVGL modes
mode_maps = {
self.MODE_LOWERCASE: (self._lowercase_map, self._lowercase_ctrl),
self.MODE_UPPERCASE: (self._uppercase_map, self._uppercase_ctrl),
self.MODE_NUMBERS: (self._numbers_map, self._numbers_ctrl),
self.MODE_SPECIALS: (self._specials_map, self._specials_ctrl),
# Also map standard LVGL modes
lv.keyboard.MODE.TEXT_LOWER: (self._lowercase_map, self._lowercase_ctrl),
lv.keyboard.MODE.TEXT_UPPER: (self._uppercase_map, self._uppercase_ctrl),
lv.keyboard.MODE.NUMBER: (self._numbers_map, self._numbers_ctrl),
lv.keyboard.MODE.SPECIAL: (self._specials_map, self._specials_ctrl),
# Determine which layout we're switching to
# We need to set the map for BOTH the USER mode and the corresponding standard mode
# to prevent crashes if LVGL internally switches between them
mode_info = {
self.MODE_LOWERCASE: (self._lowercase_map, self._lowercase_ctrl, [self.MODE_LOWERCASE, lv.keyboard.MODE.TEXT_LOWER]),
self.MODE_UPPERCASE: (self._uppercase_map, self._uppercase_ctrl, [self.MODE_UPPERCASE, lv.keyboard.MODE.TEXT_UPPER]),
self.MODE_NUMBERS: (self._numbers_map, self._numbers_ctrl, [self.MODE_NUMBERS, lv.keyboard.MODE.NUMBER]),
self.MODE_SPECIALS: (self._specials_map, self._specials_ctrl, [self.MODE_SPECIALS, lv.keyboard.MODE.SPECIAL]),
# Also support standard LVGL modes
lv.keyboard.MODE.TEXT_LOWER: (self._lowercase_map, self._lowercase_ctrl, [self.MODE_LOWERCASE, lv.keyboard.MODE.TEXT_LOWER]),
lv.keyboard.MODE.TEXT_UPPER: (self._uppercase_map, self._uppercase_ctrl, [self.MODE_UPPERCASE, lv.keyboard.MODE.TEXT_UPPER]),
lv.keyboard.MODE.NUMBER: (self._numbers_map, self._numbers_ctrl, [self.MODE_NUMBERS, lv.keyboard.MODE.NUMBER]),
lv.keyboard.MODE.SPECIAL: (self._specials_map, self._specials_ctrl, [self.MODE_SPECIALS, lv.keyboard.MODE.SPECIAL]),
}
if mode in mode_maps:
key_map, ctrl_map = mode_maps[mode]
# CRITICAL: Always call set_map() BEFORE set_mode()
# This prevents lv_keyboard_update_map() crashes
self._keyboard.set_map(mode, key_map, ctrl_map)
if mode in mode_info:
key_map, ctrl_map, mode_list = mode_info[mode]
# CRITICAL: Set the map for BOTH modes to prevent NULL pointer crashes
# This ensures the map is set regardless of which mode LVGL uses internally
for m in mode_list:
self._keyboard.set_map(m, key_map, ctrl_map)
self._keyboard.set_mode(mode)
@@ -0,0 +1,149 @@
"""
Test to reproduce the lv_strcmp crash during keyboard mode switching.
The crash happens in buttonmatrix drawing code when map_p[txt_i] is NULL.
Usage:
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_crash_reproduction.py
"""
import unittest
import lvgl as lv
from mpos.ui.keyboard import MposKeyboard
from graphical_test_helper import wait_for_render
class TestKeyboardCrash(unittest.TestCase):
"""Test to reproduce keyboard crashes."""
def setUp(self):
"""Set up test fixtures."""
self.screen = lv.obj()
self.screen.set_size(320, 240)
lv.screen_load(self.screen)
wait_for_render(5)
def tearDown(self):
"""Clean up."""
lv.screen_load(lv.obj())
wait_for_render(5)
def test_rapid_mode_switching(self):
"""
Rapidly switch between modes to trigger the crash.
The crash occurs when btnm->map_p[txt_i] is NULL during drawing.
"""
print("\n=== Testing rapid mode switching ===")
textarea = lv.textarea(self.screen)
textarea.set_size(280, 40)
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
textarea.set_one_line(True)
wait_for_render(5)
keyboard = MposKeyboard(self.screen)
keyboard.set_textarea(textarea)
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
wait_for_render(10)
print("Rapidly switching modes...")
modes = [
MposKeyboard.MODE_LOWERCASE,
MposKeyboard.MODE_NUMBERS,
MposKeyboard.MODE_LOWERCASE,
MposKeyboard.MODE_UPPERCASE,
MposKeyboard.MODE_LOWERCASE,
MposKeyboard.MODE_NUMBERS,
MposKeyboard.MODE_SPECIALS,
MposKeyboard.MODE_NUMBERS,
MposKeyboard.MODE_LOWERCASE,
]
for i, mode in enumerate(modes):
print(f" Switch {i+1}: mode {mode}")
keyboard.set_mode(mode)
# Force rendering - this is where the crash happens
wait_for_render(2)
print("SUCCESS: No crash during rapid switching")
def test_mode_switching_with_standard_modes(self):
"""
Test switching using standard LVGL modes (TEXT_LOWER, etc).
This tests if LVGL internally switching modes causes the crash.
"""
print("\n=== Testing with standard LVGL modes ===")
textarea = lv.textarea(self.screen)
textarea.set_size(280, 40)
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
textarea.set_one_line(True)
wait_for_render(5)
keyboard = MposKeyboard(self.screen)
keyboard.set_textarea(textarea)
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
wait_for_render(10)
print("Switching using standard LVGL modes...")
# Try standard modes
print(" Switching to TEXT_LOWER")
keyboard._keyboard.set_mode(lv.keyboard.MODE.TEXT_LOWER)
wait_for_render(5)
print(" Switching to NUMBER")
keyboard._keyboard.set_mode(lv.keyboard.MODE.NUMBER)
wait_for_render(5)
print(" Switching back to TEXT_LOWER")
keyboard._keyboard.set_mode(lv.keyboard.MODE.TEXT_LOWER)
wait_for_render(5)
print("SUCCESS: No crash with standard modes")
def test_multiple_keyboards(self):
"""
Test creating multiple keyboards to see if that causes issues.
"""
print("\n=== Testing multiple keyboard creation ===")
textarea = lv.textarea(self.screen)
textarea.set_size(280, 40)
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
textarea.set_one_line(True)
wait_for_render(5)
# Create first keyboard
print("Creating keyboard 1...")
keyboard1 = MposKeyboard(self.screen)
keyboard1.set_textarea(textarea)
keyboard1.align(lv.ALIGN.BOTTOM_MID, 0, 0)
wait_for_render(10)
print("Switching modes on keyboard 1...")
keyboard1.set_mode(MposKeyboard.MODE_NUMBERS)
wait_for_render(5)
print("Deleting keyboard 1...")
keyboard1._keyboard.delete()
wait_for_render(5)
# Create second keyboard
print("Creating keyboard 2...")
keyboard2 = MposKeyboard(self.screen)
keyboard2.set_textarea(textarea)
keyboard2.align(lv.ALIGN.BOTTOM_MID, 0, 0)
wait_for_render(10)
print("Switching modes on keyboard 2...")
keyboard2.set_mode(MposKeyboard.MODE_UPPERCASE)
wait_for_render(5)
print("SUCCESS: Multiple keyboards work")
if __name__ == "__main__":
unittest.main()