Skip to content

Commit 0b196ad

Browse files
Fix horrible keyboard setup
1 parent 47fda6e commit 0b196ad

File tree

1 file changed

+74
-191
lines changed

1 file changed

+74
-191
lines changed

internal_filesystem/lib/mpos/ui/keyboard.py

Lines changed: 74 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020

2121
import lvgl as lv
2222
import mpos.ui.theme
23-
import time
24-
2523

2624
class MposKeyboard:
2725
"""
@@ -38,138 +36,97 @@ class MposKeyboard:
3836
# Keyboard layout labels
3937
LABEL_NUMBERS_SPECIALS = "?123"
4038
LABEL_SPECIALS = "=\<"
41-
LABEL_LETTERS = "abc"
39+
LABEL_LETTERS = "Abc" # using abc here will trigger the default lv.keyboard() mode switch
4240
LABEL_SPACE = " "
4341

4442
# Keyboard modes - use USER modes for our API
4543
# We'll also register to standard modes to catch LVGL's internal switches
46-
MODE_LOWERCASE = lv.keyboard.MODE.USER_1
47-
MODE_UPPERCASE = lv.keyboard.MODE.USER_2
48-
MODE_NUMBERS = lv.keyboard.MODE.USER_3
49-
MODE_SPECIALS = lv.keyboard.MODE.USER_4
44+
CUSTOM_MODE_LOWERCASE = lv.keyboard.MODE.USER_1
45+
CUSTOM_MODE_UPPERCASE = lv.keyboard.MODE.USER_2
46+
CUSTOM_MODE_NUMBERS = lv.keyboard.MODE.USER_3
47+
CUSTOM_MODE_SPECIALS = lv.keyboard.MODE.USER_4
48+
49+
# Lowercase letters
50+
_lowercase_map = [
51+
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n",
52+
"a", "s", "d", "f", "g", "h", "j", "k", "l", "\n",
53+
lv.SYMBOL.UP, "z", "x", "c", "v", "b", "n", "m", lv.SYMBOL.BACKSPACE, "\n",
54+
LABEL_NUMBERS_SPECIALS, ",", LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
55+
]
56+
_lowercase_ctrl = [10] * len(_lowercase_map)
57+
58+
# Uppercase letters
59+
_uppercase_map = [
60+
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\n",
61+
"A", "S", "D", "F", "G", "H", "J", "K", "L", "\n",
62+
lv.SYMBOL.DOWN, "Z", "X", "C", "V", "B", "N", "M", lv.SYMBOL.BACKSPACE, "\n",
63+
LABEL_NUMBERS_SPECIALS, ",", LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
64+
]
65+
_uppercase_ctrl = [10] * len(_uppercase_map)
66+
67+
# Numbers and common special characters
68+
_numbers_map = [
69+
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\n",
70+
"@", "#", "$", "_", "&", "-", "+", "(", ")", "/", "\n",
71+
LABEL_SPECIALS, "*", "\"", "'", ":", ";", "!", "?", lv.SYMBOL.BACKSPACE, "\n",
72+
LABEL_LETTERS, ",", LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
73+
]
74+
_numbers_ctrl = [10] * len(_numbers_map)
75+
76+
# Additional special characters with emoticons
77+
_specials_map = [
78+
"~", "`", "|", "•", ":-)", ";-)", ":-D", "\n",
79+
":-(" , ":'-(", "^", "°", "=", "{", "}", "\\", "\n",
80+
LABEL_NUMBERS_SPECIALS, ":-o", ":-P", "[", "]", lv.SYMBOL.BACKSPACE, "\n",
81+
LABEL_LETTERS, "<", LABEL_SPACE, ">", lv.SYMBOL.NEW_LINE, None
82+
]
83+
_specials_ctrl = [10] * len(_specials_map)
84+
85+
# Map modes to their layouts
86+
mode_info = {
87+
CUSTOM_MODE_LOWERCASE: (_lowercase_map, _lowercase_ctrl),
88+
CUSTOM_MODE_UPPERCASE: (_uppercase_map, _uppercase_ctrl),
89+
CUSTOM_MODE_NUMBERS: (_numbers_map, _numbers_ctrl),
90+
CUSTOM_MODE_SPECIALS: (_specials_map, _specials_ctrl),
91+
}
92+
93+
_current_mode = None
5094

5195
def __init__(self, parent):
52-
"""
53-
Create a custom keyboard.
54-
55-
Args:
56-
parent: Parent LVGL object to attach keyboard to
57-
"""
5896
# Create underlying LVGL keyboard widget
5997
self._keyboard = lv.keyboard(parent)
6098

6199
# Store textarea reference (we DON'T pass it to LVGL to avoid double-typing)
62100
self._textarea = None
63101

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
102+
self.set_mode(self.CUSTOM_MODE_LOWERCASE)
71103

72-
# Configure layouts
73-
self._setup_layouts()
74-
75-
# Set default mode to lowercase
76-
# IMPORTANT: We do NOT call set_map() here in __init__.
77-
# Instead, set_mode() will call set_map() immediately before set_mode().
78-
# This matches the proof-of-concept pattern and prevents crashes from
79-
# calling set_map() multiple times which can corrupt button matrix state.
80-
self.set_mode(self.MODE_LOWERCASE)
81-
82-
# Add event handler for custom behavior
83-
# We need to handle ALL events to catch mode changes that LVGL might trigger
84104
self._keyboard.add_event_cb(self._handle_events, lv.EVENT.ALL, None)
85105

86106
# Apply theme fix for light mode visibility
87107
mpos.ui.theme.fix_keyboard_button_style(self._keyboard)
88108

89-
# Set reasonable default height
90-
self._keyboard.set_style_min_height(145, 0)
91-
92-
def _setup_layouts(self):
93-
"""Configure all keyboard layout modes."""
94-
95-
# Lowercase letters
96-
self._lowercase_map = [
97-
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n",
98-
"a", "s", "d", "f", "g", "h", "j", "k", "l", "\n",
99-
lv.SYMBOL.UP, "z", "x", "c", "v", "b", "n", "m", lv.SYMBOL.BACKSPACE, "\n",
100-
self.LABEL_NUMBERS_SPECIALS, ",", self.LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
101-
]
102-
self._lowercase_ctrl = [10] * len(self._lowercase_map)
103-
104-
# Uppercase letters
105-
self._uppercase_map = [
106-
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\n",
107-
"A", "S", "D", "F", "G", "H", "J", "K", "L", "\n",
108-
lv.SYMBOL.DOWN, "Z", "X", "C", "V", "B", "N", "M", lv.SYMBOL.BACKSPACE, "\n",
109-
self.LABEL_NUMBERS_SPECIALS, ",", self.LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
110-
]
111-
self._uppercase_ctrl = [10] * len(self._uppercase_map)
112-
113-
# Numbers and common special characters
114-
self._numbers_map = [
115-
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\n",
116-
"@", "#", "$", "_", "&", "-", "+", "(", ")", "/", "\n",
117-
self.LABEL_SPECIALS, "*", "\"", "'", ":", ";", "!", "?", lv.SYMBOL.BACKSPACE, "\n",
118-
self.LABEL_LETTERS, ",", self.LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
119-
]
120-
self._numbers_ctrl = [10] * len(self._numbers_map)
121-
122-
# Additional special characters with emoticons
123-
self._specials_map = [
124-
"~", "`", "|", "•", ":-)", ";-)", ":-D", "\n",
125-
":-(" , ":'-(", "^", "°", "=", "{", "}", "\\", "\n",
126-
self.LABEL_NUMBERS_SPECIALS, ":-o", ":-P", "[", "]", lv.SYMBOL.BACKSPACE, "\n",
127-
self.LABEL_LETTERS, "<", self.LABEL_SPACE, ">", lv.SYMBOL.NEW_LINE, None
128-
]
129-
self._specials_ctrl = [10] * len(self._specials_map)
109+
# Set good default height
110+
self._keyboard.set_style_min_height(165, 0)
130111

131112
def _handle_events(self, event):
132-
"""
133-
Handle keyboard button presses.
134-
135-
Args:
136-
event: LVGL event object
137-
"""
138-
event_code = event.get_code()
139-
140-
# Intercept READY event to prevent LVGL from changing modes
141-
if event_code == lv.EVENT.READY:
142-
# Stop LVGL from processing READY (which might trigger mode changes)
143-
event.stop_processing()
144-
# Forward READY event to external handlers if needed
113+
event_code=event.get_code()
114+
if event_code in [19,23,24,25,26,27,28,29,30,31,32,33,39,49,52]:
145115
return
146116

147-
# Intercept CANCEL event similarly
148-
if event_code == lv.EVENT.CANCEL:
149-
event.stop_processing()
150-
return
117+
name = mpos.ui.get_event_name(event_code)
118+
print(f"lv_event_t: code={event_code}, name={name}")
119+
120+
# Get the pressed button and its text
121+
target_obj=event.get_target_obj() # keyboard
122+
button = target_obj.get_selected_button()
123+
text = target_obj.get_button_text(button)
124+
print(f"[KBD] btn={button}, mode={self._current_mode}, text='{text}'")
151125

152126
# Only process VALUE_CHANGED events for actual typing
153127
if event_code != lv.EVENT.VALUE_CHANGED:
154128
return
155129

156-
# Stop event propagation FIRST, before doing anything else
157-
# This prevents LVGL's default handler from interfering
158-
event.stop_processing()
159-
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-
165-
# Get the pressed button and its text
166-
button = self._keyboard.get_selected_button()
167-
current_mode = self._keyboard.get_mode()
168-
text = self._keyboard.get_button_text(button)
169-
170-
# DEBUG
171-
print(f"[KBD] btn={button}, mode={current_mode}, text='{text}'")
172-
173130
# Ignore if no valid button text (can happen during mode switching)
174131
if text is None:
175132
return
@@ -186,31 +143,25 @@ def _handle_events(self, event):
186143
if text == lv.SYMBOL.BACKSPACE:
187144
# Delete last character
188145
new_text = current_text[:-1]
189-
190146
elif text == lv.SYMBOL.UP:
191147
# Switch to uppercase
192-
self.set_mode(self.MODE_UPPERCASE)
148+
self.set_mode(self.CUSTOM_MODE_UPPERCASE)
193149
return # Don't modify text
194-
195150
elif text == lv.SYMBOL.DOWN or text == self.LABEL_LETTERS:
196151
# Switch to lowercase
197-
self.set_mode(self.MODE_LOWERCASE)
152+
self.set_mode(self.CUSTOM_MODE_LOWERCASE)
198153
return # Don't modify text
199-
200154
elif text == self.LABEL_NUMBERS_SPECIALS:
201155
# Switch to numbers/specials
202-
self.set_mode(self.MODE_NUMBERS)
156+
self.set_mode(self.CUSTOM_MODE_NUMBERS)
203157
return # Don't modify text
204-
205158
elif text == self.LABEL_SPECIALS:
206159
# Switch to additional specials
207-
self.set_mode(self.MODE_SPECIALS)
160+
self.set_mode(self.CUSTOM_MODE_SPECIALS)
208161
return # Don't modify text
209-
210162
elif text == self.LABEL_SPACE:
211163
# Space bar
212164
new_text = current_text + " "
213-
214165
elif text == lv.SYMBOL.NEW_LINE:
215166
# Handle newline (only for multi-line textareas)
216167
if ta.get_one_line():
@@ -219,7 +170,6 @@ def _handle_events(self, event):
219170
return
220171
else:
221172
new_text = current_text + "\n"
222-
223173
else:
224174
# Regular character
225175
new_text = current_text + text
@@ -253,45 +203,16 @@ def get_textarea(self):
253203
return self._textarea
254204

255205
def set_mode(self, mode):
256-
"""
257-
Set keyboard mode with proper map configuration.
206+
print(f"[kbc] setting mode to {mode}")
207+
self._current_mode = mode
208+
key_map, ctrl_map = self.mode_info[mode]
209+
self._keyboard.set_map(mode, key_map, ctrl_map)
210+
self._keyboard.set_mode(mode)
258211

