Skip to content

Commit b8a61d1

Browse files
update keyboard
1 parent c9f6952 commit b8a61d1

File tree

2 files changed

+173
-35
lines changed

2 files changed

+173
-35
lines changed

internal_filesystem/lib/mpos/ui/keyboard.py

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,12 @@ def __init__(self, parent):
6363
# Configure layouts
6464
self._setup_layouts()
6565

66-
# Initialize ALL keyboard mode maps
67-
# Register to BOTH our USER modes AND standard LVGL modes
68-
# This prevents LVGL from using default maps when it internally switches modes
69-
70-
# Our USER modes (what we use in our API)
71-
self._keyboard.set_map(self.MODE_LOWERCASE, self._lowercase_map, self._lowercase_ctrl)
72-
self._keyboard.set_map(self.MODE_UPPERCASE, self._uppercase_map, self._uppercase_ctrl)
73-
self._keyboard.set_map(self.MODE_NUMBERS, self._numbers_map, self._numbers_ctrl)
74-
self._keyboard.set_map(self.MODE_SPECIALS, self._specials_map, self._specials_ctrl)
75-
76-
# ALSO register to standard LVGL modes (what LVGL uses internally)
77-
# This catches cases where LVGL internally calls set_mode(TEXT_LOWER)
78-
self._keyboard.set_map(lv.keyboard.MODE.TEXT_LOWER, self._lowercase_map, self._lowercase_ctrl)
79-
self._keyboard.set_map(lv.keyboard.MODE.TEXT_UPPER, self._uppercase_map, self._uppercase_ctrl)
80-
self._keyboard.set_map(lv.keyboard.MODE.NUMBER, self._numbers_map, self._numbers_ctrl)
81-
self._keyboard.set_map(lv.keyboard.MODE.SPECIAL, self._specials_map, self._specials_ctrl)
82-
8366
# Set default mode to lowercase
84-
self._keyboard.set_mode(self.MODE_LOWERCASE)
67+
# IMPORTANT: We do NOT call set_map() here in __init__.
68+
# Instead, set_mode() will call set_map() immediately before set_mode().
69+
# This matches the proof-of-concept pattern and prevents crashes from
70+
# calling set_map() multiple times which can corrupt button matrix state.
71+
self.set_mode(self.MODE_LOWERCASE)
8572

