diff --git a/scripts/gdb/lvglgdb/__init__.py b/scripts/gdb/lvglgdb/__init__.py index f91b017aa0..495530b5f1 100644 --- a/scripts/gdb/lvglgdb/__init__.py +++ b/scripts/gdb/lvglgdb/__init__.py @@ -38,9 +38,11 @@ from .lvgl import ( LVTimer, LVImageDecoder, LVFsDrv, + format_coord, LVIndev, INDEV_TYPE_NAMES, LVGroup, + LVObjClass, ) from . import cmds as cmds @@ -82,7 +84,9 @@ __all__ = [ "LVTimer", "LVImageDecoder", "LVFsDrv", + "format_coord", "LVIndev", "INDEV_TYPE_NAMES", "LVGroup", + "LVObjClass", ] diff --git a/scripts/gdb/lvglgdb/cmds/__init__.py b/scripts/gdb/lvglgdb/cmds/__init__.py index fa1bfd80f8..b323326f2c 100644 --- a/scripts/gdb/lvglgdb/cmds/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/__init__.py @@ -1,6 +1,6 @@ import gdb -from .core import DumpObj, DumpIndev, DumpGroup +from .core import DumpObj, DumpIndev, DumpGroup, InfoObjClass from .display import DumpDisplayBuf from .draw import InfoDrawUnit, DumpDrawTask from .misc import ( @@ -45,6 +45,7 @@ DumpDrawTask() # Infos InfoStyle() InfoDrawUnit() +InfoObjClass() # Drivers Lvglobal() diff --git a/scripts/gdb/lvglgdb/cmds/core/__init__.py b/scripts/gdb/lvglgdb/cmds/core/__init__.py index 1fd0ca0bbf..27fb2d3dab 100644 --- a/scripts/gdb/lvglgdb/cmds/core/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/core/__init__.py @@ -1,9 +1,11 @@ from .lv_obj import DumpObj from .lv_indev import DumpIndev from .lv_group import DumpGroup +from .lv_obj_class import InfoObjClass __all__ = [ "DumpObj", "DumpIndev", "DumpGroup", + "InfoObjClass", ] diff --git a/scripts/gdb/lvglgdb/cmds/core/lv_obj_class.py b/scripts/gdb/lvglgdb/cmds/core/lv_obj_class.py new file mode 100644 index 0000000000..ae38aa7677 --- /dev/null +++ b/scripts/gdb/lvglgdb/cmds/core/lv_obj_class.py @@ -0,0 +1,46 @@ +import argparse + +import gdb + +from lvglgdb.lvgl.core.lv_obj_class import LVObjClass + + +class InfoObjClass(gdb.Command): + """show object class hierarchy or list all classes""" + + def __init__(self): + super(InfoObjClass, self).__init__( + "info obj_class", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION + ) + + def invoke(self, args, from_tty): + parser = argparse.ArgumentParser(description="Show object class info.") + parser.add_argument( + "--all", + action="store_true", + default=False, + help="List all registered object classes.", + ) + parser.add_argument( + "expr", + type=str, + nargs="?", + default=None, + help="Expression evaluating to an lv_obj_class_t.", + ) + try: + args = parser.parse_args(gdb.string_to_argv(args)) + except SystemExit: + return + + if args.all or not args.expr: + classes = LVObjClass.collect_all() + LVObjClass.print_entries(classes) + return + + try: + cls = LVObjClass(args.expr) + except gdb.error as e: + print(f"Error: {e}") + return + cls.print_info() diff --git a/scripts/gdb/lvglgdb/lvgl/__init__.py b/scripts/gdb/lvglgdb/lvgl/__init__.py index 118077d513..5dc8f4587a 100644 --- a/scripts/gdb/lvglgdb/lvgl/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/__init__.py @@ -7,6 +7,7 @@ from .core import ( LVIndev, INDEV_TYPE_NAMES, LVGroup, + LVObjClass, ) from .display import LVDisplay from .draw import ( @@ -48,6 +49,7 @@ from .misc import ( LVTimer, LVImageDecoder, LVFsDrv, + format_coord, ) __all__ = [ @@ -93,7 +95,9 @@ __all__ = [ "LVTimer", "LVImageDecoder", "LVFsDrv", + "format_coord", "LVIndev", "INDEV_TYPE_NAMES", "LVGroup", + "LVObjClass", ] diff --git a/scripts/gdb/lvglgdb/lvgl/core/__init__.py b/scripts/gdb/lvglgdb/lvgl/core/__init__.py index 5034f1c6b2..95395f2df8 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/core/__init__.py @@ -2,6 +2,7 @@ from .lv_obj import LVObject, ObjStyle, dump_obj_info, dump_obj_styles from .lv_global import curr_inst from .lv_indev import LVIndev, INDEV_TYPE_NAMES from .lv_group import LVGroup +from .lv_obj_class import LVObjClass __all__ = [ "LVObject", @@ -12,4 +13,5 @@ __all__ = [ "LVIndev", "INDEV_TYPE_NAMES", "LVGroup", + "LVObjClass", ] diff --git a/scripts/gdb/lvglgdb/lvgl/core/lv_obj_class.py b/scripts/gdb/lvglgdb/lvgl/core/lv_obj_class.py new file mode 100644 index 0000000000..cbab387109 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/core/lv_obj_class.py @@ -0,0 +1,149 @@ +import gdb +from prettytable import PrettyTable + +from lvglgdb.value import Value, ValueInput +from ..misc.lv_utils import format_coord + + +class LVObjClass(Value): + """LVGL object class wrapper""" + + def __init__(self, cls: ValueInput): + super().__init__(Value.normalize(cls, "lv_obj_class_t")) + + @property + def name(self) -> str: + n = self.super_value("name") + return n.string() if int(n) else "(unnamed)" + + @property + def base_class(self): + base = self.super_value("base_class") + return LVObjClass(base) if int(base) else None + + @property + def width_def(self) -> int: + return int(self.super_value("width_def")) + + @property + def height_def(self) -> int: + return int(self.super_value("height_def")) + + @property + def instance_size(self) -> int: + return int(self.super_value("instance_size")) + + @property + def editable(self) -> int: + return int(self.super_value("editable")) + + @property + def group_def(self) -> int: + return int(self.super_value("group_def")) + + @property + def theme_inheritable(self) -> bool: + return bool(int(self.super_value("theme_inheritable"))) + + @property + def constructor_cb(self) -> Value: + return self.super_value("constructor_cb") + + @property + def destructor_cb(self) -> Value: + return self.super_value("destructor_cb") + + @property + def event_cb(self) -> Value: + return self.super_value("event_cb") + + @property + def user_data(self) -> Value: + return self.super_value("user_data") + + def __iter__(self): + cls = self + while cls: + yield cls + cls = cls.base_class + + @staticmethod + def collect_all(): + """Collect all lv_obj_class_t globals from the symbol table.""" + import re + + # Search by symbol name suffix; filter by type to exclude non-class matches + output = gdb.execute("info variables _class$", to_string=True) + classes = [] + for line in output.splitlines(): + if "lv_obj_class_t" not in line: + continue + m = re.search(r"\b(lv_\w+_class)\s*;", line) + if m: + try: + cls = LVObjClass(m.group(1)) + classes.append(cls) + except gdb.error: + pass + return classes + + @staticmethod + def print_entries(classes): + """Print object classes as a PrettyTable.""" + table = PrettyTable() + table.field_names = [ + "#", + "name", + "base", + "size", + "editable", + "group_def", + "default_size", + "theme_inh", + ] + table.align = "l" + + for i, cls in enumerate(classes): + base = cls.base_class + base_name = base.name if base else "-" + table.add_row( + [ + i, + cls.name, + base_name, + cls.instance_size, + cls.editable, + cls.group_def, + f"({format_coord(cls.width_def)}, {format_coord(cls.height_def)})", + cls.theme_inheritable, + ] + ) + + if not table.rows: + print("No object classes found.") + else: + print(table) + + def print_info(self): + chain = list(self.__iter__()) + names = [c.name for c in chain] + print(f"ObjClass: {' -> '.join(names)}") + print( + f" size={self.instance_size} editable={self.editable} group_def={self.group_def}" + ) + w = format_coord(self.width_def) + h = format_coord(self.height_def) + print(f" default_size=({w}, {h}) theme_inheritable={self.theme_inheritable}") + ctor = int(self.constructor_cb) + dtor = int(self.destructor_cb) + evt = int(self.event_cb) + if ctor: + print( + f" constructor_cb = {self.constructor_cb.format_string(symbols=True)}" + ) + if dtor: + print( + f" destructor_cb = {self.destructor_cb.format_string(symbols=True)}" + ) + if evt: + print(f" event_cb = {self.event_cb.format_string(symbols=True)}") diff --git a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py index 1d08840b39..1adc7b0aad 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py @@ -27,6 +27,7 @@ from .lv_anim import LVAnim from .lv_timer import LVTimer from .lv_image_decoder import LVImageDecoder from .lv_fs import LVFsDrv +from .lv_utils import format_coord __all__ = [ "LVList", @@ -59,4 +60,5 @@ __all__ = [ "LVTimer", "LVImageDecoder", "LVFsDrv", + "format_coord", ] diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py index 20b75fddab..c10154113a 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py @@ -65,3 +65,30 @@ def build_global_field_map(field_type_name): return result except gdb.error: return {} + + +# LVGL coordinate type constants (from lv_area.h) +_COORD_TYPE_SHIFT = 29 +_COORD_TYPE_SPEC = 1 << _COORD_TYPE_SHIFT +_COORD_MAX = (1 << _COORD_TYPE_SHIFT) - 1 +_SIZE_CONTENT = _COORD_MAX | _COORD_TYPE_SPEC +_PCT_POS_MAX = (_COORD_MAX - 1) // 2 + + +def format_coord(val): + """Format an lv_coord_t value into a human-readable string. + + Decodes special LVGL coordinate encodings: + - LV_SIZE_CONTENT -> "CONTENT" + - LV_PCT(x) -> "x%" + - plain pixel -> "123" + """ + val = int(val) + if val == _SIZE_CONTENT: + return "CONTENT" + if val & _COORD_TYPE_SPEC: + plain = val & ~_COORD_TYPE_SPEC + if plain <= _PCT_POS_MAX: + return f"{plain}%" + return f"{_PCT_POS_MAX - plain}%" + return str(val)