From 372b39a422dc1cc0ca30df41bda75ba4dace85e8 Mon Sep 17 00:00:00 2001 From: Benign X <1341398182@qq.com> Date: Fri, 6 Mar 2026 00:11:16 +0800 Subject: [PATCH] chore(gdb): add LVAnim wrapper with print_info, print_entries and dump anims command --- scripts/gdb/lvglgdb/__init__.py | 2 + scripts/gdb/lvglgdb/cmds/__init__.py | 9 +- scripts/gdb/lvglgdb/cmds/misc/__init__.py | 9 +- scripts/gdb/lvglgdb/cmds/misc/lv_anim.py | 31 ++++ scripts/gdb/lvglgdb/lvgl/__init__.py | 2 + scripts/gdb/lvglgdb/lvgl/core/lv_global.py | 6 + scripts/gdb/lvglgdb/lvgl/misc/__init__.py | 2 + scripts/gdb/lvglgdb/lvgl/misc/lv_anim.py | 157 +++++++++++++++++++++ 8 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 scripts/gdb/lvglgdb/cmds/misc/lv_anim.py create mode 100644 scripts/gdb/lvglgdb/lvgl/misc/lv_anim.py diff --git a/scripts/gdb/lvglgdb/__init__.py b/scripts/gdb/lvglgdb/__init__.py index bd6ca58776..a2fc81bbf6 100644 --- a/scripts/gdb/lvglgdb/__init__.py +++ b/scripts/gdb/lvglgdb/__init__.py @@ -34,6 +34,7 @@ from .lvgl import ( LVEventList, event_code_name, EVENT_CODE_NAMES, + LVAnim, ) from . import cmds as cmds @@ -71,4 +72,5 @@ __all__ = [ "LVEventList", "event_code_name", "EVENT_CODE_NAMES", + "LVAnim", ] diff --git a/scripts/gdb/lvglgdb/cmds/__init__.py b/scripts/gdb/lvglgdb/cmds/__init__.py index 0094588c90..9fbc41bbd5 100644 --- a/scripts/gdb/lvglgdb/cmds/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/__init__.py @@ -2,8 +2,14 @@ import gdb from .core import DumpObj from .display import DumpDisplayBuf -from .misc import InfoStyle, DumpCache, CheckPrefix, CheckCache from .draw import InfoDrawUnit, DumpDrawTask +from .misc import ( + InfoStyle, + DumpCache, + CheckPrefix, + CheckCache, + DumpAnim, +) from .debugger import Debugger from .drivers import Lvglobal @@ -25,6 +31,7 @@ DumpDisplayBuf() DumpCache() CheckPrefix() CheckCache() +DumpAnim() DumpDrawTask() # Infos diff --git a/scripts/gdb/lvglgdb/cmds/misc/__init__.py b/scripts/gdb/lvglgdb/cmds/misc/__init__.py index f9e29a2093..cf0c5441b3 100644 --- a/scripts/gdb/lvglgdb/cmds/misc/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/misc/__init__.py @@ -1,4 +1,11 @@ from .lv_style import InfoStyle from .lv_cache import DumpCache, CheckPrefix, CheckCache +from .lv_anim import DumpAnim -__all__ = ["InfoStyle", "DumpCache", "CheckPrefix", "CheckCache"] +__all__ = [ + "InfoStyle", + "DumpCache", + "CheckPrefix", + "CheckCache", + "DumpAnim", +] diff --git a/scripts/gdb/lvglgdb/cmds/misc/lv_anim.py b/scripts/gdb/lvglgdb/cmds/misc/lv_anim.py new file mode 100644 index 0000000000..c2d2108c15 --- /dev/null +++ b/scripts/gdb/lvglgdb/cmds/misc/lv_anim.py @@ -0,0 +1,31 @@ +import gdb + +from lvglgdb.lvgl import curr_inst +from lvglgdb.lvgl.misc.lv_anim import LVAnim + + +class DumpAnim(gdb.Command): + """dump all active animations + + Usage: + dump anims - list all animations in a table + dump anims --detail - print detailed info for each animation + """ + + def __init__(self): + super(DumpAnim, self).__init__( + "dump anims", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION + ) + + def invoke(self, args, from_tty): + anims = list(curr_inst().anims()) + if not anims: + print("No active animations.") + return + + if args.strip() == "--detail": + for anim in anims: + anim.print_info() + print() + else: + LVAnim.print_entries(anims) diff --git a/scripts/gdb/lvglgdb/lvgl/__init__.py b/scripts/gdb/lvglgdb/lvgl/__init__.py index 0706aafbe9..abd2eaf347 100644 --- a/scripts/gdb/lvglgdb/lvgl/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/__init__.py @@ -35,6 +35,7 @@ from .misc import ( LVEventList, event_code_name, EVENT_CODE_NAMES, + LVAnim, ) __all__ = [ @@ -76,4 +77,5 @@ __all__ = [ "LVEventList", "event_code_name", "EVENT_CODE_NAMES", + "LVAnim", ] diff --git a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py index 02b6f820c5..fe733fbe69 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py +++ b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py @@ -61,6 +61,12 @@ class LVGL: return LVImageCache(self.lv_global.img_cache) + def anims(self): + from ..misc.lv_anim import LVAnim + + for anim in LVList(self.lv_global.anim_state.anim_ll, "lv_anim_t"): + yield LVAnim(anim) + def image_header_cache(self): from ..misc.lv_image_header_cache import LVImageHeaderCache diff --git a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py index 663e18aa8c..209f7eb744 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py @@ -23,6 +23,7 @@ from .lv_event import ( event_code_name, EVENT_CODE_NAMES, ) +from .lv_anim import LVAnim __all__ = [ "LVList", @@ -51,4 +52,5 @@ __all__ = [ "LVEventList", "event_code_name", "EVENT_CODE_NAMES", + "LVAnim", ] diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_anim.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_anim.py new file mode 100644 index 0000000000..2aec10c3a8 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_anim.py @@ -0,0 +1,157 @@ +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""" + + def __init__(self, anim: ValueInput): + super().__init__(Value.normalize(anim, "lv_anim_t")) + + @property + def var(self) -> Value: + return self.super_value("var") + + @property + def exec_cb(self) -> Value: + return self.super_value("exec_cb") + + @property + def start_cb(self) -> Value: + return self.super_value("start_cb") + + @property + def completed_cb(self) -> Value: + return self.super_value("completed_cb") + + @property + def deleted_cb(self) -> Value: + return self.super_value("deleted_cb") + + @property + def path_cb(self) -> Value: + return self.super_value("path_cb") + + @property + def user_data(self) -> Value: + return self.super_value("user_data") + + @property + def start_value(self) -> int: + return int(self.super_value("start_value")) + + @property + def current_value(self) -> int: + return int(self.super_value("current_value")) + + @property + def end_value(self) -> int: + return int(self.super_value("end_value")) + + @property + def duration(self) -> int: + return int(self.super_value("duration")) + + @property + def act_time(self) -> int: + return int(self.super_value("act_time")) + + @property + def reverse_duration(self) -> int: + return int(self.super_value("reverse_duration")) + + @property + def reverse_delay(self) -> int: + return int(self.super_value("reverse_delay")) + + @property + def repeat_delay(self) -> int: + return int(self.super_value("repeat_delay")) + + @property + def repeat_cnt(self) -> int: + return int(self.super_value("repeat_cnt")) + + @property + def is_paused(self) -> bool: + return bool(int(self.super_value("is_paused"))) + + @property + def reverse_play_in_progress(self) -> bool: + return bool(int(self.super_value("reverse_play_in_progress"))) + + @property + def early_apply(self) -> bool: + return bool(int(self.super_value("early_apply"))) + + def _status_str(self) -> str: + """Short status string for table display.""" + if self.is_paused: + return "paused" + if self.reverse_play_in_progress: + return "reverse" + return "running" + + def print_info(self): + """Print detailed info for a single animation.""" + print(f"Animation @{hex(int(self.address))}") + 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 == 0xFFFF 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}") + + @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 == 0xFFFF 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)