259-
This method ensures set_map() is called before set_mode() to prevent
260-
LVGL crashes when switching between custom keyboard modes.
261212

262-
Args:
263-
mode: One of MODE_LOWERCASE, MODE_UPPERCASE, MODE_NUMBERS, MODE_SPECIALS
264-
(can also accept standard LVGL modes)
265-
"""
266-
# Map modes to their layouts
267-
mode_info = {
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),
272-
}
273-
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
289-
290-
# ========================================================================
291213
# Python magic method for automatic method forwarding
292-
# ========================================================================
293-
294214
def __getattr__(self, name):
215+
print(f"[kbd] __getattr__ {name}")
295216
"""
296217
Forward any undefined method/attribute to the underlying LVGL keyboard.
297218
@@ -307,41 +228,3 @@ def __getattr__(self, name):
307228
"""
308229
# Forward to the underlying keyboard object
309230
return getattr(self._keyboard, name)
310-
311-
def get_lvgl_obj(self):
312-
"""
313-
Get the underlying LVGL keyboard object.
314-
315-
This is now rarely needed since __getattr__ forwards everything automatically.
316-
Kept for backwards compatibility.
317-
"""
318-
return self._keyboard
319-
320-
321-
def create_keyboard(parent, custom=False):
322-
"""
323-
Factory function to create a keyboard.
324-
325-
This provides a simple way to switch between standard LVGL keyboard
326-
and custom keyboard.
327-
328-
Args:
329-
parent: Parent LVGL object
330-
custom: If True, create MposKeyboard; if False, create standard lv.keyboard
331-
332-
Returns:
333-
MposKeyboard instance or lv.keyboard instance
334-
335-
Example:
336-
# Use custom keyboard
337-
keyboard = create_keyboard(screen, custom=True)
338-
339-
# Use standard LVGL keyboard
340-
keyboard = create_keyboard(screen, custom=False)
341-
"""
342-
if custom:
343-
return MposKeyboard(parent)
344-
else:
345-
keyboard = lv.keyboard(parent)
346-
mpos.ui.theme.fix_keyboard_button_style(keyboard)
347-
return keyboard

0 commit comments

Comments
 (0)