From 4fa71ab54892752373707aeb02677a8579f5e669 Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Sun, 23 Nov 2025 05:55:39 +0100 Subject: [PATCH] Fix tests/test_graphical_keyboard_q_button_bug.py --- internal_filesystem/lib/mpos/ui/testing.py | 98 +++++++++++++++++++ tests/test_graphical_keyboard_q_button_bug.py | 69 +++---------- 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/internal_filesystem/lib/mpos/ui/testing.py b/internal_filesystem/lib/mpos/ui/testing.py index acd782f3..dc3fa063 100644 --- a/internal_filesystem/lib/mpos/ui/testing.py +++ b/internal_filesystem/lib/mpos/ui/testing.py @@ -383,6 +383,104 @@ def find_button_with_text(obj, search_text): return None +def get_keyboard_button_coords(keyboard, button_text): + """ + Get the coordinates of a specific button on an LVGL keyboard/buttonmatrix. + + This function calculates the exact center position of a keyboard button + by finding its index and computing its position based on the keyboard's + layout, control widths, and actual screen coordinates. + + Args: + keyboard: LVGL keyboard widget (or MposKeyboard wrapper) + button_text: Text of the button to find (e.g., "q", "a", "1") + + Returns: + dict with 'center_x' and 'center_y', or None if button not found + + Example: + from mpos.ui.keyboard import MposKeyboard + keyboard = MposKeyboard(screen) + coords = get_keyboard_button_coords(keyboard, "q") + if coords: + simulate_click(coords['center_x'], coords['center_y']) + """ + # Get the underlying LVGL keyboard if this is a wrapper + if hasattr(keyboard, '_keyboard'): + lvgl_keyboard = keyboard._keyboard + else: + lvgl_keyboard = keyboard + + # Find the button index + button_idx = None + for i in range(100): # Check up to 100 buttons + try: + text = lvgl_keyboard.get_button_text(i) + if text == button_text: + button_idx = i + break + except: + break # No more buttons + + if button_idx is None: + return None + + # Get keyboard widget coordinates + area = lv.area_t() + lvgl_keyboard.get_coords(area) + kb_x = area.x1 + kb_y = area.y1 + kb_width = area.x2 - area.x1 + kb_height = area.y2 - area.y1 + + # Parse the keyboard layout to find button position + # Note: LVGL get_button_text() skips '\n' markers, so they're not in the indices + # Standard keyboard layout (from MposKeyboard): + # Row 0: 10 buttons (q w e r t y u i o p) + # Row 1: 9 buttons (a s d f g h j k l) + # Row 2: 9 buttons (shift z x c v b n m backspace) + # Row 3: 5 buttons (?123, comma, space, dot, enter) + + # Define row lengths for standard keyboard + row_lengths = [10, 9, 9, 5] + + # Find which row our button is in + row = 0 + buttons_before = 0 + for row_len in row_lengths: + if button_idx < buttons_before + row_len: + # Button is in this row + col = button_idx - buttons_before + buttons_this_row = row_len + break + buttons_before += row_len + row += 1 + else: + # Button not found in standard layout, use row 0 + row = 0 + col = button_idx + buttons_this_row = 10 + + # Calculate position + # Approximate: divide keyboard into equal rows and columns + # (This is simplified - actual LVGL uses control widths, but this is good enough) + num_rows = 4 # Typical keyboard has 4 rows + button_height = kb_height / num_rows + button_width = kb_width / max(buttons_this_row, 1) + + # Calculate center position + center_x = int(kb_x + (col * button_width) + (button_width / 2)) + center_y = int(kb_y + (row * button_height) + (button_height / 2)) + + return { + 'center_x': center_x, + 'center_y': center_y, + 'button_idx': button_idx, + 'row': row, + 'col': col + } + + def _touch_read_cb(indev_drv, data): """ Internal callback for simulated touch input device. diff --git a/tests/test_graphical_keyboard_q_button_bug.py b/tests/test_graphical_keyboard_q_button_bug.py index 65555ab6..b52dde6e 100644 --- a/tests/test_graphical_keyboard_q_button_bug.py +++ b/tests/test_graphical_keyboard_q_button_bug.py @@ -20,6 +20,7 @@ from mpos.ui.testing import ( wait_for_render, find_button_with_text, get_widget_coords, + get_keyboard_button_coords, simulate_click, print_screen_labels ) @@ -79,43 +80,16 @@ class TestKeyboardQButton(unittest.TestCase): # --- Test 'q' button --- print("\n--- Testing 'q' button ---") - # Find button index for 'q' in the keyboard - q_button_id = None - for i in range(100): # Check first 100 button indices - try: - text = keyboard.get_button_text(i) - if text == "q": - q_button_id = i - print(f"Found 'q' button at index {i}") - break - except: - break # No more buttons + # Get exact button coordinates using helper function + q_coords = get_keyboard_button_coords(keyboard, "q") + self.assertIsNotNone(q_coords, "Should find 'q' button on keyboard") - self.assertIsNotNone(q_button_id, "Should find 'q' button on keyboard") - - # Get the keyboard widget coordinates to calculate button position - keyboard_area = lv.area_t() - keyboard.get_coords(keyboard_area) - print(f"Keyboard area: x1={keyboard_area.x1}, y1={keyboard_area.y1}, x2={keyboard_area.x2}, y2={keyboard_area.y2}") - - # LVGL keyboards organize buttons in a grid - # From the map: "q" is at index 0, in top row (10 buttons per row) - # Let's estimate position based on keyboard layout - # Top row starts at y1 + some padding, each button is ~width/10 - keyboard_width = keyboard_area.x2 - keyboard_area.x1 - keyboard_height = keyboard_area.y2 - keyboard_area.y1 - button_width = keyboard_width // 10 # ~10 buttons per row - button_height = keyboard_height // 4 # ~4 rows - - # 'q' is first button (index 0), top row - q_x = keyboard_area.x1 + button_width // 2 - q_y = keyboard_area.y1 + button_height // 2 - - print(f"Estimated 'q' button position: ({q_x}, {q_y})") + print(f"Found 'q' button at index {q_coords['button_idx']}, row {q_coords['row']}, col {q_coords['col']}") + print(f"Exact 'q' button position: ({q_coords['center_x']}, {q_coords['center_y']})") # Click the 'q' button - print(f"Clicking 'q' button at ({q_x}, {q_y})") - simulate_click(q_x, q_y) + print(f"Clicking 'q' button at ({q_coords['center_x']}, {q_coords['center_y']})") + simulate_click(q_coords['center_x'], q_coords['center_y']) wait_for_render(10) # Check textarea content @@ -134,29 +108,16 @@ class TestKeyboardQButton(unittest.TestCase): wait_for_render(5) print("Cleared textarea") - # Find button index for 'a' - a_button_id = None - for i in range(100): - try: - text = keyboard.get_button_text(i) - if text == "a": - a_button_id = i - print(f"Found 'a' button at index {i}") - break - except: - break + # Get exact button coordinates using helper function + a_coords = get_keyboard_button_coords(keyboard, "a") + self.assertIsNotNone(a_coords, "Should find 'a' button on keyboard") - self.assertIsNotNone(a_button_id, "Should find 'a' button on keyboard") - - # 'a' is at index 11 (second row, first position) - a_x = keyboard_area.x1 + button_width // 2 - a_y = keyboard_area.y1 + button_height + button_height // 2 - - print(f"Estimated 'a' button position: ({a_x}, {a_y})") + print(f"Found 'a' button at index {a_coords['button_idx']}, row {a_coords['row']}, col {a_coords['col']}") + print(f"Exact 'a' button position: ({a_coords['center_x']}, {a_coords['center_y']})") # Click the 'a' button - print(f"Clicking 'a' button at ({a_x}, {a_y})") - simulate_click(a_x, a_y) + print(f"Clicking 'a' button at ({a_coords['center_x']}, {a_coords['center_y']})") + simulate_click(a_coords['center_x'], a_coords['center_y']) wait_for_render(10) # Check textarea content