Skip to content

Commit 47fda6e

Browse files
Trying to finish keyboard
1 parent b8a61d1 commit 47fda6e

File tree

5 files changed

+533
-21
lines changed

5 files changed

+533
-21
lines changed

internal_filesystem/lib/mpos/ui/keyboard.py

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import lvgl as lv
2222
import mpos.ui.theme
23+
import time
2324

2425

2526
class MposKeyboard:
@@ -60,6 +61,14 @@ def __init__(self, parent):
6061
# Store textarea reference (we DON'T pass it to LVGL to avoid double-typing)
6162
self._textarea = None
6263

64+
# Track last mode switch time to prevent race conditions
65+
# When user rapidly clicks mode buttons, button indices can get confused
66+
# because index 29 is "abc" in numbers mode but "," in lowercase mode
67+
self._last_mode_switch_time = 0
68+
69+
# Re-entrancy guard to prevent recursive event processing during mode switches
70+
self._in_mode_switch = False
71+
6372
# Configure layouts
6473
self._setup_layouts()
6574

@@ -148,11 +157,20 @@ def _handle_events(self, event):
148157
# This prevents LVGL's default handler from interfering
149158
event.stop_processing()
150159

160+
# Re-entrancy guard: Skip processing if we're currently switching modes
161+
# This prevents set_mode() from triggering recursive event processing
162+
if self._in_mode_switch:
163+
return
164+
151165
# Get the pressed button and its text
152166
button = self._keyboard.get_selected_button()
167+
current_mode = self._keyboard.get_mode()
153168
text = self._keyboard.get_button_text(button)
154169

155-
# Ignore if no valid button text (can happen during initialization)
170+
# DEBUG
171+
print(f"[KBD] btn={button}, mode={current_mode}, text='{text}'")
172+
173+
# Ignore if no valid button text (can happen during mode switching)
156174
if text is None:
157175
return
158176

