You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
update keyboard
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user