diff --git a/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py b/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py index fb8b53afdc..b92c567d40 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py +++ b/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py @@ -240,11 +240,14 @@ class LVObject(Value): return hex(addr) if addr else None def _get_spec_int(self, field_name): - """Get an int field from spec_attr, or None.""" + """Get an int field from spec_attr, or None if unavailable.""" spec = self.spec_attr if not spec or not int(spec): return None - return int(spec.safe_field(field_name, 0)) + val = spec.safe_field(field_name) + if val is None: + return None + return int(val) def _get_scroll(self): """Get scroll offset from spec_attr, or None.""" diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/_helpers.py b/scripts/gdb/lvglgdb/lvgl/widgets/_helpers.py index f1982bda8a..018acb9da0 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/_helpers.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/_helpers.py @@ -45,3 +45,15 @@ def safe_point(obj, field_name): "x": int(val.safe_field("x", 0)), "y": int(val.safe_field("y", 0)), } + + +def safe_wrapper(obj, field_name, module_path, class_name): + """Read a struct field using its known Value wrapper, return snapshot dict.""" + val = obj.safe_field(field_name) + if val is None or not getattr(val, 'is_ok', True): + return None + import importlib + mod = importlib.import_module(module_path) + cls = getattr(mod, class_name) + wrapper = cls(val) + return wrapper.snapshot().as_dict() diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_animimg.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_animimg.py index 29f790ccfb..a30a635a8f 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_animimg.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_animimg.py @@ -6,6 +6,7 @@ Do not edit manually. Regenerate from the GDB script root with: """ from .lv_image import LVImage +from ._helpers import ptr_or_none, safe_wrapper class LVAnimimg(LVImage): @@ -17,7 +18,11 @@ class LVAnimimg(LVImage): @property def anim(self): - return int(self._wv.safe_field("anim", 0)) + return safe_wrapper(self._wv, "anim", "lvglgdb.lvgl.misc.lv_anim", "LVAnim") + + @property + def dsc(self): + return ptr_or_none(self._wv.safe_field("dsc")) @property def pic_count(self): @@ -28,6 +33,7 @@ class LVAnimimg(LVImage): s = super().snapshot(include_children=include_children, include_styles=include_styles) d = s.get('widget_data') or {} d["anim"] = self.anim + d["dsc"] = self.dsc d["pic_count"] = self.pic_count s['widget_data'] = d return s diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_bar.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_bar.py index d64fe3bc48..89f32a8de3 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_bar.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_bar.py @@ -46,14 +46,6 @@ class LVBar(LVObject): """Whether value been reversed""" return int(self._wv.safe_field("val_reversed", 0)) - @property - def cur_value_anim(self): - return int(self._wv.safe_field("cur_value_anim", 0)) - - @property - def start_value_anim(self): - return int(self._wv.safe_field("start_value_anim", 0)) - @property def mode(self): """Type of bar""" @@ -74,8 +66,6 @@ class LVBar(LVObject): d["start_value"] = self.start_value d["indic_area"] = self.indic_area d["val_reversed"] = self.val_reversed - d["cur_value_anim"] = self.cur_value_anim - d["start_value_anim"] = self.start_value_anim d["mode"] = self.mode d["orientation"] = self.orientation s['widget_data'] = d diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_buttonmatrix.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_buttonmatrix.py index cdfcc8d689..d1d53400bd 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_buttonmatrix.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_buttonmatrix.py @@ -17,9 +17,9 @@ class LVButtonmatrix(LVObject): self._wv = self.cast("lv_buttonmatrix_t", ptr=True) @property - def const(self): + def map_p(self): """Pointer to the current map""" - return safe_string(self._wv, "const") + return safe_string(self._wv, "map_p") @property def button_areas(self): @@ -60,7 +60,7 @@ class LVButtonmatrix(LVObject): """Snapshot with widget-specific fields in widget_data.""" s = super().snapshot(include_children=include_children, include_styles=include_styles) d = s.get('widget_data') or {} - d["const"] = self.const + d["map_p"] = self.map_p d["button_areas"] = self.button_areas d["ctrl_bits"] = self.ctrl_bits d["btn_cnt"] = self.btn_cnt diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_calendar.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_calendar.py index 000a4b67de..d0de269636 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_calendar.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_calendar.py @@ -20,16 +20,6 @@ class LVCalendar(LVObject): def btnm(self): return ptr_or_none(self._wv.safe_field("btnm")) - @property - def today(self): - """Date of today""" - return int(self._wv.safe_field("today", 0)) - - @property - def showed_date(self): - """Currently visible month (day is ignored)""" - return int(self._wv.safe_field("showed_date", 0)) - @property def highlighted_dates(self): """Apply different style on these days (pointer to user-defined array)""" @@ -49,8 +39,6 @@ class LVCalendar(LVObject): s = super().snapshot(include_children=include_children, include_styles=include_styles) d = s.get('widget_data') or {} d["btnm"] = self.btnm - d["today"] = self.today - d["showed_date"] = self.showed_date d["highlighted_dates"] = self.highlighted_dates d["highlighted_dates_num"] = self.highlighted_dates_num d["use_chinese_calendar"] = self.use_chinese_calendar diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_canvas.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_canvas.py index 3052d7175f..8a719b518f 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_canvas.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_canvas.py @@ -6,7 +6,7 @@ Do not edit manually. Regenerate from the GDB script root with: """ from .lv_image import LVImage -from ._helpers import ptr_or_none +from ._helpers import ptr_or_none, safe_wrapper class LVCanvas(LVImage): @@ -22,7 +22,7 @@ class LVCanvas(LVImage): @property def static_buf(self): - return int(self._wv.safe_field("static_buf", 0)) + return safe_wrapper(self._wv, "static_buf", "lvglgdb.lvgl.draw.lv_draw_buf", "LVDrawBuf") def snapshot(self, include_children=False, include_styles=False): """Snapshot with widget-specific fields in widget_data.""" diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_chart.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_chart.py index 4c521557e4..546e72ab97 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_chart.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_chart.py @@ -6,6 +6,7 @@ Do not edit manually. Regenerate from the GDB script root with: """ from lvglgdb.lvgl.core.lv_obj import LVObject +from ._helpers import safe_wrapper class LVChart(LVObject): @@ -18,12 +19,12 @@ class LVChart(LVObject): @property def series_ll(self): """Linked list for series (stores lv_chart_series_t)""" - return int(self._wv.safe_field("series_ll", 0)) + return safe_wrapper(self._wv, "series_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList") @property def cursor_ll(self): """Linked list for cursors (stores lv_chart_cursor_t)""" - return int(self._wv.safe_field("cursor_ll", 0)) + return safe_wrapper(self._wv, "cursor_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList") @property def pressed_point_id(self): diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_ime_pinyin.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_ime_pinyin.py index d0749ee631..1619cd3a10 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_ime_pinyin.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_ime_pinyin.py @@ -6,7 +6,7 @@ Do not edit manually. Regenerate from the GDB script root with: """ from lvglgdb.lvgl.core.lv_obj import LVObject -from ._helpers import ptr_or_none, safe_string +from ._helpers import ptr_or_none, safe_string, safe_wrapper class LVImePinyin(LVObject): @@ -30,7 +30,7 @@ class LVImePinyin(LVObject): @property def k9_legal_py_ll(self): - return int(self._wv.safe_field("k9_legal_py_ll", 0)) + return safe_wrapper(self._wv, "k9_legal_py_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList") @property def cand_str(self): diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_label.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_label.py index d039fed9ae..29fde704f7 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_label.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_label.py @@ -29,10 +29,6 @@ class LVLabel(LVObject): """Offset where bytes have been replaced with dots""" return int(self._wv.safe_field("dot_begin", 0)) - @property - def hint(self): - return int(self._wv.safe_field("hint", 0)) - @property def sel_start(self): return int(self._wv.safe_field("sel_start", 0)) @@ -92,7 +88,6 @@ class LVLabel(LVObject): d["text"] = self.text d["translation_tag"] = self.translation_tag d["dot_begin"] = self.dot_begin - d["hint"] = self.hint d["sel_start"] = self.sel_start d["sel_end"] = self.sel_end d["size_cache"] = self.size_cache diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_line.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_line.py index 1bc1c66d67..e40ccce01b 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_line.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_line.py @@ -15,7 +15,27 @@ class LVLine(LVObject): super().__init__(obj) self._wv = self.cast("lv_line_t", ptr=True) + @property + def point_num(self): + """Number of points in 'point_array'""" + return int(self._wv.safe_field("point_num", 0)) + + @property + def y_inv(self): + """1: y == 0 will be on the bottom""" + return int(self._wv.safe_field("y_inv", 0)) + + @property + def point_array_is_mutable(self): + """whether the point array is const or mutable""" + return int(self._wv.safe_field("point_array_is_mutable", 0)) + def snapshot(self, include_children=False, include_styles=False): """Snapshot with widget-specific fields in widget_data.""" s = super().snapshot(include_children=include_children, include_styles=include_styles) + d = s.get('widget_data') or {} + d["point_num"] = self.point_num + d["y_inv"] = self.y_inv + d["point_array_is_mutable"] = self.point_array_is_mutable + s['widget_data'] = d return s diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_menu.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_menu.py index 005a593027..a5dfcf56a0 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_menu.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_menu.py @@ -6,7 +6,7 @@ Do not edit manually. Regenerate from the GDB script root with: """ from lvglgdb.lvgl.core.lv_obj import LVObject -from ._helpers import ptr_or_none +from ._helpers import ptr_or_none, safe_wrapper class LVMenu(LVObject): @@ -59,7 +59,7 @@ class LVMenu(LVObject): @property def history_ll(self): - return int(self._wv.safe_field("history_ll", 0)) + return safe_wrapper(self._wv, "history_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList") @property def cur_depth(self): diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_scale.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_scale.py index e7d8d3c3f4..8b81ce2332 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_scale.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_scale.py @@ -6,6 +6,7 @@ Do not edit manually. Regenerate from the GDB script root with: """ from lvglgdb.lvgl.core.lv_obj import LVObject +from ._helpers import safe_string, safe_wrapper class LVScale(LVObject): @@ -18,7 +19,11 @@ class LVScale(LVObject): @property def section_ll(self): """Linked list for the sections (stores lv_scale_section_t)""" - return int(self._wv.safe_field("section_ll", 0)) + return safe_wrapper(self._wv, "section_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList") + + @property + def txt_src(self): + return safe_string(self._wv, "txt_src") @property def mode(self): @@ -87,13 +92,14 @@ class LVScale(LVObject): @property def needles(self): """Needle list of this scale""" - return int(self._wv.safe_field("needles", 0)) + return safe_wrapper(self._wv, "needles", "lvglgdb.lvgl.misc.lv_array", "LVArray") def snapshot(self, include_children=False, include_styles=False): """Snapshot with widget-specific fields in widget_data.""" s = super().snapshot(include_children=include_children, include_styles=include_styles) d = s.get('widget_data') or {} d["section_ll"] = self.section_ll + d["txt_src"] = self.txt_src d["mode"] = self.mode d["range_min"] = self.range_min d["range_max"] = self.range_max diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_spangroup.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_spangroup.py index 0f3d1e8d8c..8be92b3af7 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_spangroup.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_spangroup.py @@ -6,6 +6,7 @@ Do not edit manually. Regenerate from the GDB script root with: """ from lvglgdb.lvgl.core.lv_obj import LVObject +from ._helpers import safe_wrapper class LVSpangroup(LVObject): @@ -36,7 +37,7 @@ class LVSpangroup(LVObject): @property def child_ll(self): - return int(self._wv.safe_field("child_ll", 0)) + return safe_wrapper(self._wv, "child_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList") @property def overflow(self): diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_table.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_table.py index 81cc5de18a..da1f91683b 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_table.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_table.py @@ -24,6 +24,10 @@ class LVTable(LVObject): def row_cnt(self): return int(self._wv.safe_field("row_cnt", 0)) + @property + def cell_data(self): + return ptr_or_none(self._wv.safe_field("cell_data")) + @property def row_h(self): return ptr_or_none(self._wv.safe_field("row_h")) @@ -46,6 +50,7 @@ class LVTable(LVObject): d = s.get('widget_data') or {} d["col_cnt"] = self.col_cnt d["row_cnt"] = self.row_cnt + d["cell_data"] = self.cell_data d["row_h"] = self.row_h d["col_w"] = self.col_w d["col_act"] = self.col_act diff --git a/scripts/gdb/lvglgdb/lvgl/widgets/lv_textarea.py b/scripts/gdb/lvglgdb/lvgl/widgets/lv_textarea.py index 0f9d6119f4..a31e4fd5e8 100644 --- a/scripts/gdb/lvglgdb/lvgl/widgets/lv_textarea.py +++ b/scripts/gdb/lvglgdb/lvgl/widgets/lv_textarea.py @@ -56,6 +56,40 @@ class LVTextarea(LVObject): """Time to show characters in password mode before change them to '*'""" return int(self._wv.safe_field("pwd_show_time", 0)) + @property + def sel_start(self): + """Temporary values for text selection""" + return int(self._wv.safe_field("sel_start", 0)) + + @property + def sel_end(self): + return int(self._wv.safe_field("sel_end", 0)) + + @property + def text_sel_in_prog(self): + """User is in process of selecting""" + return int(self._wv.safe_field("text_sel_in_prog", 0)) + + @property + def text_sel_en(self): + """Text can be selected on this text area""" + return int(self._wv.safe_field("text_sel_en", 0)) + + @property + def pwd_mode(self): + """Replace characters with '*'""" + return int(self._wv.safe_field("pwd_mode", 0)) + + @property + def one_line(self): + """One line mode (ignore line breaks)""" + return int(self._wv.safe_field("one_line", 0)) + + @property + def static_accepted_chars(self): + """1: Only a pointer is saved in `accepted_chars`""" + return int(self._wv.safe_field("static_accepted_chars", 0)) + def snapshot(self, include_children=False, include_styles=False): """Snapshot with widget-specific fields in widget_data.""" s = super().snapshot(include_children=include_children, include_styles=include_styles) @@ -68,5 +102,12 @@ class LVTextarea(LVObject): d["accepted_chars"] = self.accepted_chars d["max_length"] = self.max_length d["pwd_show_time"] = self.pwd_show_time + d["sel_start"] = self.sel_start + d["sel_end"] = self.sel_end + d["text_sel_in_prog"] = self.text_sel_in_prog + d["text_sel_en"] = self.text_sel_en + d["pwd_mode"] = self.pwd_mode + d["one_line"] = self.one_line + d["static_accepted_chars"] = self.static_accepted_chars s['widget_data'] = d return s diff --git a/scripts/gdb/scripts/generators/gen_widget_wrappers.py b/scripts/gdb/scripts/generators/gen_widget_wrappers.py index 7682eba726..ecee5a83c7 100644 --- a/scripts/gdb/scripts/generators/gen_widget_wrappers.py +++ b/scripts/gdb/scripts/generators/gen_widget_wrappers.py @@ -36,6 +36,35 @@ SIMPLE_INT_TYPES = { } +def _scan_enum_types() -> set[str]: + """Scan LVGL headers to find all typedef enum and int-like alias type names.""" + result = set() + for h in LVGL_SRC.rglob("*.h"): + text = h.read_text(errors="ignore") + # typedef enum { ... } lv_xxx_t; + for m in re.finditer( + r"typedef\s+enum\s*\{[^}]*\}\s*(lv_\w+_t)", text, re.DOTALL + ): + result.add(m.group(1)) + # typedef lv_xxx_t; + for m in re.finditer( + r"typedef\s+((?:unsigned\s+)?\w+)\s+(lv_\w+_t)\s*;", text + ): + base = m.group(1).strip() + if base in ( + "unsigned int", "unsigned char", "unsigned short", + "unsigned long", "int", "char", "short", "long", + "uint8_t", "uint16_t", "uint32_t", "uint64_t", + "int8_t", "int16_t", "int32_t", "int64_t", "size_t", + ): + result.add(m.group(2)) + return result + + +# Pre-scan: types safe to cast to int (enums + int-like typedefs) +_INT_SAFE_TYPES = _scan_enum_types() + + @dataclass class StructField: name: str @@ -127,11 +156,11 @@ def parse_struct_fields(body: str) -> list[StructField]: )) continue - fm = re.match(r"((?:const\s+)?\w[\w\s]*?\s*\*?)\s+(\*?\w+)", line) + fm = re.match(r"((?:const\s+)?[\w][\w\s]*?(?:\s*\*\s*(?:const\s*)?)*\*?)\s+(\*?\w+)", line) if fm: c_type = fm.group(1).strip() name = fm.group(2).strip().lstrip("*") - is_ptr = "*" in fm.group(1) or fm.group(2).startswith("*") + is_ptr = "*" in c_type or fm.group(2).startswith("*") fields.append(StructField( name=name, c_type=c_type, is_pointer=is_ptr, @@ -143,15 +172,31 @@ def parse_struct_fields(body: str) -> list[StructField]: return fields +def _find_structs(text: str): + """Find top-level struct _lv_*_t definitions with brace-balanced matching.""" + for m in re.finditer(r"struct\s+_lv_(\w+)_t\s*\{", text): + name = m.group(1) + start = m.end() + depth = 1 + i = start + while i < len(text) and depth > 0: + if text[i] == "{": + depth += 1 + elif text[i] == "}": + depth -= 1 + i += 1 + if depth == 0: + yield name, text[start:i - 1] + + def parse_widgets() -> dict[str, WidgetDef]: widgets = {} for private_h in sorted(WIDGETS_DIR.glob("*/lv_*_private.h")): text = private_h.read_text() widget_dir = private_h.parent.name - for m in re.finditer(r"struct\s+_lv_(\w+)_t\s*\{([^}]+)\}", text, re.DOTALL): - struct_name = f"lv_{m.group(1)}_t" - body = m.group(2) + for raw_name, body in _find_structs(text): + struct_name = f"lv_{raw_name}_t" first_line = "" for line in body.splitlines(): @@ -194,9 +239,21 @@ def _field_expr(f: StructField) -> str | None: return f'safe_area(self._wv, "{f.name}")' if f.c_type == "lv_point_t": return f'safe_point(self._wv, "{f.name}")' + # Known wrapper types — use snapshot() for rich output + _WRAPPER_TYPES = { + "lv_draw_buf_t": ("lvglgdb.lvgl.draw.lv_draw_buf", "LVDrawBuf"), + "lv_ll_t": ("lvglgdb.lvgl.misc.lv_ll", "LVList"), + "lv_anim_t": ("lvglgdb.lvgl.misc.lv_anim", "LVAnim"), + "lv_array_t": ("lvglgdb.lvgl.misc.lv_array", "LVArray"), + } + if f.c_type in _WRAPPER_TYPES: + mod, cls = _WRAPPER_TYPES[f.c_type] + return f'safe_wrapper(self._wv, "{f.name}", "{mod}", "{cls}")' if f.is_bitfield or f.c_type in SIMPLE_INT_TYPES or f.c_type.startswith(("uint", "int")): return f'int(self._wv.safe_field("{f.name}", 0))' - if f.c_type.startswith("lv_") and f.c_type.endswith("_t"): + # TODO: implement generic struct expansion (Value.to_dict) for + # non-enum lv_*_t types like lv_calendar_date_t. + if f.c_type in _INT_SAFE_TYPES: return f'int(self._wv.safe_field("{f.name}", 0))' return None @@ -264,6 +321,18 @@ def safe_point(obj, field_name): "x": int(val.safe_field("x", 0)), "y": int(val.safe_field("y", 0)), } + + +def safe_wrapper(obj, field_name, module_path, class_name): + """Read a struct field using its known Value wrapper, return snapshot dict.""" + val = obj.safe_field(field_name) + if val is None or not getattr(val, 'is_ok', True): + return None + import importlib + mod = importlib.import_module(module_path) + cls = getattr(mod, class_name) + wrapper = cls(val) + return wrapper.snapshot().as_dict() ''' @@ -302,6 +371,8 @@ def gen_widget_file(wdef: WidgetDef, widgets: dict[str, WidgetDef]) -> str: needs.add("safe_area") if "safe_point" in expr: needs.add("safe_point") + if "safe_wrapper" in expr: + needs.add("safe_wrapper") if needs: imports = ", ".join(sorted(needs))