@@ -245,29 +263,29 @@ def set_mode(self, mode):
245263
mode: One of MODE_LOWERCASE, MODE_UPPERCASE, MODE_NUMBERS, MODE_SPECIALS
246264
(can also accept standard LVGL modes)
247265
"""
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
266+
# Map modes to their layouts
251267
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]),
268+
self.MODE_LOWERCASE: (self._lowercase_map, self._lowercase_ctrl),
269+
self.MODE_UPPERCASE: (self._uppercase_map, self._uppercase_ctrl),
270+
self.MODE_NUMBERS: (self._numbers_map, self._numbers_ctrl),
271+
self.MODE_SPECIALS: (self._specials_map, self._specials_ctrl),
261272
}
262273

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)
269-
270-
self._keyboard.set_mode(mode)
274+
# Set re-entrancy guard to block any events triggered during mode switch
275+
self._in_mode_switch = True
276+
277+
try:
278+
# Set the map for the new mode BEFORE calling set_mode()
279+
# This prevents crashes from set_mode() being called with no map set
280+
if mode in mode_info:
281+
key_map, ctrl_map = mode_info[mode]
282+
self._keyboard.set_map(mode, key_map, ctrl_map)
283+
284+
# Now switch to the new mode
285+
self._keyboard.set_mode(mode)
286+
finally:
287+
# Always clear the guard, even if an exception occurs
288+
self._in_mode_switch = False
271289

272290
# ========================================================================
273291
# Python magic method for automatic method forwarding
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Manual test for the "abc" button bug with DEBUG OUTPUT.
3+
4+
Run with: ./scripts/run_desktop.sh tests/manual_test_abc_button_debug.py
5+
6+
This will show debug output when you click the "abc" button.
7+
Watch the terminal to see what's happening!
8+
"""
9+
10+
import lvgl as lv
11+
from mpos.ui.keyboard import MposKeyboard
12+
13+
# Get active screen
14+
screen = lv.screen_active()
15+
screen.clean()
16+
17+
# Create title
18+
title = lv.label(screen)
19+
title.set_text("ABC Button Debug Test")
20+
title.align(lv.ALIGN.TOP_MID, 0, 5)
21+
22+
# Create instructions
23+
instructions = lv.label(screen)
24+
instructions.set_text(
25+
"Watch the TERMINAL output!\n"
26+
"\n"
27+
"1. Click '?123' to go to numbers mode\n"
28+
"2. Click 'abc' to go back to lowercase\n"
29+
"3. Check terminal for debug output\n"
30+
"4. Check if comma appears in textarea"
31+
)
32+
instructions.set_style_text_align(lv.TEXT_ALIGN.LEFT, 0)
33+
instructions.align(lv.ALIGN.TOP_LEFT, 10, 30)
34+
35+
# Create textarea
36+
textarea = lv.textarea(screen)
37+
textarea.set_size(280, 30)
38+
textarea.set_one_line(True)
39+
textarea.align(lv.ALIGN.TOP_MID, 0, 120)
40+
textarea.set_placeholder_text("Type here...")
41+
42+
# Create keyboard
43+
keyboard = MposKeyboard(screen)
44+
keyboard.set_textarea(textarea)
45+
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
46+
47+
print("\n" + "="*70)
48+
print("ABC BUTTON DEBUG TEST")
49+
print("="*70)
50+
print("Instructions:")
51+
print("1. The keyboard starts in LOWERCASE mode")
52+
print("2. Click the '?123' button (bottom left) to switch to NUMBERS mode")
53+
print("3. Click the 'abc' button (bottom left) to switch back to LOWERCASE")
54+
print("4. Watch this terminal for [KEYBOARD DEBUG] messages")
55+
print("5. Check if a comma appears in the textarea")
56+
print("="*70)
57+
print("\nWaiting for button clicks...")
58+
print()
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
Automated test that simulates clicking the abc button and shows debug output.
3+
4+
This will show us exactly what's happening when the abc button is clicked.
5+
6+
Usage:
7+
Desktop: ./tests/unittest.sh tests/test_graphical_abc_button_debug.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 TestAbcButtonDebug(unittest.TestCase):
17+
"""Test that shows debug output when clicking abc button."""
18+
19+
def setUp(self):
20+
"""Set up test fixtures."""
21+
self.screen = lv.obj()
22+
self.screen.set_size(320, 240)
23+
24+
# Create textarea
25+
self.textarea = lv.textarea(self.screen)
26+
self.textarea.set_size(280, 40)
27+
self.textarea.align(lv.ALIGN.TOP_MID, 0, 10)
28+
self.textarea.set_one_line(True)
29+
30+
# Load screen
31+
lv.screen_load(self.screen)
32+
wait_for_render(5)
33+
34+
def tearDown(self):
35+
"""Clean up."""
36+
lv.screen_load(lv.obj())
37+
wait_for_render(5)
38+
39+
def test_simulate_abc_button_click(self):
40+
"""
41+
Simulate clicking the abc button and show what happens.
42+
"""
43+
print("\n" + "="*70)
44+
print("SIMULATING ABC BUTTON CLICK - WATCH FOR DEBUG OUTPUT")
45+
print("="*70)
46+
47+
keyboard = MposKeyboard(self.screen)
48+
keyboard.set_textarea(self.textarea)
49+
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
50+
wait_for_render(10)
51+
52+
# Start in lowercase, switch to numbers
53+
print("\n>>> Switching to NUMBERS mode...")
54+
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
55+
wait_for_render(10)
56+
57+
# Wait for debounce period to expire (150ms + margin)
58+
import time
59+
print(">>> Waiting 200ms for debounce period to expire...")
60+
time.sleep(0.2)
61+
62+
# Clear textarea
63+
self.textarea.set_text("")
64+
print(f">>> Textarea cleared: '{self.textarea.get_text()}'")
65+
66+
# Find the "abc" button
67+
abc_button_index = None
68+
for i in range(100):
69+
try:
70+
text = keyboard.get_button_text(i)
71+
if text == "abc":
72+
abc_button_index = i
73+
print(f">>> Found 'abc' button at index {abc_button_index}")
74+
break
75+
except:
76+
pass
77+
78+
# Now simulate what happens when user TOUCHES the button
79+
# When user touches a button, LVGL's button matrix:
80+
# 1. Sets the button as selected
81+
# 2. Triggers VALUE_CHANGED event
82+
print(f"\n>>> Simulating user clicking button {abc_button_index}...")
83+
print(f">>> Before click: textarea = '{self.textarea.get_text()}'")
84+
print("\n--- DEBUG OUTPUT SHOULD APPEAR BELOW ---\n")
85+
86+
# Trigger the VALUE_CHANGED event which our handler catches
87+
# This simulates a real button press
88+
keyboard._keyboard.send_event(lv.EVENT.VALUE_CHANGED, None)
89+
wait_for_render(5)
90+
91+
print("\n--- END DEBUG OUTPUT ---\n")
92+
93+
textarea_after = self.textarea.get_text()
94+
print(f">>> After click: textarea = '{textarea_after}'")
95+
96+
if textarea_after != "":
97+
print(f"\n❌ BUG CONFIRMED!")
98+
print(f" Expected: '' (empty)")
99+
print(f" Got: '{textarea_after}'")
100+
else:
101+
print(f"\n✓ No text added (but check debug output above)")
102+
103+
print("="*70)
104+
105+
106+
if __name__ == "__main__":
107+
unittest.main()

0 commit comments

Comments
 (0)