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): 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 from lvglgdb.value import CorruptedValue
val = obj.safe_field(field_name) val = obj.safe_field(field_name)
if val is None: if val is None:
return str(CorruptedValue(0, ValueError("field not found"))) return None
if not getattr(val, 'is_ok', True): if not getattr(val, 'is_ok', True):
return str(val) return str(val)
addr = int(val) addr = int(val)
if not addr: if not addr:
return str(CorruptedValue(0, ValueError("NULL pointer"))) return None
return val.string(fallback=str(CorruptedValue(addr, MemoryError("unreadable")))) return val.string(fallback=str(CorruptedValue(addr, MemoryError("unreadable"))))
@@ -13,7 +13,7 @@ class LV3dtexture(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def id(self): def id(self):
@@ -14,7 +14,7 @@ class LVAnimimg(LVImage):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def anim(self): def anim(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class LVArc(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def rotation(self): def rotation(self):
@@ -14,7 +14,7 @@ class LVArclabel(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def text(self): def text(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVBar(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def cur_value(self): def cur_value(self):
@@ -13,7 +13,7 @@ class LVButton(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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): def snapshot(self, include_children=False, include_styles=False):
"""Snapshot with widget-specific fields in widget_data.""" """Snapshot with widget-specific fields in widget_data."""
@@ -14,7 +14,7 @@ class LVButtonmatrix(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def map_p(self): def map_p(self):
@@ -14,7 +14,7 @@ class LVCalendar(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def btnm(self): def btnm(self):
@@ -14,7 +14,7 @@ class LVCanvas(LVImage):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def draw_buf(self): def draw_buf(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVChart(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def series_ll(self): def series_ll(self):
@@ -14,7 +14,7 @@ class LVCheckbox(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def txt(self): def txt(self):
@@ -14,7 +14,7 @@ class LVDropdown(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def list(self): def list(self):
@@ -14,7 +14,7 @@ class LVDropdownList(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def dropdown(self): def dropdown(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVImage(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def src(self): def src(self):
@@ -13,7 +13,7 @@ class LVImagebutton(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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): def snapshot(self, include_children=False, include_styles=False):
"""Snapshot with widget-specific fields in widget_data.""" """Snapshot with widget-specific fields in widget_data."""
@@ -14,7 +14,7 @@ class LVImePinyin(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def kb(self): def kb(self):
@@ -14,7 +14,7 @@ class LVKeyboard(LVButtonmatrix):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def ta(self): def ta(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVLabel(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def text(self): def text(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVLed(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def color(self): def color(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class LVLine(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def point_num(self): def point_num(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVMenu(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def storage(self): def storage(self):
@@ -14,7 +14,7 @@ class LVMenuPage(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def title(self): def title(self):
@@ -14,7 +14,7 @@ class LVMsgbox(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def header(self): def header(self):
@@ -13,7 +13,7 @@ class LVRoller(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def option_cnt(self): def option_cnt(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVScale(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def section_ll(self): def section_ll(self):
@@ -14,7 +14,7 @@ class LVSlider(LVBar):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def left_knob_area(self): def left_knob_area(self):
@@ -14,7 +14,7 @@ class LVSpangroup(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def lines(self): def lines(self):
@@ -13,7 +13,7 @@ class LVSpinbox(LVTextarea):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def value(self): def value(self):
@@ -13,7 +13,7 @@ class LVSpinner(LVArc):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def duration(self): def duration(self):
@@ -13,7 +13,7 @@ class LVSwitch(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def anim_state(self): def anim_state(self):
+1 -1
View File
@@ -14,7 +14,7 @@ class LVTable(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def col_cnt(self): def col_cnt(self):
@@ -13,7 +13,7 @@ class LVTabview(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def tab_cur(self): def tab_cur(self):
@@ -14,7 +14,7 @@ class LVTextarea(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def label(self): def label(self):
@@ -14,7 +14,7 @@ class LVTileview(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def tile_act(self): def tile_act(self):
@@ -13,7 +13,7 @@ class LVTileviewTile(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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 @property
def dir(self): def dir(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class LVWin(LVObject):
def __init__(self, obj): def __init__(self, obj):
super().__init__(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): def snapshot(self, include_children=False, include_styles=False):
"""Snapshot with widget-specific fields in widget_data.""" """Snapshot with widget-specific fields in widget_data."""
@@ -190,36 +190,44 @@ def _find_structs(text: str):
def parse_widgets() -> dict[str, WidgetDef]: 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")): for private_h in sorted(WIDGETS_DIR.glob("*/lv_*_private.h")):
text = private_h.read_text() text = private_h.read_text()
widget_dir = private_h.parent.name widget_dir = private_h.parent.name
for raw_name, body in _find_structs(text): for raw_name, body in _find_structs(text):
struct_name = f"lv_{raw_name}_t" struct_name = f"lv_{raw_name}_t"
first_line = "" first_line = ""
for line in body.splitlines(): for line in body.splitlines():
line = line.strip() line = line.strip()
if line and not line.startswith(("/*", "//", "*", "#")): if line and not line.startswith(("/*", "//", "*", "#")):
first_line = line.rstrip(";").strip() first_line = line.rstrip(";").strip()
break break
parent_match = re.match(r"(lv_\w+_t)\s+\w+", first_line) parent_match = re.match(r"(lv_\w+_t)\s+\w+", first_line)
if not parent_match: if parent_match:
continue candidates.append((struct_name, parent_match.group(1), body, widget_dir))
parent_type = parent_match.group(1) # Pass 2: resolve inheritance from lv_obj_t (order-independent)
if parent_type == "lv_obj_t" or parent_type in widgets or parent_type in ( widget_types = {"lv_obj_t"}
"lv_bar_t", "lv_image_t", "lv_arc_t", "lv_textarea_t", "lv_buttonmatrix_t", changed = True
): while changed:
all_fields = parse_struct_fields(body) changed = False
widgets[struct_name] = WidgetDef( for name, parent, _, _ in candidates:
struct_name=struct_name, c_type=struct_name, if name not in widget_types and parent in widget_types:
parent_type=parent_type, widget_types.add(name)
fields=all_fields[1:] if all_fields else [], changed = True
widget_dir=widget_dir,
) # 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 return widgets
@@ -284,16 +292,16 @@ from lvglgdb.lvgl.data_utils import ptr_or_none # noqa: F401
def safe_string(obj, field_name): 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 from lvglgdb.value import CorruptedValue
val = obj.safe_field(field_name) val = obj.safe_field(field_name)
if val is None: if val is None:
return str(CorruptedValue(0, ValueError("field not found"))) return None
if not getattr(val, 'is_ok', True): if not getattr(val, 'is_ok', True):
return str(val) return str(val)
addr = int(val) addr = int(val)
if not addr: if not addr:
return str(CorruptedValue(0, ValueError("NULL pointer"))) return None
return val.string(fallback=str(CorruptedValue(addr, MemoryError("unreadable")))) 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("")
lines.append(f" def __init__(self, obj):") lines.append(f" def __init__(self, obj):")
lines.append(f" super().__init__(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("") lines.append("")
# Properties # Properties