chore(gdb): cast fallback, NULL string handling, order-independent widget discovery

- Widget __init__ cast fallback to self when type missing in debug info
- safe_string: NULL returns None (normal), CorruptedValue returns marker
- parse_widgets: two-pass inheritance resolution, remove hardcoded list
This commit is contained in:
Benign X
2026-04-22 16:10:51 +08:00
committed by VIFEX
parent aa86c0d79b
commit 9dbdb4eb1f
38 changed files with 68 additions and 60 deletions
+3 -3
View File
@@ -8,16 +8,16 @@ from lvglgdb.lvgl.data_utils import ptr_or_none # noqa: F401
def safe_string(obj, field_name):
"""Read a char* field as string or corrupted marker. Never returns None."""
"""Read a char* field as string, corrupted marker, or None (NULL/missing)."""
from lvglgdb.value import CorruptedValue
val = obj.safe_field(field_name)
if val is None:
return str(CorruptedValue(0, ValueError("field not found")))
return None
if not getattr(val, 'is_ok', True):
return str(val)
addr = int(val)
if not addr:
return str(CorruptedValue(0, ValueError("NULL pointer")))
return None
return val.string(fallback=str(CorruptedValue(addr, MemoryError("unreadable"))))
@@ -13,7 +13,7 @@ class LV3dtexture(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_3dtexture_t", ptr=True)
self._wv = self.cast("lv_3dtexture_t", ptr=True) or self
@property
def id(self):
@@ -14,7 +14,7 @@ class LVAnimimg(LVImage):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_animimg_t", ptr=True)
self._wv = self.cast("lv_animimg_t", ptr=True) or self
@property
def anim(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class LVArc(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_arc_t", ptr=True)
self._wv = self.cast("lv_arc_t", ptr=True) or self
@property
def rotation(self):
@@ -14,7 +14,7 @@ class LVArclabel(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_arclabel_t", ptr=True)
self._wv = self.cast("lv_arclabel_t", ptr=True) or self
@property
def text(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVBar(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_bar_t", ptr=True)
self._wv = self.cast("lv_bar_t", ptr=True) or self
@property
def cur_value(self):
@@ -13,7 +13,7 @@ class LVButton(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_button_t", ptr=True)
self._wv = self.cast("lv_button_t", ptr=True) or self
def snapshot(self, include_children=False, include_styles=False):
"""Snapshot with widget-specific fields in widget_data."""
@@ -14,7 +14,7 @@ class LVButtonmatrix(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_buttonmatrix_t", ptr=True)
self._wv = self.cast("lv_buttonmatrix_t", ptr=True) or self
@property
def map_p(self):
@@ -14,7 +14,7 @@ class LVCalendar(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_calendar_t", ptr=True)
self._wv = self.cast("lv_calendar_t", ptr=True) or self
@property
def btnm(self):
@@ -14,7 +14,7 @@ class LVCanvas(LVImage):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_canvas_t", ptr=True)
self._wv = self.cast("lv_canvas_t", ptr=True) or self
@property
def draw_buf(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVChart(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_chart_t", ptr=True)
self._wv = self.cast("lv_chart_t", ptr=True) or self
@property
def series_ll(self):
@@ -14,7 +14,7 @@ class LVCheckbox(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_checkbox_t", ptr=True)
self._wv = self.cast("lv_checkbox_t", ptr=True) or self
@property
def txt(self):
@@ -14,7 +14,7 @@ class LVDropdown(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_dropdown_t", ptr=True)
self._wv = self.cast("lv_dropdown_t", ptr=True) or self
@property
def list(self):
@@ -14,7 +14,7 @@ class LVDropdownList(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_dropdown_list_t", ptr=True)
self._wv = self.cast("lv_dropdown_list_t", ptr=True) or self
@property
def dropdown(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVImage(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_image_t", ptr=True)
self._wv = self.cast("lv_image_t", ptr=True) or self
@property
def src(self):
@@ -13,7 +13,7 @@ class LVImagebutton(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_imagebutton_t", ptr=True)
self._wv = self.cast("lv_imagebutton_t", ptr=True) or self
def snapshot(self, include_children=False, include_styles=False):
"""Snapshot with widget-specific fields in widget_data."""
@@ -14,7 +14,7 @@ class LVImePinyin(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_ime_pinyin_t", ptr=True)
self._wv = self.cast("lv_ime_pinyin_t", ptr=True) or self
@property
def kb(self):
@@ -14,7 +14,7 @@ class LVKeyboard(LVButtonmatrix):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_keyboard_t", ptr=True)
self._wv = self.cast("lv_keyboard_t", ptr=True) or self
@property
def ta(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVLabel(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_label_t", ptr=True)
self._wv = self.cast("lv_label_t", ptr=True) or self
@property
def text(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVLed(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_led_t", ptr=True)
self._wv = self.cast("lv_led_t", ptr=True) or self
@property
def color(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class LVLine(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_line_t", ptr=True)
self._wv = self.cast("lv_line_t", ptr=True) or self
@property
def point_num(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVMenu(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_menu_t", ptr=True)
self._wv = self.cast("lv_menu_t", ptr=True) or self
@property
def storage(self):
@@ -14,7 +14,7 @@ class LVMenuPage(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_menu_page_t", ptr=True)
self._wv = self.cast("lv_menu_page_t", ptr=True) or self
@property
def title(self):
@@ -14,7 +14,7 @@ class LVMsgbox(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_msgbox_t", ptr=True)
self._wv = self.cast("lv_msgbox_t", ptr=True) or self
@property
def header(self):
@@ -13,7 +13,7 @@ class LVRoller(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_roller_t", ptr=True)
self._wv = self.cast("lv_roller_t", ptr=True) or self
@property
def option_cnt(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVScale(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_scale_t", ptr=True)
self._wv = self.cast("lv_scale_t", ptr=True) or self
@property
def section_ll(self):
@@ -14,7 +14,7 @@ class LVSlider(LVBar):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_slider_t", ptr=True)
self._wv = self.cast("lv_slider_t", ptr=True) or self
@property
def left_knob_area(self):
@@ -14,7 +14,7 @@ class LVSpangroup(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_spangroup_t", ptr=True)
self._wv = self.cast("lv_spangroup_t", ptr=True) or self
@property
def lines(self):
@@ -13,7 +13,7 @@ class LVSpinbox(LVTextarea):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_spinbox_t", ptr=True)
self._wv = self.cast("lv_spinbox_t", ptr=True) or self
@property
def value(self):
@@ -13,7 +13,7 @@ class LVSpinner(LVArc):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_spinner_t", ptr=True)
self._wv = self.cast("lv_spinner_t", ptr=True) or self
@property
def duration(self):
@@ -13,7 +13,7 @@ class LVSwitch(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_switch_t", ptr=True)
self._wv = self.cast("lv_switch_t", ptr=True) or self
@property
def anim_state(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVTable(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_table_t", ptr=True)
self._wv = self.cast("lv_table_t", ptr=True) or self
@property
def col_cnt(self):
@@ -13,7 +13,7 @@ class LVTabview(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_tabview_t", ptr=True)
self._wv = self.cast("lv_tabview_t", ptr=True) or self
@property
def tab_cur(self):
@@ -14,7 +14,7 @@ class LVTextarea(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_textarea_t", ptr=True)
self._wv = self.cast("lv_textarea_t", ptr=True) or self
@property
def label(self):
@@ -14,7 +14,7 @@ class LVTileview(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_tileview_t", ptr=True)
self._wv = self.cast("lv_tileview_t", ptr=True) or self
@property
def tile_act(self):
@@ -13,7 +13,7 @@ class LVTileviewTile(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_tileview_tile_t", ptr=True)
self._wv = self.cast("lv_tileview_tile_t", ptr=True) or self
@property
def dir(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class LVWin(LVObject):
def __init__(self, obj):
super().__init__(obj)
self._wv = self.cast("lv_win_t", ptr=True)
self._wv = self.cast("lv_win_t", ptr=True) or self
def snapshot(self, include_children=False, include_styles=False):
"""Snapshot with widget-specific fields in widget_data."""
@@ -190,36 +190,44 @@ def _find_structs(text: str):
def parse_widgets() -> dict[str, WidgetDef]:
widgets = {}
# Pass 1: collect all structs with parent type
candidates = []
for private_h in sorted(WIDGETS_DIR.glob("*/lv_*_private.h")):
text = private_h.read_text()
widget_dir = private_h.parent.name
for raw_name, body in _find_structs(text):
struct_name = f"lv_{raw_name}_t"
first_line = ""
for line in body.splitlines():
line = line.strip()
if line and not line.startswith(("/*", "//", "*", "#")):
first_line = line.rstrip(";").strip()
break
parent_match = re.match(r"(lv_\w+_t)\s+\w+", first_line)
if not parent_match:
continue
if parent_match:
candidates.append((struct_name, parent_match.group(1), body, widget_dir))
parent_type = parent_match.group(1)
if parent_type == "lv_obj_t" or parent_type in widgets or parent_type in (
"lv_bar_t", "lv_image_t", "lv_arc_t", "lv_textarea_t", "lv_buttonmatrix_t",
):
all_fields = parse_struct_fields(body)
widgets[struct_name] = WidgetDef(
struct_name=struct_name, c_type=struct_name,
parent_type=parent_type,
fields=all_fields[1:] if all_fields else [],
widget_dir=widget_dir,
)
# Pass 2: resolve inheritance from lv_obj_t (order-independent)
widget_types = {"lv_obj_t"}
changed = True
while changed:
changed = False
for name, parent, _, _ in candidates:
if name not in widget_types and parent in widget_types:
widget_types.add(name)
changed = True
# Pass 3: build WidgetDef for discovered widgets
widgets = {}
for struct_name, parent_type, body, widget_dir in candidates:
if struct_name in widget_types and struct_name != "lv_obj_t":
all_fields = parse_struct_fields(body)
widgets[struct_name] = WidgetDef(
struct_name=struct_name, c_type=struct_name,
parent_type=parent_type,
fields=all_fields[1:] if all_fields else [],
widget_dir=widget_dir,
)
return widgets
@@ -284,16 +292,16 @@ from lvglgdb.lvgl.data_utils import ptr_or_none # noqa: F401
def safe_string(obj, field_name):
"""Read a char* field as string or corrupted marker. Never returns None."""
"""Read a char* field as string, corrupted marker, or None (NULL/missing)."""
from lvglgdb.value import CorruptedValue
val = obj.safe_field(field_name)
if val is None:
return str(CorruptedValue(0, ValueError("field not found")))
return None
if not getattr(val, 'is_ok', True):
return str(val)
addr = int(val)
if not addr:
return str(CorruptedValue(0, ValueError("NULL pointer")))
return None
return val.string(fallback=str(CorruptedValue(addr, MemoryError("unreadable"))))
@@ -396,7 +404,7 @@ def gen_widget_file(wdef: WidgetDef, widgets: dict[str, WidgetDef]) -> str:
lines.append("")
lines.append(f" def __init__(self, obj):")
lines.append(f" super().__init__(obj)")
lines.append(f' self._wv = self.cast("{wdef.c_type}", ptr=True)')
lines.append(f' self._wv = self.cast("{wdef.c_type}", ptr=True) or self')
lines.append("")
# Properties