1import argparse
2from typing import Iterator, Union, Optional
3
4import gdb
5
6from .value import Value
7
8gdb.execute("set pagination off")
9gdb.write("set pagination off\n")
10gdb.execute("set python print-stack full")
11gdb.write("set python print-stack full\n")
12
13g_lvgl_instance = None
14
15
16class LVList(Value):
17    """LVGL linked list iterator"""
18
19    def __init__(self, ll: Value, nodetype: Union[gdb.Type, str] = None):
20        if not ll:
21            raise ValueError("Invalid linked list")
22        super().__init__(ll)
23
24        self.nodetype = (
25            gdb.lookup_type(nodetype).pointer()
26            if isinstance(nodetype, str)
27            else nodetype
28        )
29        self.lv_ll_node_t = gdb.lookup_type("lv_ll_node_t").pointer()
30        self.current = self.head
31        self._next_offset = self.n_size + self.lv_ll_node_t.sizeof
32        self._prev_offset = self.n_size
33
34    def _next(self, node):
35        next_value = Value(int(node) + self._next_offset)
36        return next_value.cast(self.lv_ll_node_t, ptr=True).dereference()
37
38    def _prev(self, node):
39        prev_value = Value(int(node) + self._prev_offset)
40        return prev_value.cast(self.lv_ll_node_t, ptr=True).dereference()
41
42    def __iter__(self):
43        return self
44
45    def __next__(self):
46        if not self.current:
47            raise StopIteration
48
49        nodetype = self.nodetype if self.nodetype else self.lv_ll_node_t
50        node = self.current.cast(nodetype)
51
52        self.current = self._next(self.current)
53        return node
54
55    @property
56    def len(self):
57        len = 0
58        node = self.head
59        while node:
60            len += 1
61            node = self._next(node)
62        return len
63
64
65class LVObject(Value):
66    """LVGL object"""
67
68    def __init__(self, obj: Value):
69        super().__init__(obj.cast("lv_obj_t", ptr=True))
70
71    @property
72    def class_name(self):
73        name = self.class_p.name
74        return name.string() if name else "unknown"
75
76    @property
77    def x1(self):
78        return int(self.coords.x1)
79
80    @property
81    def y1(self):
82        return int(self.coords.y1)
83
84    @property
85    def x2(self):
86        return int(self.coords.x2)
87
88    @property
89    def y2(self):
90        return int(self.coords.y2)
91
92    @property
93    def child_count(self):
94        return self.spec_attr.child_cnt if self.spec_attr else 0
95
96    @property
97    def childs(self):
98        if not self.spec_attr:
99            return
100
101        for i in range(self.child_count):
102            child = self.spec_attr.children[i]
103            yield LVObject(child)
104
105    @property
106    def styles(self):
107        LV_STYLE_PROP_INV = 0
108        LV_STYLE_PROP_ANY = 0xFF
109        count = self.style_cnt
110        if count == 0:
111            return
112
113        styles = self.super_value("styles")
114        for i in range(count):
115            style = styles[i].style
116            prop_cnt = style.prop_cnt
117            values_and_props = style.values_and_props.cast("lv_style_const_prop_t", ptr=True)
118            for j in range(prop_cnt):
119                prop = values_and_props[j].prop
120                if prop == LV_STYLE_PROP_INV or prop == LV_STYLE_PROP_ANY:
121                    continue
122                yield values_and_props[j]
123
124    def get_child(self, index: int):
125        return (
126            self.spec_attr.children[index] if self.spec_attr else None
127        )
128
129
130class LVDisplay(Value):
131    """LVGL display"""
132
133    def __init__(self, disp: Value):
134        super().__init__(disp)
135
136    @property
137    def screens(self):
138        screens = self.super_value("screens")
139        for i in range(self.screen_cnt):
140            yield LVObject(screens[i])
141
142
143class LVGL:
144    """LVGL instance"""
145
146    def __init__(self, lv_global: Value):
147        self.lv_global = lv_global.cast("lv_global_t", ptr=True)
148
149    def displays(self) -> Iterator[LVDisplay]:
150        ll = self.lv_global.disp_ll
151        if not ll:
152            return
153
154        for disp in LVList(ll, "lv_display_t"):
155            yield LVDisplay(disp)
156
157    def screen_active(self):
158        disp = self.lv_global.disp_default
159        return disp.act_scr if disp else None
160
161    def draw_units(self):
162        unit = self.lv_global.draw_info.unit_head
163
164        # Iterate through all draw units
165        while unit:
166            yield unit
167            unit = unit.next
168
169
170def set_lvgl_instance(lv_global: Union[gdb.Value, Value, None]):
171    global g_lvgl_instance
172
173    if not lv_global:
174        try:
175            lv_global = Value(gdb.parse_and_eval("lv_global").address)
176        except gdb.error as e:
177            print(f"Failed to get lv_global: {e}")
178            return
179
180    if not isinstance(lv_global, Value):
181        lv_global = Value(lv_global)
182
183    inited = lv_global.inited
184    if not inited:
185        print("\x1b[31mlvgl is not initialized yet. Please call `set_lvgl_instance(None)` later.\x1b[0m")
186        return
187
188    g_lvgl_instance = LVGL(lv_global)
189
190
191def dump_obj_info(obj: LVObject):
192    clzname = obj.class_name
193    coords = f"{obj.x1},{obj.y1},{obj.x2},{obj.y2}"
194    print(f"{clzname}@{hex(obj)} {coords}")
195
196
197#  Dump lv_style_const_prop_t
198def dump_style_info(style: Value):
199    prop = int(style.prop)
200    value = style.value
201    print(f"{prop} = {value}")
202
203
204class DumpObj(gdb.Command):
205    """dump obj tree from specified obj"""
206
207    def __init__(self):
208        super(DumpObj, self).__init__(
209            "dump obj", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
210        )
211
212    def dump_obj(self, obj: LVObject, depth=0, limit=None):
213        if not obj:
214            return
215
216        # dump self
217        print("  " * depth, end="")
218        dump_obj_info(obj)
219
220        if limit is not None and depth >= limit:
221            return
222
223        # dump children
224        for child in obj.childs:
225            self.dump_obj(child, depth + 1, limit=limit)
226
227    def invoke(self, args, from_tty):
228        parser = argparse.ArgumentParser(description="Dump lvgl obj tree.")
229        parser.add_argument(
230            "-L",
231            "--level",
232            type=int,
233            default=None,
234            help="Limit the depth of the tree.",
235        )
236        parser.add_argument(
237            "root",
238            type=str,
239            nargs="?",
240            default=None,
241            help="Optional root obj to dump.",
242        )
243        try:
244            args = parser.parse_args(gdb.string_to_argv(args))
245        except SystemExit:
246            return
247
248        if args.root:
249            root = gdb.parse_and_eval(args.root)
250            root = LVObject(root)
251            self.dump_obj(root, limit=args.level)
252        else:
253            # dump all displays
254            depth = 0
255            for disp in g_lvgl_instance.displays():
256                print(f"Display {hex(disp)}")
257                for screen in disp.screens:
258                    print(f'{"  " * (depth + 1)}Screen@{hex(screen)}')
259                    self.dump_obj(screen, depth=depth + 1, limit=args.level)
260
261
262class InfoStyle(gdb.Command):
263    """dump obj style value for specified obj"""
264
265    def __init__(self):
266        super(InfoStyle, self).__init__(
267            "info style", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
268        )
269
270    def invoke(self, args, from_tty):
271        parser = argparse.ArgumentParser(description="Dump lvgl obj local style.")
272        parser.add_argument(
273            "obj",
274            type=str,
275            help="obj to show style.",
276        )
277
278        try:
279            args = parser.parse_args(gdb.string_to_argv(args))
280        except SystemExit:
281            return
282
283        obj = gdb.parse_and_eval(args.obj)
284        if not obj:
285            print("Invalid obj: ", args.obj)
286            return
287
288        obj = Value(obj)
289
290        # show all styles applied to this obj
291        for style in LVObject(obj).styles:
292            print("  ", end="")
293            dump_style_info(style)
294
295
296class InfoDrawUnit(gdb.Command):
297    """dump draw unit info"""
298
299    def __init__(self):
300        super(InfoDrawUnit, self).__init__(
301            "info draw_unit", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
302        )
303
304    def dump_draw_unit(self, draw_unit: Value):
305        # Dereference to get the string content of the name from draw_unit
306        name = draw_unit.name.string()
307
308        # Print draw_unit information and the name
309        print(f"Draw Unit: {draw_unit}, Name: {name}")
310
311        # Handle different draw_units based on the name
312        def lookup_type(name):
313            try:
314                return gdb.lookup_type(name)
315            except gdb.error:
316                return None
317
318        types = {
319            "DMA2D": lookup_type("lv_draw_dma2d_unit_t"),
320            "NEMA_GFX": lookup_type("lv_draw_nema_gfx_unit_t"),
321            "NXP_PXP": lookup_type("lv_draw_pxp_unit_t"),
322            "NXP_VGLITE": lookup_type("lv_draw_vglite_unit_t"),
323            "OPENGLES": lookup_type("lv_draw_opengles_unit_t"),
324            "DAVE2D": lookup_type("lv_draw_dave2d_unit_t"),
325            "SDL": lookup_type("lv_draw_sdl_unit_t"),
326            "SW": lookup_type("lv_draw_sw_unit_t"),
327            "VG_LITE": lookup_type("lv_draw_vg_lite_unit_t"),
328        }
329
330        type = types.get(name, lookup_type("lv_draw_unit_t"))
331        print(draw_unit.cast(type, ptr=True).dereference().format_string(pretty_structs=True, symbols=True))
332
333    def invoke(self, args, from_tty):
334        for unit in g_lvgl_instance.draw_units():
335            self.dump_draw_unit(unit)
336