chore(gdb): add LVObjClass wrapper with class hierarchy and format_coord utility

This commit is contained in:
Benign X
2026-03-06 12:18:33 +08:00
committed by VIFEX
parent 79c626c585
commit d803a26b43
9 changed files with 238 additions and 1 deletions

View File

@@ -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",
]

View File

@@ -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()

View File

@@ -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",
]

View File

@@ -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()

View File

@@ -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",
]

View File

@@ -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",
]

View File

@@ -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)}")

View File

@@ -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",
]

View File

@@ -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)