mirror of
https://github.com/lvgl/lvgl.git
synced 2026-03-23 14:03:13 +08:00
chore(gdb): add snapshot/data_utils/formatter layers with declarative _DISPLAY_SPEC (#9866)
This commit is contained in:
@@ -25,15 +25,15 @@ Example of usage:
|
||||
(gdb) source lvgl/scripts/gdb/gdbinit.py
|
||||
|
||||
(gdb) dump obj -L 2
|
||||
obj@0x60700000dd10 (0,0,799,599)
|
||||
tabview@0x608000204ca0 (0,0,799,599)
|
||||
obj@0x607000025da0 (0,0,799,69)
|
||||
obj@0x607000025e80 (0,70,799,599)
|
||||
obj@0x60700002bd70 (743,543,791,591)
|
||||
btn@0x60700002c7f0 (747,547,787,587)
|
||||
keyboard@0x60d0000f7040 (0,300,799,599)
|
||||
dropdown-list@0x608000205420 (0,0,129,129)
|
||||
label@0x60d0000f7ba0 (22,22,56,39)
|
||||
obj@0x60700000dd10 0,0,799,599
|
||||
tabview@0x608000204ca0 0,0,799,599
|
||||
obj@0x607000025da0 0,0,799,69
|
||||
obj@0x607000025e80 0,70,799,599
|
||||
obj@0x60700002bd70 743,543,791,591
|
||||
btn@0x60700002c7f0 747,547,787,587
|
||||
keyboard@0x60d0000f7040 0,300,799,599
|
||||
dropdown-list@0x608000205420 0,0,129,129
|
||||
label@0x60d0000f7ba0 22,22,56,39
|
||||
(gdb)
|
||||
|
||||
The plugin provides the following commands.
|
||||
@@ -50,9 +50,10 @@ The plugin provides the following commands.
|
||||
- ``dump fs_drv``: List all registered filesystem drivers.
|
||||
- ``dump draw_task <expr>``: List draw tasks from a layer.
|
||||
- ``info style``: Inspect style properties of an ``lv_style_t`` or an ``lv_obj_t``.
|
||||
- ``info draw_unit``: Display all current drawing unit information.
|
||||
- ``info draw_unit``: Print raw struct details for each drawing unit.
|
||||
- ``info obj_class <expr>``: Show object class hierarchy.
|
||||
- ``info subject <expr>``: Show subject and its observers.
|
||||
- ``lvglobal``: (NuttX only) Set which LVGL instance to inspect.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -204,8 +205,13 @@ Example:
|
||||
|
||||
(gdb) info obj_class lv_button_class
|
||||
ObjClass: lv_button -> lv_obj -> lv_obj
|
||||
size=... editable=0 group_def=2
|
||||
default_size=(CONTENT, CONTENT) theme_inheritable=True
|
||||
name = lv_button
|
||||
base = lv_obj
|
||||
size = 48 editable=0 group_def=2
|
||||
editable = 0
|
||||
group_def = 2
|
||||
default_size = (CONTENT, CONTENT) theme_inheritable=True
|
||||
theme_inh = True
|
||||
|
||||
Inspect Subject
|
||||
***************
|
||||
@@ -218,4 +224,138 @@ Example:
|
||||
|
||||
(gdb) info subject &my_subject
|
||||
Subject: type=INT subscribers=2
|
||||
Observer: cb=0x... <my_cb> target=0x... for_obj=True
|
||||
Observer: cb=<my_cb> target=0x... for_obj=True
|
||||
|
||||
|
||||
Set LVGL Instance (NuttX)
|
||||
*************************
|
||||
|
||||
``lvglobal``: Set which LVGL instance to inspect by finding the ``lv_global``
|
||||
pointer. On single-instance systems, it auto-detects the global. On NuttX
|
||||
multi-process systems, use ``--pid`` to specify the target process.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
(gdb) lvglobal
|
||||
(gdb) lvglobal --pid 3
|
||||
|
||||
|
||||
Data Export API
|
||||
***************
|
||||
|
||||
Each wrapper class provides a ``snapshot()`` method that returns a ``Snapshot``
|
||||
object containing a pure Python dict (JSON-serializable) plus an optional
|
||||
reference to the original wrapper via ``_source``.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from lvglgdb import LVTimer, curr_inst
|
||||
|
||||
timers = list(curr_inst().timers())
|
||||
snap = timers[0].snapshot()
|
||||
|
||||
# Dict-like access
|
||||
print(snap["timer_cb"], snap["period"])
|
||||
|
||||
# JSON serialization
|
||||
import json
|
||||
print(json.dumps(snap.as_dict(), indent=2))
|
||||
|
||||
# Bulk export
|
||||
snapshots = LVTimer.snapshots(timers)
|
||||
data = [s.as_dict() for s in snapshots]
|
||||
|
||||
The ``Snapshot`` class supports dict-like read access (``[]``, ``keys()``,
|
||||
``len()``, ``in``, iteration) and ``as_dict()`` for JSON serialization.
|
||||
All values in ``as_dict()`` are pure Python types (``str``, ``int``, ``float``,
|
||||
``bool``, ``None``, ``dict``, ``list``) with no ``gdb.Value`` references.
|
||||
|
||||
Wrapper classes with ``snapshot()`` support: ``LVAnim``, ``LVTimer``,
|
||||
``LVIndev``, ``LVGroup``, ``LVObject``, ``LVObjClass``, ``LVObserver``,
|
||||
``LVSubject``, ``LVDrawTask``, ``LVDrawUnit``, ``LVFsDrv``,
|
||||
``LVImageDecoder``, ``LVCache``, ``LVDisplay``,
|
||||
``LVRedBlackTree``, ``LVEventDsc``, ``LVList``.
|
||||
|
||||
``LVStyle`` provides ``snapshots()`` (plural) which returns a list of
|
||||
``Snapshot`` objects for each style property, but does not have a singular
|
||||
``snapshot()`` method.
|
||||
|
||||
Most wrapper classes also provide a static ``snapshots(items)`` method for
|
||||
bulk export (e.g. ``LVAnim.snapshots(anims)``). Additionally,
|
||||
``LVImageCache`` and ``LVImageHeaderCache`` provide instance-level
|
||||
``snapshots()`` methods that export all cache entries.
|
||||
|
||||
|
||||
Architecture
|
||||
************
|
||||
|
||||
The GDB plugin decouples data extraction from display through a snapshot
|
||||
abstraction. Each wrapper class (``LVAnim``, ``LVTimer``, ``LVObject``, etc.)
|
||||
declares a ``_DISPLAY_SPEC`` describing its fields and exports a ``snapshot()``
|
||||
method that returns a self-describing ``Snapshot`` object carrying both the
|
||||
data dict and the display spec. The ``cmds/`` layer simply passes snapshots to
|
||||
generic formatters (``print_info``, ``print_spec_table``) which read the
|
||||
embedded spec to render output — no command needs to know the internal
|
||||
structure of any wrapper.
|
||||
|
||||
.. mermaid::
|
||||
:zoom:
|
||||
|
||||
graph RL
|
||||
subgraph "cmds/ layer"
|
||||
CMD["GDB Commands<br/>(dump obj, info style, ...)"]
|
||||
end
|
||||
|
||||
subgraph "formatter.py"
|
||||
PI["print_info(snapshot)"]
|
||||
PT["print_table()"]
|
||||
PST["print_spec_table(snapshots)"]
|
||||
RTC["resolve_table_columns(spec)"]
|
||||
PST --> RTC
|
||||
PST --> PT
|
||||
end
|
||||
|
||||
subgraph "snapshot.py"
|
||||
SNAP["Snapshot<br/>._data (pure dict)<br/>._display_spec<br/>._source"]
|
||||
end
|
||||
|
||||
subgraph "data_utils.py"
|
||||
DU["ptr_or_none()<br/>fmt_cb()<br/>..."]
|
||||
end
|
||||
|
||||
subgraph "Wrapper classes"
|
||||
direction TB
|
||||
DS["_DISPLAY_SPEC<br/>{info, table, empty_msg}"]
|
||||
SN["snapshot() → Snapshot"]
|
||||
SNS["snapshots() → list[Snapshot]"]
|
||||
SN --> SNAP
|
||||
SN -. "display_spec=" .-> DS
|
||||
SNS --> SN
|
||||
end
|
||||
|
||||
subgraph "Wrappers"
|
||||
direction LR
|
||||
W1["LVAnim"]
|
||||
W2["LVTimer"]
|
||||
W3["LVCache"]
|
||||
W4["LVObject"]
|
||||
W5["LVGroup"]
|
||||
W6["LVIndev"]
|
||||
W7["...others"]
|
||||
end
|
||||
|
||||
CMD -- "wrapper.snapshot()" --> SN
|
||||
CMD -- "print_info(snap)" --> PI
|
||||
CMD -- "print_spec_table(snaps)" --> PST
|
||||
PI -- "reads _display_spec" --> SNAP
|
||||
PST -- "reads _display_spec" --> SNAP
|
||||
SN -- "uses" --> DU
|
||||
W1 & W2 & W3 & W4 & W5 & W6 & W7 -. "each defines" .-> DS
|
||||
|
||||
style PI fill:#4CAF50,color:#fff
|
||||
style PT fill:#4CAF50,color:#fff
|
||||
style PST fill:#4CAF50,color:#fff
|
||||
style RTC fill:#4CAF50,color:#fff
|
||||
style SNAP fill:#2196F3,color:#fff
|
||||
style DU fill:#FF9800,color:#fff
|
||||
style DS fill:#9C27B0,color:#fff
|
||||
|
||||
@@ -13,7 +13,6 @@ from .lvgl import (
|
||||
ObjStyle,
|
||||
LVStyle,
|
||||
StyleEntry,
|
||||
dump_style_info,
|
||||
dump_obj_styles,
|
||||
dump_obj_info,
|
||||
style_prop_name,
|
||||
@@ -62,7 +61,6 @@ __all__ = [
|
||||
"ObjStyle",
|
||||
"LVStyle",
|
||||
"StyleEntry",
|
||||
"dump_style_info",
|
||||
"dump_obj_styles",
|
||||
"dump_obj_info",
|
||||
"style_prop_name",
|
||||
|
||||
@@ -2,6 +2,7 @@ import gdb
|
||||
|
||||
from lvglgdb.lvgl import curr_inst
|
||||
from lvglgdb.lvgl.core.lv_group import LVGroup
|
||||
from lvglgdb.lvgl.formatter import print_spec_table
|
||||
|
||||
|
||||
class DumpGroup(gdb.Command):
|
||||
@@ -13,4 +14,9 @@ class DumpGroup(gdb.Command):
|
||||
)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
LVGroup.print_entries(curr_inst().groups())
|
||||
groups = curr_inst().groups()
|
||||
snaps = LVGroup.snapshots(groups)
|
||||
if snaps:
|
||||
print_spec_table(snaps)
|
||||
else:
|
||||
print(LVGroup._DISPLAY_SPEC["empty_msg"])
|
||||
|
||||
@@ -2,6 +2,7 @@ import gdb
|
||||
|
||||
from lvglgdb.lvgl import curr_inst
|
||||
from lvglgdb.lvgl.core.lv_indev import LVIndev
|
||||
from lvglgdb.lvgl.formatter import print_spec_table
|
||||
|
||||
|
||||
class DumpIndev(gdb.Command):
|
||||
@@ -13,4 +14,9 @@ class DumpIndev(gdb.Command):
|
||||
)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
LVIndev.print_entries(curr_inst().indevs())
|
||||
indevs = curr_inst().indevs()
|
||||
snaps = LVIndev.snapshots(indevs)
|
||||
if snaps:
|
||||
print_spec_table(snaps)
|
||||
else:
|
||||
print(LVIndev._DISPLAY_SPEC["empty_msg"])
|
||||
|
||||
@@ -3,6 +3,7 @@ import argparse
|
||||
import gdb
|
||||
|
||||
from lvglgdb.lvgl.core.lv_obj_class import LVObjClass
|
||||
from lvglgdb.lvgl.formatter import print_info, print_spec_table
|
||||
|
||||
|
||||
class InfoObjClass(gdb.Command):
|
||||
@@ -35,7 +36,11 @@ class InfoObjClass(gdb.Command):
|
||||
|
||||
if args.all or not args.expr:
|
||||
classes = LVObjClass.collect_all()
|
||||
LVObjClass.print_entries(classes)
|
||||
snaps = LVObjClass.snapshots(classes)
|
||||
if snaps:
|
||||
print_spec_table(snaps)
|
||||
else:
|
||||
print(LVObjClass._DISPLAY_SPEC["empty_msg"])
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -43,4 +48,4 @@ class InfoObjClass(gdb.Command):
|
||||
except gdb.error as e:
|
||||
print(f"Error: {e}")
|
||||
return
|
||||
cls.print_info()
|
||||
print_info(cls.snapshot())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import gdb
|
||||
|
||||
from lvglgdb.lvgl.core.lv_observer import LVSubject
|
||||
from lvglgdb.lvgl.formatter import print_info
|
||||
|
||||
|
||||
class InfoSubject(gdb.Command):
|
||||
@@ -20,4 +21,4 @@ class InfoSubject(gdb.Command):
|
||||
except gdb.error as e:
|
||||
print(f"Error: {e}")
|
||||
return
|
||||
subject.print_info()
|
||||
print_info(subject.snapshot())
|
||||
|
||||
@@ -2,6 +2,7 @@ import gdb
|
||||
|
||||
from lvglgdb.value import Value
|
||||
from lvglgdb.lvgl.draw.lv_draw_task import LVDrawTask
|
||||
from lvglgdb.lvgl.formatter import print_spec_table
|
||||
|
||||
|
||||
class DumpDrawTask(gdb.Command):
|
||||
@@ -22,4 +23,6 @@ class DumpDrawTask(gdb.Command):
|
||||
if not int(task_head):
|
||||
print("No draw tasks on this layer.")
|
||||
return
|
||||
LVDrawTask.print_entries(LVDrawTask(task_head))
|
||||
print_spec_table(
|
||||
LVDrawTask.snapshots(LVDrawTask(task_head)),
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import gdb
|
||||
|
||||
from lvglgdb.lvgl import curr_inst
|
||||
from lvglgdb.lvgl.misc.lv_anim import LVAnim
|
||||
from lvglgdb.lvgl.formatter import print_info, print_spec_table
|
||||
|
||||
|
||||
class DumpAnim(gdb.Command):
|
||||
@@ -20,7 +21,7 @@ class DumpAnim(gdb.Command):
|
||||
|
||||
if args.strip() == "--detail":
|
||||
for anim in anims:
|
||||
anim.print_info()
|
||||
print_info(anim.snapshot())
|
||||
print()
|
||||
else:
|
||||
LVAnim.print_entries(anims)
|
||||
print_spec_table(LVAnim.snapshots(anims))
|
||||
|
||||
@@ -37,7 +37,29 @@ class DumpCache(gdb.Command):
|
||||
print("Invalid cache: ", args.cache)
|
||||
return
|
||||
|
||||
cache.print_entries()
|
||||
from lvglgdb.lvgl.formatter import print_info, print_spec_table
|
||||
|
||||
# Print cache-level info
|
||||
snap = cache.snapshot()
|
||||
print_info(snap)
|
||||
|
||||
# Print cache entries
|
||||
snaps = cache.snapshots()
|
||||
extra_fields = getattr(cache, "_last_extra_fields", [])
|
||||
|
||||
col_align = {"src": "l", "type": "c"}
|
||||
|
||||
extra_columns = ["entry"] + extra_fields if extra_fields else ["entry"]
|
||||
|
||||
def _extra_row(d):
|
||||
extras = d.get("extra_fields", {})
|
||||
extra_vals = [extras.get(f, "") for f in extra_fields]
|
||||
return [d["entry_addr"]] + extra_vals
|
||||
|
||||
print_spec_table(snaps,
|
||||
align="r", numbered=False, col_align=col_align,
|
||||
extra_columns=extra_columns,
|
||||
extra_row_fn=_extra_row)
|
||||
|
||||
|
||||
class CheckPrefix(gdb.Command):
|
||||
|
||||
@@ -2,6 +2,7 @@ import gdb
|
||||
|
||||
from lvglgdb.lvgl import curr_inst
|
||||
from lvglgdb.lvgl.misc.lv_fs import LVFsDrv
|
||||
from lvglgdb.lvgl.formatter import print_spec_table
|
||||
|
||||
|
||||
class DumpFsDrv(gdb.Command):
|
||||
@@ -13,4 +14,9 @@ class DumpFsDrv(gdb.Command):
|
||||
)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
LVFsDrv.print_entries(curr_inst().fs_drivers())
|
||||
drivers = curr_inst().fs_drivers()
|
||||
snaps = LVFsDrv.snapshots(drivers)
|
||||
if snaps:
|
||||
print_spec_table(snaps)
|
||||
else:
|
||||
print(LVFsDrv._DISPLAY_SPEC["empty_msg"])
|
||||
|
||||
@@ -2,6 +2,7 @@ import gdb
|
||||
|
||||
from lvglgdb.lvgl import curr_inst
|
||||
from lvglgdb.lvgl.misc.lv_image_decoder import LVImageDecoder
|
||||
from lvglgdb.lvgl.formatter import print_spec_table
|
||||
|
||||
|
||||
class DumpImageDecoder(gdb.Command):
|
||||
@@ -13,4 +14,9 @@ class DumpImageDecoder(gdb.Command):
|
||||
)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
LVImageDecoder.print_entries(curr_inst().image_decoders())
|
||||
decoders = curr_inst().image_decoders()
|
||||
snaps = LVImageDecoder.snapshots(decoders)
|
||||
if snaps:
|
||||
print_spec_table(snaps)
|
||||
else:
|
||||
print(LVImageDecoder._DISPLAY_SPEC["empty_msg"])
|
||||
|
||||
@@ -2,6 +2,25 @@ import argparse
|
||||
import gdb
|
||||
|
||||
from lvglgdb.lvgl import LVStyle, dump_obj_styles
|
||||
from lvglgdb.lvgl.formatter import print_table
|
||||
|
||||
|
||||
def _style_row_fn(_i, d):
|
||||
"""Format a style property row with optional ANSI color block."""
|
||||
value_str = d["value_str"]
|
||||
color_rgb = d.get("color_rgb")
|
||||
if color_rgb:
|
||||
r, g, b = color_rgb["r"], color_rgb["g"], color_rgb["b"]
|
||||
value_str = f"{value_str} \033[48;2;{r};{g};{b}m \033[0m"
|
||||
return [d["prop_name"], value_str]
|
||||
|
||||
|
||||
def print_style_props(entries):
|
||||
"""Print style properties as a 2-column table with optional ANSI color."""
|
||||
print_table(
|
||||
entries, ["prop", "value"], _style_row_fn,
|
||||
"Empty style.", align="l", numbered=False,
|
||||
)
|
||||
|
||||
|
||||
class InfoStyle(gdb.Command):
|
||||
@@ -43,6 +62,6 @@ class InfoStyle(gdb.Command):
|
||||
if not style:
|
||||
print("Invalid style:", args.style)
|
||||
return
|
||||
LVStyle(style).print_entries()
|
||||
print_style_props(LVStyle(style).snapshots())
|
||||
else:
|
||||
print("Usage: info style <style_var> or info style --obj <obj_var>")
|
||||
|
||||
@@ -2,6 +2,7 @@ import gdb
|
||||
|
||||
from lvglgdb.lvgl import curr_inst
|
||||
from lvglgdb.lvgl.misc.lv_timer import LVTimer
|
||||
from lvglgdb.lvgl.formatter import print_spec_table
|
||||
|
||||
|
||||
class DumpTimer(gdb.Command):
|
||||
@@ -13,4 +14,9 @@ class DumpTimer(gdb.Command):
|
||||
)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
LVTimer.print_entries(curr_inst().timers())
|
||||
timers = curr_inst().timers()
|
||||
snaps = LVTimer.snapshots(timers)
|
||||
if snaps:
|
||||
print_spec_table(snaps)
|
||||
else:
|
||||
print(LVTimer._DISPLAY_SPEC["empty_msg"])
|
||||
|
||||
@@ -25,18 +25,13 @@ from .misc import (
|
||||
LVList,
|
||||
LVStyle,
|
||||
StyleEntry,
|
||||
dump_style_info,
|
||||
style_prop_name,
|
||||
decode_selector,
|
||||
format_style_value,
|
||||
LVRedBlackTree,
|
||||
dump_rb_info,
|
||||
LVCache,
|
||||
dump_cache_info,
|
||||
LVCacheEntry,
|
||||
dump_cache_entry_info,
|
||||
LVCacheLRURB,
|
||||
dump_lru_rb_cache_info,
|
||||
LVCacheLRURBIterator,
|
||||
LVCacheIteratorBase,
|
||||
LVImageCache,
|
||||
@@ -54,6 +49,9 @@ from .misc import (
|
||||
LVFsDrv,
|
||||
format_coord,
|
||||
)
|
||||
from .snapshot import Snapshot
|
||||
from .data_utils import fmt_cb, ptr_or_none
|
||||
from . import formatter
|
||||
|
||||
__all__ = [
|
||||
"LVObject",
|
||||
@@ -69,20 +67,15 @@ __all__ = [
|
||||
"LVList",
|
||||
"LVStyle",
|
||||
"StyleEntry",
|
||||
"dump_style_info",
|
||||
"dump_obj_styles",
|
||||
"dump_obj_info",
|
||||
"style_prop_name",
|
||||
"decode_selector",
|
||||
"format_style_value",
|
||||
"LVRedBlackTree",
|
||||
"dump_rb_info",
|
||||
"LVCache",
|
||||
"dump_cache_info",
|
||||
"LVCacheEntry",
|
||||
"dump_cache_entry_info",
|
||||
"LVCacheLRURB",
|
||||
"dump_lru_rb_cache_info",
|
||||
"LVCacheLRURBIterator",
|
||||
"LVCacheIteratorBase",
|
||||
"LVImageCache",
|
||||
@@ -106,4 +99,8 @@ __all__ = [
|
||||
"LVSubject",
|
||||
"LVObserver",
|
||||
"SUBJECT_TYPE_NAMES",
|
||||
"Snapshot",
|
||||
"fmt_cb",
|
||||
"ptr_or_none",
|
||||
"formatter",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from ..misc.lv_ll import LVList
|
||||
|
||||
@@ -7,6 +5,18 @@ from ..misc.lv_ll import LVList
|
||||
class LVGroup(Value):
|
||||
"""LVGL focus group wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("objects", "obj_count"),
|
||||
("frozen", "frozen"),
|
||||
("editing", "editing"),
|
||||
("wrap", "wrap"),
|
||||
("focused", "focused"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No focus groups.",
|
||||
}
|
||||
|
||||
def __init__(self, group: ValueInput):
|
||||
super().__init__(Value.normalize(group, "lv_group_t"))
|
||||
|
||||
@@ -55,34 +65,38 @@ class LVGroup(Value):
|
||||
for obj_ptr in LVList(self.obj_ll, "lv_obj_t"):
|
||||
yield LVObject(obj_ptr)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(groups):
|
||||
"""Print focus groups as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = ["#", "objects", "frozen", "editing", "wrap", "focused"]
|
||||
table.align = "l"
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
for i, group in enumerate(groups):
|
||||
focus = group.obj_focus
|
||||
if focus:
|
||||
from .lv_obj import LVObject
|
||||
focus = self.obj_focus
|
||||
if focus:
|
||||
from .lv_obj import LVObject
|
||||
|
||||
focus_obj = LVObject(focus)
|
||||
focus_str = f"{focus_obj.class_name}@{int(focus):x}"
|
||||
else:
|
||||
focus_str = "(none)"
|
||||
table.add_row(
|
||||
[
|
||||
i,
|
||||
group.obj_count,
|
||||
group.frozen,
|
||||
group.editing,
|
||||
group.wrap,
|
||||
focus_str,
|
||||
]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No focus groups.")
|
||||
focus_obj = LVObject(focus)
|
||||
focus_str = f"{focus_obj.class_name}@{int(focus):x}"
|
||||
focused_addr = hex(int(focus))
|
||||
else:
|
||||
print(table)
|
||||
focus_str = "(none)"
|
||||
focused_addr = None
|
||||
|
||||
member_addrs = []
|
||||
for obj_ptr in LVList(self.obj_ll, "lv_obj_t"):
|
||||
addr = int(obj_ptr)
|
||||
if addr:
|
||||
member_addrs.append(hex(addr))
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"obj_count": self.obj_count,
|
||||
"frozen": self.frozen,
|
||||
"editing": self.editing,
|
||||
"wrap": self.wrap,
|
||||
"focused": focus_str,
|
||||
"focused_addr": focused_addr,
|
||||
"member_addrs": member_addrs,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def snapshots(groups):
|
||||
return [group.snapshot() for group in groups]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from .lv_indev_consts import INDEV_TYPE_NAMES
|
||||
|
||||
@@ -7,6 +5,20 @@ from .lv_indev_consts import INDEV_TYPE_NAMES
|
||||
class LVIndev(Value):
|
||||
"""LVGL input device wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("type", "type_name"),
|
||||
("enabled", "enabled"),
|
||||
("state", "state"),
|
||||
("read_cb", "read_cb"),
|
||||
("long_press_time", "long_press_time"),
|
||||
("scroll_limit", "scroll_limit"),
|
||||
("group", "group"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No input devices.",
|
||||
}
|
||||
|
||||
def __init__(self, indev: ValueInput):
|
||||
super().__init__(Value.normalize(indev, "lv_indev_t"))
|
||||
|
||||
@@ -70,40 +82,28 @@ class LVIndev(Value):
|
||||
def driver_data(self) -> Value:
|
||||
return self.super_value("driver_data")
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb, ptr_or_none
|
||||
|
||||
grp = int(self.group)
|
||||
grp_str = f"0x{grp:x}" if grp else "-"
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"type": self.type,
|
||||
"type_name": self.type_name,
|
||||
"enabled": self.enabled,
|
||||
"state": self.state,
|
||||
"read_cb": fmt_cb(self.read_cb),
|
||||
"long_press_time": self.long_press_time,
|
||||
"scroll_limit": self.scroll_limit,
|
||||
"group": grp_str,
|
||||
"display_addr": ptr_or_none(self.disp),
|
||||
"group_addr": ptr_or_none(self.group),
|
||||
"read_timer_addr": ptr_or_none(self.read_timer),
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(indevs):
|
||||
"""Print input devices as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = [
|
||||
"#",
|
||||
"type",
|
||||
"enabled",
|
||||
"state",
|
||||
"read_cb",
|
||||
"long_press_time",
|
||||
"scroll_limit",
|
||||
"group",
|
||||
]
|
||||
table.align = "l"
|
||||
|
||||
for i, indev in enumerate(indevs):
|
||||
cb_str = indev.read_cb.format_string(symbols=True)
|
||||
grp = int(indev.group)
|
||||
grp_str = f"0x{grp:x}" if grp else "-"
|
||||
table.add_row(
|
||||
[
|
||||
i,
|
||||
indev.type_name,
|
||||
indev.enabled,
|
||||
indev.state,
|
||||
cb_str,
|
||||
indev.long_press_time,
|
||||
indev.scroll_limit,
|
||||
grp_str,
|
||||
]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No input devices.")
|
||||
else:
|
||||
print(table)
|
||||
def snapshots(indevs):
|
||||
return [indev.snapshot() for indev in indevs]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from lvglgdb.lvgl.misc.lv_style import LVStyle, StyleEntry, decode_selector
|
||||
from lvglgdb.lvgl.misc.lv_style import LVStyle, decode_selector
|
||||
|
||||
|
||||
class ObjStyle:
|
||||
@@ -25,10 +25,35 @@ class ObjStyle:
|
||||
def __len__(self):
|
||||
return len(list(self.style))
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
props = [s.as_dict() for s in self.style.snapshots()]
|
||||
d = {
|
||||
"index": self.index,
|
||||
"selector": self.selector,
|
||||
"selector_str": self.selector_str,
|
||||
"flags_str": self.flags_str,
|
||||
"properties": props,
|
||||
}
|
||||
return Snapshot(d, source=self)
|
||||
|
||||
|
||||
class LVObject(Value):
|
||||
"""LVGL object"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: (
|
||||
f"{d['class_name']}@{d['addr']}"
|
||||
f" {d['coords']['x1']},{d['coords']['y1']},"
|
||||
f"{d['coords']['x2']},{d['coords']['y2']}"
|
||||
)),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "",
|
||||
}
|
||||
|
||||
def __init__(self, obj: ValueInput):
|
||||
super().__init__(Value.normalize(obj, "lv_obj_t"))
|
||||
|
||||
@@ -105,22 +130,73 @@ class LVObject(Value):
|
||||
def get_child(self, index: int):
|
||||
return self.spec_attr.children[index] if self.spec_attr else None
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import ptr_or_none
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"class_name": self.class_name,
|
||||
"coords": {
|
||||
"x1": self.x1,
|
||||
"y1": self.y1,
|
||||
"x2": self.x2,
|
||||
"y2": self.y2,
|
||||
},
|
||||
"child_count": int(self.child_count),
|
||||
"style_count": int(self.style_cnt),
|
||||
"parent_addr": ptr_or_none(self.super_value("parent")),
|
||||
"group_addr": self._get_group_addr(),
|
||||
}
|
||||
if include_children:
|
||||
d["children"] = [
|
||||
c.snapshot(include_children=True, include_styles=include_styles).as_dict()
|
||||
for c in self.children
|
||||
]
|
||||
if include_styles:
|
||||
d["styles"] = [s.snapshot().as_dict() for s in self.obj_styles]
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
def _get_group_addr(self):
|
||||
"""Get group address from spec_attr, or None."""
|
||||
spec = self.spec_attr
|
||||
if not spec or not int(spec):
|
||||
return None
|
||||
try:
|
||||
grp = spec.group
|
||||
addr = int(grp)
|
||||
return hex(addr) if addr else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def dump_obj_info(obj: LVObject):
|
||||
clzname = obj.class_name
|
||||
coords = f"{obj.x1},{obj.y1},{obj.x2},{obj.y2}"
|
||||
print(f"{clzname}@{hex(obj)} {coords}")
|
||||
from lvglgdb.lvgl.formatter import print_info
|
||||
print_info(obj.snapshot())
|
||||
|
||||
|
||||
def dump_obj_styles(obj: ValueInput):
|
||||
"""Print all styles of an object, reusing LVStyle.print_entries()."""
|
||||
"""Print all styles of an object."""
|
||||
from lvglgdb.lvgl.formatter import print_table
|
||||
|
||||
lv_obj = LVObject(obj)
|
||||
|
||||
has_any = False
|
||||
for obj_style in lv_obj.obj_styles:
|
||||
has_any = True
|
||||
print(f"[{obj_style.index}] {obj_style.selector_str} {obj_style.flags_str}")
|
||||
obj_style.style.print_entries()
|
||||
|
||||
if not has_any:
|
||||
d = lv_obj.snapshot(include_styles=True)
|
||||
styles = d.get("styles")
|
||||
if not styles:
|
||||
print("No styles applied.")
|
||||
return
|
||||
|
||||
def _style_row(_i, entry):
|
||||
value_str = entry["value_str"]
|
||||
color_rgb = entry.get("color_rgb")
|
||||
if color_rgb:
|
||||
r, g, b = color_rgb["r"], color_rgb["g"], color_rgb["b"]
|
||||
value_str = f"{value_str} \033[48;2;{r};{g};{b}m \033[0m"
|
||||
return [entry["prop_name"], value_str]
|
||||
|
||||
for s in styles:
|
||||
print(f"[{s['index']}] {s['selector_str']} {s['flags_str']}")
|
||||
print_table(
|
||||
s.get("properties", []), ["prop", "value"], _style_row,
|
||||
"Empty style.", align="l", numbered=False,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import gdb
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from ..misc.lv_utils import format_coord
|
||||
@@ -8,6 +7,37 @@ from ..misc.lv_utils import format_coord
|
||||
class LVObjClass(Value):
|
||||
"""LVGL object class wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: (
|
||||
f"ObjClass: {' -> '.join(d.get('class_chain', [d['name']]))}"
|
||||
)),
|
||||
("name", "name"),
|
||||
("base", "base_class"),
|
||||
("size", lambda d: (
|
||||
f"{d['instance_size']} editable={d['editable']}"
|
||||
f" group_def={d['group_def']}"
|
||||
)),
|
||||
("editable", "editable"),
|
||||
("group_def", "group_def"),
|
||||
("default_size", lambda d: (
|
||||
f"({d['width_def_str']}, {d['height_def_str']})"
|
||||
f" theme_inheritable={d['theme_inheritable']}"
|
||||
)),
|
||||
("theme_inh", "theme_inheritable"),
|
||||
("_skip_if", "constructor_cb", "-", "constructor_cb"),
|
||||
("_skip_if", "destructor_cb", "-", "destructor_cb"),
|
||||
("_skip_if", "event_cb", "-", "event_cb"),
|
||||
],
|
||||
"table": [
|
||||
("size", "instance_size"),
|
||||
("default_size", lambda d: (
|
||||
f"({d['width_def_str']}, {d['height_def_str']})"
|
||||
)),
|
||||
],
|
||||
"empty_msg": "No object classes found.",
|
||||
}
|
||||
|
||||
def __init__(self, cls: ValueInput):
|
||||
super().__init__(Value.normalize(cls, "lv_obj_class_t"))
|
||||
|
||||
@@ -87,63 +117,30 @@ class LVObjClass(Value):
|
||||
pass
|
||||
return classes
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb
|
||||
|
||||
base = self.base_class
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"name": self.name,
|
||||
"base_class": base.name if base else "-",
|
||||
"instance_size": self.instance_size,
|
||||
"editable": self.editable,
|
||||
"group_def": self.group_def,
|
||||
"width_def": self.width_def,
|
||||
"height_def": self.height_def,
|
||||
"width_def_str": format_coord(self.width_def),
|
||||
"height_def_str": format_coord(self.height_def),
|
||||
"theme_inheritable": self.theme_inheritable,
|
||||
"constructor_cb": fmt_cb(self.constructor_cb),
|
||||
"destructor_cb": fmt_cb(self.destructor_cb),
|
||||
"event_cb": fmt_cb(self.event_cb),
|
||||
"class_chain": [c.name for c in self.__iter__()],
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@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)}")
|
||||
def snapshots(classes):
|
||||
return [cls.snapshot() for cls in classes]
|
||||
|
||||
@@ -37,16 +37,44 @@ class LVObserver(Value):
|
||||
def for_obj(self) -> bool:
|
||||
return bool(int(self.super_value("for_obj")))
|
||||
|
||||
def print_info(self):
|
||||
cb_str = self.cb.format_string(symbols=True, address=True)
|
||||
print(
|
||||
f" Observer: cb={cb_str} target={self.target}" f" for_obj={self.for_obj}"
|
||||
)
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb, ptr_or_none
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"cb": fmt_cb(self.cb),
|
||||
"target": str(self.target),
|
||||
"for_obj": self.for_obj,
|
||||
"user_data": str(self.user_data),
|
||||
"subject_addr": ptr_or_none(self.subject),
|
||||
"target_addr": ptr_or_none(self.target),
|
||||
}
|
||||
return Snapshot(d, source=self)
|
||||
|
||||
|
||||
class LVSubject(Value):
|
||||
"""LVGL subject wrapper"""
|
||||
|
||||
_OBSERVER_FIELDS = [
|
||||
("_title", lambda d: (
|
||||
f" Observer: cb={d['cb']} target={d['target']}"
|
||||
f" for_obj={d['for_obj']}"
|
||||
)),
|
||||
]
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: (
|
||||
f"Subject: type={d['type_name']}"
|
||||
f" subscribers={len(d.get('observers', []))}"
|
||||
)),
|
||||
("_children", "observers", _OBSERVER_FIELDS),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "",
|
||||
}
|
||||
|
||||
def __init__(self, subject: ValueInput):
|
||||
super().__init__(Value.normalize(subject, "lv_subject_t"))
|
||||
|
||||
@@ -70,8 +98,21 @@ class LVSubject(Value):
|
||||
for obs in LVList(self.subs_ll, "lv_observer_t"):
|
||||
yield LVObserver(obs)
|
||||
|
||||
def print_info(self):
|
||||
ll = LVList(self.subs_ll, "lv_observer_t")
|
||||
print(f"Subject: type={self.type_name} subscribers={ll.len}")
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
observers = []
|
||||
observer_addrs = []
|
||||
for obs in self.__iter__():
|
||||
obs.print_info()
|
||||
observers.append(obs.snapshot().as_dict())
|
||||
observer_addrs.append(hex(int(obs)))
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"type": self.type,
|
||||
"type_name": self.type_name,
|
||||
"size": self.size,
|
||||
"observers": observers,
|
||||
"observer_addrs": observer_addrs,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
18
scripts/gdb/lvglgdb/lvgl/data_utils.py
Normal file
18
scripts/gdb/lvglgdb/lvgl/data_utils.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from typing import Optional
|
||||
|
||||
from lvglgdb.value import Value
|
||||
|
||||
|
||||
def fmt_cb(cb: Value) -> str:
|
||||
"""Format callback pointer as resolved symbol string or '-' for NULL.
|
||||
Strips null bytes that may appear in some GDB output."""
|
||||
addr = int(cb)
|
||||
if not addr:
|
||||
return "-"
|
||||
return cb.format_string(symbols=True, address=True).replace("\x00", "")
|
||||
|
||||
|
||||
def ptr_or_none(val: Value) -> Optional[str]:
|
||||
"""Convert pointer to hex string or None if NULL."""
|
||||
addr = int(val)
|
||||
return hex(addr) if addr else None
|
||||
@@ -6,6 +6,17 @@ from lvglgdb.value import Value, ValueInput
|
||||
class LVDisplay(Value):
|
||||
"""LVGL display"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: f"Display @{d['addr']}"),
|
||||
("hor_res", "hor_res"),
|
||||
("ver_res", "ver_res"),
|
||||
("screen_count", "screen_count"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No displays.",
|
||||
}
|
||||
|
||||
def __init__(self, disp: ValueInput):
|
||||
super().__init__(Value.normalize(disp, "lv_display_t"))
|
||||
|
||||
@@ -43,3 +54,14 @@ class LVDisplay(Value):
|
||||
"""Get currently active draw buffer (may be None)"""
|
||||
buf_ptr = self.super_value("buf_act")
|
||||
return LVDrawBuf(buf_ptr) if buf_ptr else None
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"hor_res": self.hor_res,
|
||||
"ver_res": self.ver_res,
|
||||
"screen_count": int(self.screen_cnt),
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from .lv_draw_consts import DRAW_TASK_TYPE_NAMES, DRAW_TASK_STATE_NAMES
|
||||
|
||||
@@ -7,6 +5,21 @@ from .lv_draw_consts import DRAW_TASK_TYPE_NAMES, DRAW_TASK_STATE_NAMES
|
||||
class LVDrawTask(Value):
|
||||
"""LVGL draw task wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("type", "type_name"),
|
||||
("state", "state_name"),
|
||||
("area", lambda d: (
|
||||
f"({d['area']['x1']}, {d['area']['y1']}, "
|
||||
f"{d['area']['x2']}, {d['area']['y2']})"
|
||||
)),
|
||||
("opa", "opa"),
|
||||
("unit_id", "preferred_draw_unit_id"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No draw tasks.",
|
||||
}
|
||||
|
||||
def __init__(self, task: ValueInput):
|
||||
super().__init__(Value.normalize(task, "lv_draw_task_t"))
|
||||
|
||||
@@ -29,7 +42,7 @@ class LVDrawTask(Value):
|
||||
@property
|
||||
def area(self) -> tuple:
|
||||
a = self.super_value("area")
|
||||
return (int(a["x1"]), int(a["y1"]), int(a["x2"]), int(a["y2"]))
|
||||
return (int(a.x1), int(a.y1), int(a.x2), int(a.y2))
|
||||
|
||||
@property
|
||||
def opa(self) -> int:
|
||||
@@ -50,26 +63,22 @@ class LVDrawTask(Value):
|
||||
def preferred_draw_unit_id(self) -> int:
|
||||
return int(self.super_value("preferred_draw_unit_id"))
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
x1, y1, x2, y2 = self.area
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"type": self.type,
|
||||
"type_name": self.type_name,
|
||||
"state": self.state,
|
||||
"state_name": self.state_name,
|
||||
"area": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
|
||||
"opa": self.opa,
|
||||
"preferred_draw_unit_id": self.preferred_draw_unit_id,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(tasks):
|
||||
"""Print draw tasks as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = ["#", "type", "state", "area", "opa", "unit_id"]
|
||||
table.align = "l"
|
||||
|
||||
for i, t in enumerate(tasks):
|
||||
table.add_row(
|
||||
[
|
||||
i,
|
||||
t.type_name,
|
||||
t.state_name,
|
||||
t.area,
|
||||
t.opa,
|
||||
t.preferred_draw_unit_id,
|
||||
]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No draw tasks.")
|
||||
else:
|
||||
print(table)
|
||||
def snapshots(tasks):
|
||||
return [t.snapshot() for t in tasks]
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
|
||||
|
||||
class LVDrawUnit(Value):
|
||||
"""LVGL draw unit wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("name", "name"),
|
||||
("idx", "idx"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No draw units.",
|
||||
}
|
||||
|
||||
def __init__(self, unit: ValueInput):
|
||||
super().__init__(Value.normalize(unit, "lv_draw_unit_t"))
|
||||
|
||||
@@ -29,17 +36,16 @@ class LVDrawUnit(Value):
|
||||
yield node
|
||||
node = node.next
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"name": self.name,
|
||||
"idx": self.idx,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(units):
|
||||
"""Print draw units as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = ["#", "name", "idx"]
|
||||
table.align = "l"
|
||||
|
||||
for i, unit in enumerate(units):
|
||||
table.add_row([i, unit.name, unit.idx])
|
||||
|
||||
if not table.rows:
|
||||
print("No draw units.")
|
||||
else:
|
||||
print(table)
|
||||
def snapshots(units):
|
||||
return [unit.snapshot() for unit in units]
|
||||
|
||||
281
scripts/gdb/lvglgdb/lvgl/formatter.py
Normal file
281
scripts/gdb/lvglgdb/lvgl/formatter.py
Normal file
@@ -0,0 +1,281 @@
|
||||
"""Generic formatting helpers for snapshot data.
|
||||
|
||||
This module provides data-driven formatters that work with any snapshot dict.
|
||||
No module-specific logic lives here — all customization is done via field specs
|
||||
and format callbacks passed by callers.
|
||||
"""
|
||||
from typing import Callable, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from prettytable import PrettyTable
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Type aliases for field specs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# A field spec is a tuple describing one line of print_info output:
|
||||
# (label, key_or_fmt)
|
||||
#
|
||||
# - label: the left-hand label string (e.g. "var")
|
||||
# - key_or_fmt: either a dict key string, or a callable(d) -> str
|
||||
#
|
||||
# Special forms:
|
||||
# ("_title", callable(d) -> str) — title line (no indent)
|
||||
# ("_children", key, child_fields) — nested list of dicts
|
||||
# ("_skip_if", key, sentinel, (label, key_or_fmt))
|
||||
# — only print if d[key] != sentinel
|
||||
|
||||
FieldSpec = Union[
|
||||
Tuple[str, Union[str, Callable]],
|
||||
Tuple[str, str, Callable],
|
||||
Tuple[str, str, str, Tuple],
|
||||
Tuple[str, str, list],
|
||||
]
|
||||
|
||||
|
||||
def print_info(d, fields: Sequence = None, indent: int = 0) -> None:
|
||||
"""Print a snapshot dict using a declarative field spec list.
|
||||
|
||||
Fields resolution order:
|
||||
1. Explicit *fields* parameter (highest priority)
|
||||
2. d._display_spec["info"] (Snapshot self-describing)
|
||||
3. None → default rendering (iterate dict keys as "key = value")
|
||||
"""
|
||||
if fields is None:
|
||||
spec = getattr(d, "_display_spec", None)
|
||||
if spec:
|
||||
fields = spec["info"]
|
||||
|
||||
# Default rendering: iterate dict keys
|
||||
if fields is None:
|
||||
prefix = " " * indent
|
||||
items = d.items() if hasattr(d, "items") else vars(d).items()
|
||||
for key, value in items:
|
||||
print(f"{prefix} {key} = {value}")
|
||||
return
|
||||
|
||||
prefix = " " * indent
|
||||
for spec in fields:
|
||||
# Bare string shorthand: "key" → ("key", "key")
|
||||
if isinstance(spec, str):
|
||||
spec = (spec, spec)
|
||||
|
||||
tag = spec[0]
|
||||
|
||||
if tag == "_title":
|
||||
fmt_fn = spec[1]
|
||||
print(f"{prefix}{fmt_fn(d)}")
|
||||
|
||||
elif tag == "_children":
|
||||
key, child_fields = spec[1], spec[2]
|
||||
children = d.get(key, [])
|
||||
for child in children:
|
||||
print_info(child, fields=child_fields, indent=indent + 1)
|
||||
|
||||
elif tag == "_skip_if":
|
||||
key, sentinel, inner = spec[1], spec[2], spec[3]
|
||||
# Bare string shorthand for inner: "key" → ("key", "key")
|
||||
if isinstance(inner, str):
|
||||
inner = (inner, inner)
|
||||
if d.get(key) != sentinel:
|
||||
_print_field(prefix, inner, d)
|
||||
|
||||
else:
|
||||
_print_field(prefix, spec, d)
|
||||
|
||||
|
||||
def _print_field(prefix: str, spec: tuple, d) -> None:
|
||||
"""Print a single (label, key_or_fmt) field."""
|
||||
label, key_or_fmt = spec[0], spec[1]
|
||||
if callable(key_or_fmt):
|
||||
value = key_or_fmt(d)
|
||||
else:
|
||||
value = d.get(key_or_fmt, "")
|
||||
print(f"{prefix} {label:14s} = {value}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Table helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def print_table(
|
||||
entries: List,
|
||||
columns: List[str],
|
||||
row_fn: Callable,
|
||||
empty_msg: str,
|
||||
align: str = "l",
|
||||
numbered: bool = True,
|
||||
col_align: dict = None,
|
||||
) -> None:
|
||||
"""Generic helper for printing a PrettyTable from snapshot data.
|
||||
|
||||
Args:
|
||||
entries: list of dict-like objects (Snapshot or plain dict).
|
||||
columns: column header names (excluding '#' if numbered=True).
|
||||
row_fn: callable(index, entry) -> list of cell values.
|
||||
empty_msg: message to print when entries is empty.
|
||||
align: default column alignment.
|
||||
numbered: if True, prepend a '#' column with row index.
|
||||
col_align: optional per-column alignment overrides, e.g. {"src": "l"}.
|
||||
"""
|
||||
table = PrettyTable()
|
||||
table.field_names = (["#"] + columns) if numbered else columns
|
||||
table.align = align
|
||||
if col_align:
|
||||
for col, a in col_align.items():
|
||||
if col in table.field_names:
|
||||
table.align[col] = a
|
||||
|
||||
for i, d in enumerate(entries):
|
||||
row = row_fn(i, d)
|
||||
if numbered:
|
||||
row = [i] + row
|
||||
table.add_row(row)
|
||||
|
||||
if not table.rows:
|
||||
print(empty_msg)
|
||||
else:
|
||||
print(table)
|
||||
|
||||
def resolve_table_columns(spec: dict) -> tuple:
|
||||
"""Resolve Display_Spec into (column_headers, auto_row_fn).
|
||||
|
||||
Merges the ``info`` field list with ``table`` overrides to produce a flat
|
||||
list of column headers and a row-extraction function with ``(i, d)``
|
||||
signature suitable for :func:`print_table`.
|
||||
|
||||
Algorithm:
|
||||
1. Walk ``spec["info"]``, skip ``_title`` / ``_children``, normalise
|
||||
bare strings, and collect ``(label, key_or_fmt, skip_info)`` triples.
|
||||
2. Walk ``spec["table"]``, build an override map (keyed by label) and
|
||||
an append list for labels not present in the base columns.
|
||||
3. Apply overrides in-place, then append new columns.
|
||||
4. Return ``(headers, auto_row_fn)``.
|
||||
"""
|
||||
# -- 1. base columns from info ----------------------------------------
|
||||
base_columns = []
|
||||
base_labels = set()
|
||||
for entry in spec["info"]:
|
||||
if isinstance(entry, str):
|
||||
entry = (entry, entry)
|
||||
|
||||
tag = entry[0]
|
||||
if tag in ("_title", "_children"):
|
||||
continue
|
||||
|
||||
if tag == "_skip_if":
|
||||
skip_key, sentinel, inner = entry[1], entry[2], entry[3]
|
||||
if isinstance(inner, str):
|
||||
inner = (inner, inner)
|
||||
label, key_or_fmt = inner[0], inner[1]
|
||||
base_columns.append((label, key_or_fmt, (skip_key, sentinel)))
|
||||
else:
|
||||
label, key_or_fmt = entry[0], entry[1]
|
||||
base_columns.append((label, key_or_fmt, None))
|
||||
|
||||
base_labels.add(base_columns[-1][0])
|
||||
|
||||
# -- 2. table overrides and appends -----------------------------------
|
||||
override_map = {}
|
||||
append_list = []
|
||||
for entry in spec.get("table", []):
|
||||
if isinstance(entry, str):
|
||||
entry = (entry, entry)
|
||||
|
||||
tag = entry[0]
|
||||
if tag == "_skip_if":
|
||||
skip_key, sentinel, inner = entry[1], entry[2], entry[3]
|
||||
if isinstance(inner, str):
|
||||
inner = (inner, inner)
|
||||
label, key_or_fmt = inner[0], inner[1]
|
||||
item = (label, key_or_fmt, (skip_key, sentinel))
|
||||
else:
|
||||
label, key_or_fmt = entry[0], entry[1]
|
||||
item = (label, key_or_fmt, None)
|
||||
|
||||
if label in base_labels:
|
||||
override_map[label] = item
|
||||
else:
|
||||
append_list.append(item)
|
||||
|
||||
# -- 3. merge ---------------------------------------------------------
|
||||
resolved = []
|
||||
for label, key_or_fmt, skip in base_columns:
|
||||
if label in override_map:
|
||||
resolved.append(override_map[label])
|
||||
else:
|
||||
resolved.append((label, key_or_fmt, skip))
|
||||
resolved.extend(append_list)
|
||||
|
||||
# -- 4. build outputs -------------------------------------------------
|
||||
headers = [label for label, _, _ in resolved]
|
||||
|
||||
def auto_row_fn(i, d):
|
||||
row = []
|
||||
for _label, key_or_fmt, skip in resolved:
|
||||
if skip is not None:
|
||||
sk, sentinel = skip
|
||||
if d.get(sk) == sentinel:
|
||||
row.append("")
|
||||
continue
|
||||
if callable(key_or_fmt):
|
||||
row.append(key_or_fmt(d))
|
||||
else:
|
||||
row.append(d.get(key_or_fmt, ""))
|
||||
return row
|
||||
|
||||
return (headers, auto_row_fn)
|
||||
|
||||
def print_spec_table(
|
||||
entries: list,
|
||||
spec: dict = None,
|
||||
align: str = "l",
|
||||
numbered: bool = True,
|
||||
col_align: dict = None,
|
||||
extra_columns: list = None,
|
||||
extra_row_fn: Callable = None,
|
||||
) -> None:
|
||||
"""Render a PrettyTable from a Display_Spec.
|
||||
|
||||
Resolves columns from *spec* via :func:`resolve_table_columns`, optionally
|
||||
prepends *extra_columns* (used by DumpCache for dynamic fields), then
|
||||
delegates to :func:`print_table`.
|
||||
|
||||
If *spec* is not provided, it is read from the first entry's
|
||||
``_display_spec`` attribute.
|
||||
"""
|
||||
if spec is None and entries:
|
||||
spec = getattr(entries[0], "_display_spec", None)
|
||||
if spec is None:
|
||||
print("")
|
||||
return
|
||||
columns, auto_row_fn = resolve_table_columns(spec)
|
||||
|
||||
if extra_columns and extra_row_fn:
|
||||
full_columns = extra_columns + columns
|
||||
|
||||
def combined_row_fn(i, d):
|
||||
return extra_row_fn(d) + auto_row_fn(i, d)
|
||||
|
||||
row_fn = combined_row_fn
|
||||
else:
|
||||
full_columns = columns
|
||||
row_fn = auto_row_fn
|
||||
|
||||
print_table(
|
||||
entries,
|
||||
full_columns,
|
||||
row_fn,
|
||||
spec["empty_msg"],
|
||||
align=align,
|
||||
numbered=numbered,
|
||||
col_align=col_align,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,15 +2,14 @@ from .lv_ll import LVList
|
||||
from .lv_style import (
|
||||
LVStyle,
|
||||
StyleEntry,
|
||||
dump_style_info,
|
||||
style_prop_name,
|
||||
decode_selector,
|
||||
format_style_value,
|
||||
)
|
||||
from .lv_rb import LVRedBlackTree, dump_rb_info
|
||||
from .lv_cache import LVCache, dump_cache_info
|
||||
from .lv_cache_entry import LVCacheEntry, dump_cache_entry_info
|
||||
from .lv_cache_lru_rb import LVCacheLRURB, dump_lru_rb_cache_info, LVCacheLRURBIterator
|
||||
from .lv_rb import LVRedBlackTree
|
||||
from .lv_cache import LVCache
|
||||
from .lv_cache_entry import LVCacheEntry
|
||||
from .lv_cache_lru_rb import LVCacheLRURB, LVCacheLRURBIterator
|
||||
from .lv_cache_iter_base import LVCacheIteratorBase
|
||||
from .lv_cache_iter_factory import create_cache_iterator
|
||||
from .lv_image_cache import LVImageCache
|
||||
@@ -33,18 +32,13 @@ __all__ = [
|
||||
"LVList",
|
||||
"LVStyle",
|
||||
"StyleEntry",
|
||||
"dump_style_info",
|
||||
"style_prop_name",
|
||||
"decode_selector",
|
||||
"format_style_value",
|
||||
"LVRedBlackTree",
|
||||
"dump_rb_info",
|
||||
"LVCache",
|
||||
"dump_cache_info",
|
||||
"LVCacheEntry",
|
||||
"dump_cache_entry_info",
|
||||
"LVCacheLRURB",
|
||||
"dump_lru_rb_cache_info",
|
||||
"LVCacheIteratorBase",
|
||||
"LVCacheLRURBIterator",
|
||||
"LVImageCache",
|
||||
|
||||
@@ -1,19 +1,52 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
|
||||
|
||||
def _fmt_cb(cb: Value) -> str:
|
||||
"""Format a callback pointer as symbol or hex."""
|
||||
addr = int(cb)
|
||||
if not addr:
|
||||
return "-"
|
||||
return cb.format_string(symbols=True, address=True)
|
||||
|
||||
|
||||
class LVAnim(Value):
|
||||
"""LVGL animation wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: f"Animation @{d['addr']}"),
|
||||
"var",
|
||||
"exec_cb",
|
||||
"path_cb",
|
||||
"start_cb",
|
||||
"completed_cb",
|
||||
"deleted_cb",
|
||||
"user_data",
|
||||
("value", lambda d: (
|
||||
f"{d['start_value']} -> {d['current_value']}"
|
||||
f" -> {d['end_value']}"
|
||||
)),
|
||||
("duration", lambda d: (
|
||||
f"{d['duration']}ms act_time={d['act_time']}ms"
|
||||
)),
|
||||
("repeat", lambda d: (
|
||||
f"{'inf' if d['repeat_cnt'] == 0xFFFFFFFF else d['repeat_cnt']}"
|
||||
f" repeat_delay={d['repeat_delay']}ms"
|
||||
)),
|
||||
("reverse", lambda d: (
|
||||
f"dur={d['reverse_duration']}ms"
|
||||
f" delay={d['reverse_delay']}ms"
|
||||
)),
|
||||
("status", lambda d: (
|
||||
f"{d['status']} early_apply={d['early_apply']}"
|
||||
)),
|
||||
],
|
||||
"table": [
|
||||
("value(start/cur/end)", lambda d: (
|
||||
f"{d['start_value']}/{d['current_value']}/{d['end_value']}"
|
||||
)),
|
||||
("duration", lambda d: f"{d['duration']}ms"),
|
||||
("act_time", lambda d: f"{d['act_time']}ms"),
|
||||
("repeat", lambda d: (
|
||||
"inf" if d["repeat_cnt"] == 0xFFFFFFFF
|
||||
else str(d["repeat_cnt"])
|
||||
)),
|
||||
],
|
||||
"empty_msg": "No active animations.",
|
||||
}
|
||||
|
||||
def __init__(self, anim: ValueInput):
|
||||
super().__init__(Value.normalize(anim, "lv_anim_t"))
|
||||
|
||||
@@ -101,61 +134,34 @@ class LVAnim(Value):
|
||||
return "reverse"
|
||||
return "running"
|
||||
|
||||
def print_info(self):
|
||||
"""Print detailed info for a single animation."""
|
||||
print(f"Animation @{hex(int(self))}")
|
||||
print(f" var = {self.var}")
|
||||
print(f" exec_cb = {_fmt_cb(self.exec_cb)}")
|
||||
print(f" path_cb = {_fmt_cb(self.path_cb)}")
|
||||
print(f" start_cb = {_fmt_cb(self.start_cb)}")
|
||||
print(f" completed_cb = {_fmt_cb(self.completed_cb)}")
|
||||
print(f" deleted_cb = {_fmt_cb(self.deleted_cb)}")
|
||||
print(f" user_data = {self.user_data}")
|
||||
print(
|
||||
f" value = {self.start_value} -> {self.current_value} -> {self.end_value}"
|
||||
)
|
||||
print(f" duration = {self.duration}ms act_time={self.act_time}ms")
|
||||
repeat = "inf" if self.repeat_cnt == 0xFFFFFFFF else str(self.repeat_cnt)
|
||||
print(f" repeat = {repeat} repeat_delay={self.repeat_delay}ms")
|
||||
print(
|
||||
f" reverse = dur={self.reverse_duration}ms delay={self.reverse_delay}ms"
|
||||
)
|
||||
print(f" status = {self._status_str()} early_apply={self.early_apply}")
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb, ptr_or_none
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"var": str(self.var),
|
||||
"var_addr": ptr_or_none(self.var),
|
||||
"exec_cb": fmt_cb(self.exec_cb),
|
||||
"path_cb": fmt_cb(self.path_cb),
|
||||
"start_cb": fmt_cb(self.start_cb),
|
||||
"completed_cb": fmt_cb(self.completed_cb),
|
||||
"deleted_cb": fmt_cb(self.deleted_cb),
|
||||
"user_data": str(self.user_data),
|
||||
"start_value": self.start_value,
|
||||
"current_value": self.current_value,
|
||||
"end_value": self.end_value,
|
||||
"duration": self.duration,
|
||||
"act_time": self.act_time,
|
||||
"repeat_cnt": self.repeat_cnt,
|
||||
"repeat_delay": self.repeat_delay,
|
||||
"reverse_duration": self.reverse_duration,
|
||||
"reverse_delay": self.reverse_delay,
|
||||
"status": self._status_str(),
|
||||
"early_apply": self.early_apply,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(anims):
|
||||
"""Print animations as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = [
|
||||
"#",
|
||||
"var",
|
||||
"exec_cb",
|
||||
"value(start/cur/end)",
|
||||
"duration",
|
||||
"act_time",
|
||||
"repeat",
|
||||
"status",
|
||||
]
|
||||
table.align = "l"
|
||||
|
||||
for i, anim in enumerate(anims):
|
||||
cb_str = _fmt_cb(anim.exec_cb)
|
||||
repeat = "inf" if anim.repeat_cnt == 0xFFFFFFFF else str(anim.repeat_cnt)
|
||||
value_str = f"{anim.start_value}/{anim.current_value}/{anim.end_value}"
|
||||
table.add_row(
|
||||
[
|
||||
i,
|
||||
anim.var,
|
||||
cb_str,
|
||||
value_str,
|
||||
f"{anim.duration}ms",
|
||||
f"{anim.act_time}ms",
|
||||
repeat,
|
||||
anim._status_str(),
|
||||
]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No active animations.")
|
||||
else:
|
||||
print(table)
|
||||
def snapshots(anims):
|
||||
return [anim.snapshot() for anim in anims]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Union, List, Optional, Dict
|
||||
from typing import Union
|
||||
import gdb
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
@@ -8,6 +8,21 @@ from .lv_cache_iter_factory import create_cache_iterator
|
||||
class LVCache(Value):
|
||||
"""LVGL cache wrapper - focuses on cache-level operations"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: "Cache Info:"),
|
||||
("Name", "name"),
|
||||
("Node Size", "node_size"),
|
||||
("Max Size", "max_size"),
|
||||
("Current Size", "current_size"),
|
||||
("Free Size", lambda d: d["max_size"] - d["current_size"]),
|
||||
("Enabled", "enabled"),
|
||||
("_skip_if", "iterator_type", None, ("Iterator Type", "iterator_type")),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "",
|
||||
}
|
||||
|
||||
def __init__(self, cache: ValueInput, datatype: Union[gdb.Type, str]):
|
||||
super().__init__(Value.normalize(cache, "lv_cache_t"))
|
||||
self.datatype = (
|
||||
@@ -16,24 +31,27 @@ class LVCache(Value):
|
||||
else datatype
|
||||
)
|
||||
|
||||
def print_info(self):
|
||||
"""Dump cache information"""
|
||||
print(f"Cache Info:")
|
||||
print(f" Name: {self.name.as_string()}")
|
||||
print(f" Node Size: {int(self.node_size)}")
|
||||
print(f" Max Size: {int(self.max_size)}")
|
||||
print(f" Current Size: {int(self.size)}")
|
||||
print(f" Free Size: {int(self.max_size) - int(self.size)}")
|
||||
print(f" Enabled: {bool(int(self.max_size) > 0)}")
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
# Try to identify cache type
|
||||
iter_type = None
|
||||
try:
|
||||
iterator = create_cache_iterator(self)
|
||||
print(f" Iterator Type: {iterator.__class__.__name__}")
|
||||
iterator.cache.print_info()
|
||||
except gdb.error:
|
||||
iter_type = iterator.__class__.__name__
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"name": self.name.as_string(),
|
||||
"node_size": int(self.node_size),
|
||||
"max_size": int(self.max_size),
|
||||
"current_size": int(self.size),
|
||||
"enabled": bool(int(self.max_size) > 0),
|
||||
"iterator_type": iter_type,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
def is_enabled(self):
|
||||
"""Check if cache is enabled"""
|
||||
return int(self.max_size) > 0
|
||||
@@ -55,31 +73,9 @@ class LVCache(Value):
|
||||
entries.append(entry)
|
||||
return entries
|
||||
|
||||
def print_entries(self, max_entries=10):
|
||||
"""Print cache entries in readable format"""
|
||||
cache_entries = self.items()
|
||||
cache_entries_cnt = len(cache_entries)
|
||||
print(f"Cache Entries ({cache_entries_cnt} total):")
|
||||
|
||||
count = 0
|
||||
for i, entry in enumerate(cache_entries):
|
||||
if count >= max_entries:
|
||||
print(
|
||||
f" ... showing first {max_entries} of {cache_entries_cnt} entries"
|
||||
)
|
||||
break
|
||||
|
||||
print(f" [{i}] {entry}")
|
||||
count += 1
|
||||
|
||||
if count == 0:
|
||||
print(" (empty)")
|
||||
elif count < int(cache_entries_cnt):
|
||||
print(f" ... {cache_entries_cnt - count} more entries not shown")
|
||||
|
||||
def sanity_check(self, entry_checker=None):
|
||||
"""Run sanity check and print results as a table"""
|
||||
from prettytable import PrettyTable
|
||||
from lvglgdb.lvgl.formatter import print_table
|
||||
|
||||
iterator = iter(self)
|
||||
if iterator is None:
|
||||
@@ -87,21 +83,14 @@ class LVCache(Value):
|
||||
else:
|
||||
errors = iterator.sanity_check(entry_checker)
|
||||
|
||||
table = PrettyTable()
|
||||
table.field_names = ["#", "status", "detail"]
|
||||
table.align["detail"] = "l"
|
||||
|
||||
if errors:
|
||||
for i, err in enumerate(errors):
|
||||
table.add_row([i, "FAIL", err])
|
||||
rows = [{"status": "FAIL", "detail": e} for e in errors]
|
||||
else:
|
||||
table.add_row([0, "PASS", f"all {len(iterator)} entries OK"])
|
||||
rows = [{"status": "PASS", "detail": f"all {len(iterator)} entries OK"}]
|
||||
|
||||
print(table)
|
||||
print_table(rows, ["status", "detail"],
|
||||
lambda i, d: [d["status"], d["detail"]], "",
|
||||
col_align={"detail": "l"})
|
||||
return errors
|
||||
|
||||
|
||||
def dump_cache_info(cache: ValueInput, datatype: Union[gdb.Type, str]):
|
||||
"""Dump cache information"""
|
||||
cache_obj = LVCache(cache, datatype)
|
||||
cache_obj.print_info()
|
||||
|
||||
@@ -27,22 +27,6 @@ class LVCacheEntry(Value):
|
||||
entry_ptr = int(data_ptr) + data_ptr.type.target().sizeof
|
||||
return cls(entry_ptr, datatype)
|
||||
|
||||
def print_info(self):
|
||||
"""Dump cache entry information"""
|
||||
print(f"Cache Entry Info:")
|
||||
print(f" Reference Count: {int(self.ref_cnt)}")
|
||||
print(f" Node Size: {int(self.node_size)}")
|
||||
print(f" Flags: {int(self.flags)}")
|
||||
print(f" Invalid: {self.is_invalid()}")
|
||||
print(f" Disable Delete: {self.is_disabled_delete()}")
|
||||
|
||||
# Try to get cache info if available
|
||||
try:
|
||||
cache = self.cache
|
||||
if cache:
|
||||
print(f" Cache: {cache}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_data(self):
|
||||
"""Get entry data pointer"""
|
||||
@@ -80,7 +64,3 @@ class LVCacheEntry(Value):
|
||||
return super().__str__()
|
||||
|
||||
|
||||
def dump_cache_entry_info(entry: ValueInput, datatype: Union[gdb.Type, str]):
|
||||
"""Dump cache entry information"""
|
||||
entry_obj = LVCacheEntry(entry, datatype)
|
||||
entry_obj.print_info()
|
||||
|
||||
@@ -144,20 +144,14 @@ class LVCacheLRURB(LVCache):
|
||||
super().__init__(cache, datatype)
|
||||
self.cache_base = Value(self)
|
||||
|
||||
def print_info(self):
|
||||
"""Dump LRU RB cache information"""
|
||||
print(f"LRU RB Cache Info:")
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
# Try to get cache class info
|
||||
try:
|
||||
clz = self.clz
|
||||
if clz:
|
||||
print(f" Cache Class: {clz}")
|
||||
# Check if it's LRU RB based
|
||||
if "lru_rb" in str(clz).lower():
|
||||
print(f" Type: LRU with Red-Black Tree")
|
||||
except:
|
||||
pass
|
||||
base = super().snapshot()
|
||||
d = base.as_dict()
|
||||
d["type"] = "lru_rb"
|
||||
return Snapshot(d, source=self,
|
||||
display_spec=getattr(base, "_display_spec", None))
|
||||
|
||||
def is_count_based(self):
|
||||
"""Check if this is count-based LRU cache"""
|
||||
@@ -185,9 +179,3 @@ class LVCacheLRURB(LVCache):
|
||||
for entry in self:
|
||||
entries.append(entry)
|
||||
return entries
|
||||
|
||||
|
||||
def dump_lru_rb_cache_info(cache: ValueInput):
|
||||
"""Dump LRU RB cache information"""
|
||||
cache_obj = LVCacheLRURB(cache)
|
||||
cache_obj.print_info()
|
||||
|
||||
@@ -49,11 +49,6 @@ class LVEvent(Value):
|
||||
def stop_processing(self) -> bool:
|
||||
return bool(int(self.super_value("stop_processing")))
|
||||
|
||||
def print_info(self):
|
||||
print(f"Event: code={self.code_name}({self.code})")
|
||||
print(f" current_target={self.current_target}")
|
||||
print(f" original_target={self.original_target}")
|
||||
print(f" deleted={self.deleted} stop_processing={self.stop_processing}")
|
||||
|
||||
|
||||
class LVEventDsc(Value):
|
||||
@@ -91,20 +86,41 @@ class LVEventDsc(Value):
|
||||
def is_marked_deleting(self) -> bool:
|
||||
return bool(self.filter & 0x10000)
|
||||
|
||||
def print_info(self):
|
||||
cb_str = self.cb.format_string(symbols=True, address=True)
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb
|
||||
|
||||
flags = []
|
||||
if self.is_preprocess:
|
||||
flags.append("PRE")
|
||||
if self.is_marked_deleting:
|
||||
flags.append("DEL")
|
||||
flag_str = f" [{','.join(flags)}]" if flags else ""
|
||||
print(f" cb={cb_str} filter={self.filter_name}({self.filter_code}){flag_str}")
|
||||
d = {
|
||||
"cb": fmt_cb(self.cb),
|
||||
"filter": self.filter_code,
|
||||
"filter_name": self.filter_name,
|
||||
"is_preprocess": self.is_preprocess,
|
||||
"is_marked_deleting": self.is_marked_deleting,
|
||||
"flags_str": ",".join(flags) or "-",
|
||||
"user_data": str(self.user_data),
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=LVEventList._DISPLAY_SPEC)
|
||||
|
||||
|
||||
class LVEventList(Value):
|
||||
"""LVGL event list wrapper (lv_event_list_t contains lv_array_t)"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("callback", "cb"),
|
||||
("filter", "filter_name"),
|
||||
("flags", lambda d: d.get("flags_str", "-")),
|
||||
("user_data", "user_data"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No event descriptors.",
|
||||
}
|
||||
|
||||
def __init__(self, event_list: ValueInput):
|
||||
super().__init__(Value.normalize(event_list, "lv_event_list_t"))
|
||||
|
||||
@@ -131,26 +147,5 @@ class LVEventList(Value):
|
||||
return len(self.array)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(event_dscs):
|
||||
"""Print event descriptors as a PrettyTable."""
|
||||
from prettytable import PrettyTable
|
||||
|
||||
table = PrettyTable()
|
||||
table.field_names = ["#", "callback", "filter", "flags", "user_data"]
|
||||
table.align = "l"
|
||||
|
||||
for i, dsc in enumerate(event_dscs):
|
||||
cb_str = dsc.cb.format_string(symbols=True, address=True)
|
||||
flags = []
|
||||
if dsc.is_preprocess:
|
||||
flags.append("PRE")
|
||||
if dsc.is_marked_deleting:
|
||||
flags.append("DEL")
|
||||
table.add_row(
|
||||
[i, cb_str, dsc.filter_name, ",".join(flags) or "-", dsc.user_data]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No event descriptors.")
|
||||
else:
|
||||
print(table)
|
||||
def snapshots(event_dscs):
|
||||
return [dsc.snapshot() for dsc in event_dscs]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from .lv_utils import resolve_source_name, build_global_field_map
|
||||
|
||||
@@ -7,6 +5,20 @@ from .lv_utils import resolve_source_name, build_global_field_map
|
||||
class LVFsDrv(Value):
|
||||
"""LVGL filesystem driver wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("letter", lambda d: f"{d['letter']}:"),
|
||||
("type", "driver_name"),
|
||||
("cache_size", "cache_size"),
|
||||
("open_cb", "open_cb"),
|
||||
("read_cb", "read_cb"),
|
||||
("write_cb", "write_cb"),
|
||||
("close_cb", "close_cb"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No registered filesystem drivers.",
|
||||
}
|
||||
|
||||
def __init__(self, drv: ValueInput):
|
||||
super().__init__(Value.normalize(drv, "lv_fs_drv_t"))
|
||||
|
||||
@@ -80,45 +92,22 @@ class LVFsDrv(Value):
|
||||
def user_data(self) -> Value:
|
||||
return self.super_value("user_data")
|
||||
|
||||
@staticmethod
|
||||
def _fmt_cb(cb: Value) -> str:
|
||||
addr = int(cb)
|
||||
if not addr:
|
||||
return "-"
|
||||
return cb.format_string(symbols=True).replace("\x00", "")
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"letter": self.letter,
|
||||
"driver_name": self.driver_name,
|
||||
"cache_size": self.cache_size,
|
||||
"open_cb": fmt_cb(self.open_cb),
|
||||
"read_cb": fmt_cb(self.read_cb),
|
||||
"write_cb": fmt_cb(self.write_cb),
|
||||
"close_cb": fmt_cb(self.close_cb),
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(drivers):
|
||||
"""Print filesystem drivers as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = [
|
||||
"#",
|
||||
"letter",
|
||||
"type",
|
||||
"cache_size",
|
||||
"open_cb",
|
||||
"read_cb",
|
||||
"write_cb",
|
||||
"close_cb",
|
||||
]
|
||||
table.align = "l"
|
||||
|
||||
fmt = LVFsDrv._fmt_cb
|
||||
for i, drv in enumerate(drivers):
|
||||
table.add_row(
|
||||
[
|
||||
i,
|
||||
f"{drv.letter}:",
|
||||
drv.driver_name,
|
||||
drv.cache_size,
|
||||
fmt(drv.open_cb),
|
||||
fmt(drv.read_cb),
|
||||
fmt(drv.write_cb),
|
||||
fmt(drv.close_cb),
|
||||
]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No registered filesystem drivers.")
|
||||
else:
|
||||
print(str(table).replace("\x00", ""))
|
||||
def snapshots(drivers):
|
||||
return [drv.snapshot() for drv in drivers]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import gdb
|
||||
from prettytable import PrettyTable
|
||||
from lvglgdb.value import Value
|
||||
from .lv_cache import LVCache
|
||||
from .lv_cache_entry import LVCacheEntry
|
||||
@@ -43,31 +42,34 @@ class LVImageCacheData(Value):
|
||||
|
||||
|
||||
class LVImageCache(object):
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("size", "size"),
|
||||
("data_size", lambda d: str(d["data_size"])),
|
||||
("cf", lambda d: str(d["cf"])),
|
||||
("rc", lambda d: str(d["ref_count"])),
|
||||
("type", "src_type"),
|
||||
("decoder", "decoder_name"),
|
||||
("decoded", "decoded_addr"),
|
||||
("src", "src"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "",
|
||||
}
|
||||
|
||||
def __init__(self, cache: Value):
|
||||
self._cache = LVCache(cache, "lv_image_cache_data_t")
|
||||
|
||||
def print_info(self):
|
||||
self._cache.print_info()
|
||||
def snapshot(self):
|
||||
return self._cache.snapshot()
|
||||
|
||||
def snapshots(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
def print_entries(self):
|
||||
"""Print image cache entries using prettytable format"""
|
||||
iterator = iter(self._cache)
|
||||
extra_fields = iterator.extra_fields
|
||||
|
||||
table = PrettyTable()
|
||||
fields = (
|
||||
["entry"]
|
||||
+ extra_fields
|
||||
+ ["size", "data_size", "cf", "rc", "type", "decoder", "decoded", "src"]
|
||||
)
|
||||
table.field_names = fields
|
||||
table.align = "r"
|
||||
table.align["src"] = "l"
|
||||
table.align["type"] = "c"
|
||||
result = []
|
||||
|
||||
for entry in iterator:
|
||||
entry: LVCacheEntry
|
||||
|
||||
data_ptr = entry.get_data()
|
||||
if not data_ptr:
|
||||
continue
|
||||
@@ -113,23 +115,24 @@ class LVImageCache(object):
|
||||
except gdb.error as e:
|
||||
src_str = src_str or str(e)
|
||||
|
||||
row = (
|
||||
[f"{int(entry):#x}"]
|
||||
+ iterator.get_extra(entry)
|
||||
+ [
|
||||
size_str,
|
||||
f"{data_size}",
|
||||
f"{cf}",
|
||||
f"{ref_cnt}",
|
||||
type_str,
|
||||
decoder_name,
|
||||
f"{decoded_ptr:#x}",
|
||||
src_str,
|
||||
]
|
||||
)
|
||||
table.add_row(row)
|
||||
extras = dict(zip(iterator.extra_fields, iterator.get_extra(entry)))
|
||||
d = {
|
||||
"entry_addr": f"{int(entry):#x}",
|
||||
"extra_fields": extras,
|
||||
"size": size_str,
|
||||
"data_size": data_size,
|
||||
"cf": cf,
|
||||
"ref_count": ref_cnt,
|
||||
"src_type": type_str,
|
||||
"decoder_name": decoder_name,
|
||||
"decoded_addr": f"{decoded_ptr:#x}",
|
||||
"src": src_str,
|
||||
}
|
||||
result.append(Snapshot(d, source=entry,
|
||||
display_spec=self._DISPLAY_SPEC))
|
||||
|
||||
print(table)
|
||||
self._last_extra_fields = iterator.extra_fields
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _check_image_entry(entry):
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
|
||||
|
||||
class LVImageDecoder(Value):
|
||||
"""LVGL image decoder wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("name", "name"),
|
||||
("info_cb", "info_cb"),
|
||||
("open_cb", "open_cb"),
|
||||
("close_cb", "close_cb"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No registered image decoders.",
|
||||
}
|
||||
|
||||
def __init__(self, decoder: ValueInput):
|
||||
super().__init__(Value.normalize(decoder, "lv_image_decoder_t"))
|
||||
|
||||
@@ -38,25 +47,19 @@ class LVImageDecoder(Value):
|
||||
def user_data(self) -> Value:
|
||||
return self.super_value("user_data")
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"name": self.name,
|
||||
"info_cb": fmt_cb(self.info_cb),
|
||||
"open_cb": fmt_cb(self.open_cb),
|
||||
"close_cb": fmt_cb(self.close_cb),
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(decoders):
|
||||
"""Print image decoders as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = ["#", "name", "info_cb", "open_cb", "close_cb"]
|
||||
table.align = "l"
|
||||
|
||||
for i, dec in enumerate(decoders):
|
||||
table.add_row(
|
||||
[
|
||||
i,
|
||||
dec.name,
|
||||
dec.info_cb.format_string(symbols=True),
|
||||
dec.open_cb.format_string(symbols=True),
|
||||
dec.close_cb.format_string(symbols=True),
|
||||
]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No registered image decoders.")
|
||||
else:
|
||||
print(table)
|
||||
def snapshots(decoders):
|
||||
return [dec.snapshot() for dec in decoders]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import gdb
|
||||
from prettytable import PrettyTable
|
||||
from lvglgdb.value import Value
|
||||
from .lv_cache import LVCache
|
||||
from .lv_cache_entry import LVCacheEntry
|
||||
@@ -34,29 +33,32 @@ class LVImageHeaderCacheData(Value):
|
||||
|
||||
|
||||
class LVImageHeaderCache(object):
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("size", "size"),
|
||||
("cf", lambda d: str(d["cf"])),
|
||||
("rc", lambda d: str(d["ref_count"])),
|
||||
("type", "src_type"),
|
||||
("decoder", "decoder_name"),
|
||||
("src", "src"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "",
|
||||
}
|
||||
|
||||
def __init__(self, cache: Value):
|
||||
self._cache = LVCache(cache, "lv_image_header_cache_data_t")
|
||||
|
||||
def print_info(self):
|
||||
self._cache.print_info()
|
||||
def snapshot(self):
|
||||
return self._cache.snapshot()
|
||||
|
||||
def snapshots(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
def print_entries(self):
|
||||
"""Print image header cache entries using prettytable format"""
|
||||
iterator = iter(self._cache)
|
||||
extra_fields = iterator.extra_fields
|
||||
|
||||
table = PrettyTable()
|
||||
fields = (
|
||||
["entry"] + extra_fields + ["size", "cf", "rc", "type", "decoder", "src"]
|
||||
)
|
||||
table.field_names = fields
|
||||
table.align = "r"
|
||||
table.align["src"] = "l"
|
||||
table.align["type"] = "c"
|
||||
result = []
|
||||
|
||||
for entry in iterator:
|
||||
entry: LVCacheEntry
|
||||
|
||||
data_ptr = entry.get_data()
|
||||
if not data_ptr:
|
||||
continue
|
||||
@@ -97,21 +99,22 @@ class LVImageHeaderCache(object):
|
||||
except gdb.error as e:
|
||||
src_str = src_str or str(e)
|
||||
|
||||
row = (
|
||||
[f"{int(entry):#x}"]
|
||||
+ iterator.get_extra(entry)
|
||||
+ [
|
||||
size_str,
|
||||
f"{cf}",
|
||||
f"{ref_cnt}",
|
||||
type_str,
|
||||
decoder_name,
|
||||
src_str,
|
||||
]
|
||||
)
|
||||
table.add_row(row)
|
||||
extras = dict(zip(iterator.extra_fields, iterator.get_extra(entry)))
|
||||
d = {
|
||||
"entry_addr": f"{int(entry):#x}",
|
||||
"extra_fields": extras,
|
||||
"size": size_str,
|
||||
"cf": cf,
|
||||
"ref_count": ref_cnt,
|
||||
"src_type": type_str,
|
||||
"decoder_name": decoder_name,
|
||||
"src": src_str,
|
||||
}
|
||||
result.append(Snapshot(d, source=entry,
|
||||
display_spec=self._DISPLAY_SPEC))
|
||||
|
||||
print(table)
|
||||
self._last_extra_fields = iterator.extra_fields
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _check_header_entry(entry):
|
||||
|
||||
@@ -7,6 +7,18 @@ from lvglgdb.value import Value, ValueInput
|
||||
class LVList(Value):
|
||||
"""LVGL linked list iterator"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: "Linked List Info:"),
|
||||
("Address", "addr"),
|
||||
("Node Size", "n_size"),
|
||||
("Node Count", "node_count"),
|
||||
("_skip_if", "nodetype", None, ("Node Type", "nodetype")),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "",
|
||||
}
|
||||
|
||||
def __init__(self, ll: ValueInput, nodetype: Union[gdb.Type, str] = None):
|
||||
super().__init__(Value.normalize(ll, "lv_ll_t"))
|
||||
|
||||
@@ -49,3 +61,15 @@ class LVList(Value):
|
||||
len += 1
|
||||
node = self._next(node)
|
||||
return len
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"n_size": int(self.n_size),
|
||||
"node_count": self.len,
|
||||
"nodetype": str(self.nodetype) if self.nodetype else None,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
|
||||
@@ -7,6 +7,18 @@ from lvglgdb.value import Value, ValueInput
|
||||
class LVRedBlackTree(Value):
|
||||
"""LVGL red-black tree iterator"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("_title", lambda d: "Red-Black Tree Info:"),
|
||||
("Address", "addr"),
|
||||
("Node Size", "size"),
|
||||
("Node Count", "node_count"),
|
||||
("_skip_if", "datatype", None, ("Data Type", "datatype")),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "",
|
||||
}
|
||||
|
||||
def __init__(self, rb: ValueInput, datatype: Union[gdb.Type, str] = None):
|
||||
super().__init__(Value.normalize(rb, "lv_rb_t"))
|
||||
self.lv_rb_node_t = gdb.lookup_type("lv_rb_node_t").pointer()
|
||||
@@ -67,55 +79,16 @@ class LVRedBlackTree(Value):
|
||||
return data.cast(self.datatype)
|
||||
return data
|
||||
|
||||
def format_data(self, data):
|
||||
"""Format data for display - simple GDB style"""
|
||||
if data is None:
|
||||
return "None"
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
try:
|
||||
ptr_addr = f"0x{int(data):x}"
|
||||
except:
|
||||
return str(data)
|
||||
|
||||
if self.datatype and data:
|
||||
try:
|
||||
struct_data = data.dereference()
|
||||
return f"{ptr_addr} -> {struct_data}"
|
||||
except:
|
||||
pass
|
||||
|
||||
return ptr_addr
|
||||
|
||||
def print_info(self):
|
||||
"""Dump basic tree information"""
|
||||
print(f"Red-Black Tree Info:")
|
||||
print(f" Size: {int(self.size)}")
|
||||
print(f" Node Count: {len(self)}")
|
||||
print(f" Root: {self.root}")
|
||||
if self.root:
|
||||
root_color = "Red" if int(self.root.color) == 0 else "Black"
|
||||
print(f" Root Color: {root_color}")
|
||||
if self.datatype:
|
||||
print(f" Data Type: {self.datatype}")
|
||||
|
||||
def print_tree(self, max_items=10):
|
||||
"""Print tree data in a readable format"""
|
||||
print(f"Red-Black Tree Contents ({len(self)} total items):")
|
||||
|
||||
count = 0
|
||||
for i, data in enumerate(self):
|
||||
if count >= max_items:
|
||||
print(f" ... showing first {max_items} of {len(self)} items")
|
||||
break
|
||||
|
||||
formatted = self.format_data(data)
|
||||
print(f" [{i}] {formatted}")
|
||||
count += 1
|
||||
|
||||
if count == 0:
|
||||
print(" (empty)")
|
||||
elif count < len(self):
|
||||
print(f" ... {len(self) - count} more items not shown")
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"size": int(self.size),
|
||||
"node_count": len(self),
|
||||
"datatype": str(self.datatype) if self.datatype else None,
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
|
||||
class LVRedBlackTreeIterator:
|
||||
@@ -162,7 +135,3 @@ class LVRedBlackTreeIterator:
|
||||
return f"LVRedBlackTreeIterator(current=0x{int(current):x})"
|
||||
|
||||
|
||||
def dump_rb_info(rb: ValueInput, datatype: Union[gdb.Type, str] = None):
|
||||
"""Dump red-black tree information"""
|
||||
tree = LVRedBlackTree(rb, datatype=datatype)
|
||||
tree.print_info()
|
||||
|
||||
@@ -2,7 +2,6 @@ from dataclasses import dataclass
|
||||
from typing import Iterator
|
||||
|
||||
import gdb
|
||||
from prettytable import PrettyTable
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from .lv_style_consts import (
|
||||
STYLE_PROP_NAMES,
|
||||
@@ -37,7 +36,7 @@ def decode_selector(selector: int) -> str:
|
||||
|
||||
|
||||
def format_style_value(prop_id: int, value: Value) -> str:
|
||||
"""Format a style value based on property type."""
|
||||
"""Format a style value based on property type (with ANSI color block)."""
|
||||
try:
|
||||
if prop_id in COLOR_PROPS:
|
||||
color = value.color
|
||||
@@ -55,6 +54,30 @@ def format_style_value(prop_id: int, value: Value) -> str:
|
||||
return str(value)
|
||||
|
||||
|
||||
def _style_value_data(prop_id: int, value: Value) -> dict:
|
||||
"""Extract style value as pure data dict (no ANSI codes).
|
||||
|
||||
Returns dict with 'value_str' and optional 'color_rgb'.
|
||||
"""
|
||||
try:
|
||||
if prop_id in COLOR_PROPS:
|
||||
color = value.color
|
||||
r = int(color.red) & 0xFF
|
||||
g = int(color.green) & 0xFF
|
||||
b = int(color.blue) & 0xFF
|
||||
return {
|
||||
"value_str": f"#{r:02x}{g:02x}{b:02x}",
|
||||
"color_rgb": {"r": r, "g": g, "b": b},
|
||||
}
|
||||
elif prop_id in POINTER_PROPS:
|
||||
ptr = int(value.ptr)
|
||||
return {"value_str": f"{ptr:#x}" if ptr else "NULL"}
|
||||
else:
|
||||
return {"value_str": str(int(value.num))}
|
||||
except gdb.error:
|
||||
return {"value_str": str(value)}
|
||||
|
||||
|
||||
@dataclass
|
||||
class StyleEntry:
|
||||
"""A single resolved style property."""
|
||||
@@ -103,21 +126,18 @@ class LVStyle(Value):
|
||||
continue
|
||||
yield StyleEntry(prop_id, values_ptr[j])
|
||||
|
||||
def print_entries(self):
|
||||
"""Print style properties as a table."""
|
||||
entries = list(self.__iter__())
|
||||
if not entries:
|
||||
print("Empty style.")
|
||||
return
|
||||
def snapshots(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
|
||||
table = PrettyTable()
|
||||
table.field_names = ["prop", "value"]
|
||||
table.align = "l"
|
||||
for e in entries:
|
||||
table.add_row([e.prop_name, e.value_str])
|
||||
print(table)
|
||||
result = []
|
||||
for entry in self.__iter__():
|
||||
vdata = _style_value_data(entry.prop_id, entry.value)
|
||||
d = {
|
||||
"prop_id": entry.prop_id,
|
||||
"prop_name": entry.prop_name,
|
||||
**vdata,
|
||||
}
|
||||
result.append(Snapshot(d, source=entry))
|
||||
return result
|
||||
|
||||
|
||||
def dump_style_info(entry: StyleEntry):
|
||||
"""Print a single style property."""
|
||||
print(f"{entry.prop_name}({entry.prop_id}) = {entry.value_str}")
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
|
||||
|
||||
class LVTimer(Value):
|
||||
"""LVGL timer wrapper"""
|
||||
|
||||
_DISPLAY_SPEC = {
|
||||
"info": [
|
||||
("callback", "timer_cb"),
|
||||
("period", "period"),
|
||||
("freq", "frequency"),
|
||||
("last_run", "last_run"),
|
||||
("repeat", lambda d: (
|
||||
"inf" if d["repeat_count"] == -1
|
||||
else str(d["repeat_count"])
|
||||
)),
|
||||
("paused", "paused"),
|
||||
],
|
||||
"table": [],
|
||||
"empty_msg": "No active timers.",
|
||||
}
|
||||
|
||||
def __init__(self, timer: ValueInput):
|
||||
super().__init__(Value.normalize(timer, "lv_timer_t"))
|
||||
|
||||
@@ -37,38 +51,24 @@ class LVTimer(Value):
|
||||
def auto_delete(self) -> bool:
|
||||
return bool(int(self.super_value("auto_delete")))
|
||||
|
||||
def snapshot(self):
|
||||
from lvglgdb.lvgl.snapshot import Snapshot
|
||||
from lvglgdb.lvgl.data_utils import fmt_cb, ptr_or_none
|
||||
|
||||
freq = f"{1000 / self.period:.1f}Hz" if self.period > 0 else "-"
|
||||
d = {
|
||||
"addr": hex(int(self)),
|
||||
"timer_cb": fmt_cb(self.timer_cb),
|
||||
"period": self.period,
|
||||
"frequency": freq,
|
||||
"last_run": self.last_run,
|
||||
"repeat_count": self.repeat_count,
|
||||
"paused": self.paused,
|
||||
"user_data": str(self.user_data),
|
||||
"user_data_addr": ptr_or_none(self.user_data),
|
||||
}
|
||||
return Snapshot(d, source=self, display_spec=self._DISPLAY_SPEC)
|
||||
|
||||
@staticmethod
|
||||
def print_entries(timers):
|
||||
"""Print timers as a PrettyTable."""
|
||||
table = PrettyTable()
|
||||
table.field_names = [
|
||||
"#",
|
||||
"callback",
|
||||
"period",
|
||||
"freq",
|
||||
"last_run",
|
||||
"repeat",
|
||||
"paused",
|
||||
]
|
||||
table.align = "l"
|
||||
|
||||
for i, timer in enumerate(timers):
|
||||
cb_str = timer.timer_cb.format_string(symbols=True, address=True)
|
||||
repeat = "inf" if timer.repeat_count == -1 else str(timer.repeat_count)
|
||||
freq = f"{1000 / timer.period:.1f}Hz" if timer.period > 0 else "-"
|
||||
table.add_row(
|
||||
[
|
||||
i,
|
||||
cb_str,
|
||||
timer.period,
|
||||
freq,
|
||||
timer.last_run,
|
||||
repeat,
|
||||
timer.paused,
|
||||
]
|
||||
)
|
||||
|
||||
if not table.rows:
|
||||
print("No active timers.")
|
||||
else:
|
||||
print(table)
|
||||
def snapshots(timers):
|
||||
return [timer.snapshot() for timer in timers]
|
||||
|
||||
54
scripts/gdb/lvglgdb/lvgl/snapshot.py
Normal file
54
scripts/gdb/lvglgdb/lvgl/snapshot.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from typing import Any, Dict, Iterator, Optional
|
||||
|
||||
|
||||
class Snapshot:
|
||||
"""Self-describing data snapshot from a wrapper instance.
|
||||
|
||||
Holds a pure Python dict (JSON-serializable), an optional reference
|
||||
to the original wrapper (_source), and an optional display spec that
|
||||
describes how to format the data for terminal output.
|
||||
"""
|
||||
|
||||
__slots__ = ("_data", "_source", "_display_spec")
|
||||
|
||||
def __init__(self, data: Dict[str, Any], source: Any = None,
|
||||
display_spec: Optional[Dict] = None):
|
||||
self._data = data
|
||||
self._source = source
|
||||
self._display_spec = display_spec
|
||||
|
||||
# --- dict-like read access ---
|
||||
|
||||
def __getitem__(self, key: str) -> Any:
|
||||
return self._data[key]
|
||||
|
||||
def __contains__(self, key: str) -> bool:
|
||||
return key in self._data
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._data)
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return iter(self._data)
|
||||
|
||||
def keys(self):
|
||||
return self._data.keys()
|
||||
|
||||
def values(self):
|
||||
return self._data.values()
|
||||
|
||||
def items(self):
|
||||
return self._data.items()
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
return self._data.get(key, default)
|
||||
|
||||
# --- serialization ---
|
||||
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
return dict(self._data)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
addr = self._data.get("addr", "?")
|
||||
src_type = type(self._source).__name__ if self._source else "None"
|
||||
return f"Snapshot(addr={addr}, source={src_type})"
|
||||
Reference in New Issue
Block a user