8673
# Add event handler for custom behavior
8774
# We need to handle ALL events to catch mode changes that LVGL might trigger
@@ -258,25 +245,27 @@ def set_mode(self, mode):
258245
mode: One of MODE_LOWERCASE, MODE_UPPERCASE, MODE_NUMBERS, MODE_SPECIALS
259246
(can also accept standard LVGL modes)
260247
"""
261-
# Map mode constants to their corresponding map arrays
262-
# Support both our USER modes and standard LVGL modes
263-
mode_maps = {
264-
self.MODE_LOWERCASE: (self._lowercase_map, self._lowercase_ctrl),
265-
self.MODE_UPPERCASE: (self._uppercase_map, self._uppercase_ctrl),
266-
self.MODE_NUMBERS: (self._numbers_map, self._numbers_ctrl),
267-
self.MODE_SPECIALS: (self._specials_map, self._specials_ctrl),
268-
# Also map standard LVGL modes
269-
lv.keyboard.MODE.TEXT_LOWER: (self._lowercase_map, self._lowercase_ctrl),
270-
lv.keyboard.MODE.TEXT_UPPER: (self._uppercase_map, self._uppercase_ctrl),
271-
lv.keyboard.MODE.NUMBER: (self._numbers_map, self._numbers_ctrl),
272-
lv.keyboard.MODE.SPECIAL: (self._specials_map, self._specials_ctrl),
248+
# Determine which layout we're switching to
249+
# We need to set the map for BOTH the USER mode and the corresponding standard mode
250+
# to prevent crashes if LVGL internally switches between them
251+
mode_info = {
252+
self.MODE_LOWERCASE: (self._lowercase_map, self._lowercase_ctrl, [self.MODE_LOWERCASE, lv.keyboard.MODE.TEXT_LOWER]),
253+
self.MODE_UPPERCASE: (self._uppercase_map, self._uppercase_ctrl, [self.MODE_UPPERCASE, lv.keyboard.MODE.TEXT_UPPER]),
254+
self.MODE_NUMBERS: (self._numbers_map, self._numbers_ctrl, [self.MODE_NUMBERS, lv.keyboard.MODE.NUMBER]),
255+
self.MODE_SPECIALS: (self._specials_map, self._specials_ctrl, [self.MODE_SPECIALS, lv.keyboard.MODE.SPECIAL]),
256+
# Also support standard LVGL modes
257+
lv.keyboard.MODE.TEXT_LOWER: (self._lowercase_map, self._lowercase_ctrl, [self.MODE_LOWERCASE, lv.keyboard.MODE.TEXT_LOWER]),
258+
lv.keyboard.MODE.TEXT_UPPER: (self._uppercase_map, self._uppercase_ctrl, [self.MODE_UPPERCASE, lv.keyboard.MODE.TEXT_UPPER]),
259+
lv.keyboard.MODE.NUMBER: (self._numbers_map, self._numbers_ctrl, [self.MODE_NUMBERS, lv.keyboard.MODE.NUMBER]),
260+
lv.keyboard.MODE.SPECIAL: (self._specials_map, self._specials_ctrl, [self.MODE_SPECIALS, lv.keyboard.MODE.SPECIAL]),
273261
}
274262

275-
if mode in mode_maps:
276-
key_map, ctrl_map = mode_maps[mode]
277-
# CRITICAL: Always call set_map() BEFORE set_mode()
278-
# This prevents lv_keyboard_update_map() crashes
279-
self._keyboard.set_map(mode, key_map, ctrl_map)
263+
if mode in mode_info:
264+
key_map, ctrl_map, mode_list = mode_info[mode]
265+
# CRITICAL: Set the map for BOTH modes to prevent NULL pointer crashes
266+
# This ensures the map is set regardless of which mode LVGL uses internally
267+
for m in mode_list:
268+
self._keyboard.set_map(m, key_map, ctrl_map)
280269

281270
self._keyboard.set_mode(mode)
282271

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""
2+
Test to reproduce the lv_strcmp crash during keyboard mode switching.
3+
4+
The crash happens in buttonmatrix drawing code when map_p[txt_i] is NULL.
5+
6+
Usage:
7+
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_crash_reproduction.py
8+
"""
9+
10+
import unittest
11+
import lvgl as lv
12+
from mpos.ui.keyboard import MposKeyboard
13+
from graphical_test_helper import wait_for_render
14+
15+
16+
class TestKeyboardCrash(unittest.TestCase):
17+
"""Test to reproduce keyboard crashes."""
18+
19+
def setUp(self):
20+
"""Set up test fixtures."""
21+
self.screen = lv.obj()
22+
self.screen.set_size(320, 240)
23+
lv.screen_load(self.screen)
24+
wait_for_render(5)
25+
26+
def tearDown(self):
27+
"""Clean up."""
28+
lv.screen_load(lv.obj())
29+
wait_for_render(5)
30+
31+
def test_rapid_mode_switching(self):
32+
"""
33+
Rapidly switch between modes to trigger the crash.
34+
35+
The crash occurs when btnm->map_p[txt_i] is NULL during drawing.
36+
"""
37+
print("\n=== Testing rapid mode switching ===")
38+
39+
textarea = lv.textarea(self.screen)
40+
textarea.set_size(280, 40)
41+
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
42+
textarea.set_one_line(True)
43+
wait_for_render(5)
44+
45+
keyboard = MposKeyboard(self.screen)
46+
keyboard.set_textarea(textarea)
47+
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
48+
wait_for_render(10)
49+
50+
print("Rapidly switching modes...")
51+
modes = [
52+
MposKeyboard.MODE_LOWERCASE,
53+
MposKeyboard.MODE_NUMBERS,
54+
MposKeyboard.MODE_LOWERCASE,
55+
MposKeyboard.MODE_UPPERCASE,
56+
MposKeyboard.MODE_LOWERCASE,
57+
MposKeyboard.MODE_NUMBERS,
58+
MposKeyboard.MODE_SPECIALS,
59+
MposKeyboard.MODE_NUMBERS,
60+
MposKeyboard.MODE_LOWERCASE,
61+
]
62+
63+
for i, mode in enumerate(modes):
64+
print(f" Switch {i+1}: mode {mode}")
65+
keyboard.set_mode(mode)
66+
# Force rendering - this is where the crash happens
67+
wait_for_render(2)
68+
69+
print("SUCCESS: No crash during rapid switching")
70+
71+
def test_mode_switching_with_standard_modes(self):
72+
"""
73+
Test switching using standard LVGL modes (TEXT_LOWER, etc).
74+
75+
This tests if LVGL internally switching modes causes the crash.
76+
"""
77+
print("\n=== Testing with standard LVGL modes ===")
78+
79+
textarea = lv.textarea(self.screen)
80+
textarea.set_size(280, 40)
81+
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
82+
textarea.set_one_line(True)
83+
wait_for_render(5)
84+
85+
keyboard = MposKeyboard(self.screen)
86+
keyboard.set_textarea(textarea)
87+
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
88+
wait_for_render(10)
89+
90+
print("Switching using standard LVGL modes...")
91+
92+
# Try standard modes
93+
print(" Switching to TEXT_LOWER")
94+
keyboard._keyboard.set_mode(lv.keyboard.MODE.TEXT_LOWER)
95+
wait_for_render(5)
96+
97+
print(" Switching to NUMBER")
98+
keyboard._keyboard.set_mode(lv.keyboard.MODE.NUMBER)
99+
wait_for_render(5)
100+
101+
print(" Switching back to TEXT_LOWER")
102+
keyboard._keyboard.set_mode(lv.keyboard.MODE.TEXT_LOWER)
103+
wait_for_render(5)
104+
105+
print("SUCCESS: No crash with standard modes")
106+
107+
def test_multiple_keyboards(self):
108+
"""
109+
Test creating multiple keyboards to see if that causes issues.
110+
"""
111+
print("\n=== Testing multiple keyboard creation ===")
112+
113+
textarea = lv.textarea(self.screen)
114+
textarea.set_size(280, 40)
115+
textarea.align(lv.ALIGN.TOP_MID, 0, 10)
116+
textarea.set_one_line(True)
117+
wait_for_render(5)
118+
119+
# Create first keyboard
120+
print("Creating keyboard 1...")
121+
keyboard1 = MposKeyboard(self.screen)
122+
keyboard1.set_textarea(textarea)
123+
keyboard1.align(lv.ALIGN.BOTTOM_MID, 0, 0)
124+
wait_for_render(10)
125+
126+
print("Switching modes on keyboard 1...")
127+
keyboard1.set_mode(MposKeyboard.MODE_NUMBERS)
128+
wait_for_render(5)
129+
130+
print("Deleting keyboard 1...")
131+
keyboard1._keyboard.delete()
132+
wait_for_render(5)
133+
134+
# Create second keyboard
135+
print("Creating keyboard 2...")
136+
keyboard2 = MposKeyboard(self.screen)
137+
keyboard2.set_textarea(textarea)
138+
keyboard2.align(lv.ALIGN.BOTTOM_MID, 0, 0)
139+
wait_for_render(10)
140+
141+
print("Switching modes on keyboard 2...")
142+
keyboard2.set_mode(MposKeyboard.MODE_UPPERCASE)
143+
wait_for_render(5)
144+
145+
print("SUCCESS: Multiple keyboards work")
146+
147+
148+
if __name__ == "__main__":
149+
unittest.main()

0 commit comments

Comments
 (0)