chore(gdb): add obj flag constants and expose flags in snapshot

- Add parse_bitmask_enum() to enum_parser for (1u << N) style enums
- Add gen_obj_flag_consts.py generator for lv_obj_flag_t
- Add flags_raw/flags_list properties to LVObject
- Include flags and flags_list in LVObject.snapshot() output
- Export OBJ_FLAG_NAMES and decode_obj_flags through full init chain
- Update dashboard frontend with flag display and hidden toggle
This commit is contained in:
Benign X
2026-04-01 14:16:34 +08:00
committed by VIFEX
parent 215385f15d
commit a5f6f21b2f
8 changed files with 201 additions and 13 deletions
+4
View File
@@ -44,6 +44,8 @@ from .lvgl import (
INDEV_TYPE_NAMES,
LVGroup,
LVObjClass,
OBJ_FLAG_NAMES,
decode_obj_flags,
LVSubject,
LVObserver,
SUBJECT_TYPE_NAMES,
@@ -95,6 +97,8 @@ __all__ = [
"INDEV_TYPE_NAMES",
"LVGroup",
"LVObjClass",
"OBJ_FLAG_NAMES",
"decode_obj_flags",
"LVSubject",
"LVObserver",
"SUBJECT_TYPE_NAMES",
@@ -1219,6 +1219,24 @@ ui-topbar .topbar-search::placeholder {
.detail-style-table td {
padding: 1px 4px;
}
.detail-flags-wrap {
display: flex;
flex-wrap: wrap;
gap: calc(var(--spacing) * 1);
}
.detail-flag-badge {
display: inline-block;
border-radius: 0.25rem;
background-color: var(--color-surface0);
padding-inline: calc(var(--spacing) * 1.5);
padding-block: 1px;
font-family: var(--font-mono);
font-size: 10px;
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
color: var(--color-overlay1);
border: 1px solid var(--surface0);
}
.scene-controls {
margin-bottom: calc(var(--spacing) * 1);
display: flex;
@@ -2057,7 +2075,9 @@ function renderObjTree(obj, depth = 0) {
if (det.className = "obj-node", obj.addr)
det.id = "obj-" + obj.addr;
let sum = document.createElement("summary");
if (sum.style.setProperty("--depth-color", DEPTH_COLORS[depth % DEPTH_COLORS.length]), sum.textContent = obj.class_name || "obj", det.appendChild(sum), obj.addr)
if (sum.style.setProperty("--depth-color", DEPTH_COLORS[depth % DEPTH_COLORS.length]), sum.textContent = obj.class_name || "obj", obj.flags_list?.includes("HIDDEN"))
sum.textContent += " \uD83D\uDC41\uD83D\uDDE8";
if (det.appendChild(sum), obj.addr)
objDataMap[obj.addr] = obj, registerHL(obj.addr, det), sum.addEventListener("mouseenter", () => highlightObj(obj.addr)), sum.addEventListener("mouseleave", () => clearHighlight()), sum.addEventListener("click", (e) => {
e.stopPropagation(), selectObj(obj.addr);
}), sum.addEventListener("dblclick", (e) => {
@@ -2105,7 +2125,12 @@ function renderObjDetail(addr, panel) {
let _e10 = document.createElement("span");
return _e10.setAttribute("class", "detail-coord-val"), _e10.append(String(w), " × ", String(h)), _e8.append(_e9, _e10), _e2.append(_e3, _e8), _e.append(_e1, _e2), _e;
})();
panel.appendChild(coordSec);
if (panel.appendChild(coordSec), obj.flags_list?.length) {
let flagSec = el("div", "detail-section");
flagSec.appendChild(el("div", "detail-section-title", "Flags"));
let wrap = el("div", "detail-flags-wrap");
obj.flags_list.forEach((f) => wrap.appendChild(el("span", "detail-flag-badge", f))), flagSec.appendChild(wrap), panel.appendChild(flagSec);
}
let refSec = el("div", "detail-section");
if (refSec.appendChild(el("div", "detail-section-title", "References")), obj.parent_addr) {
let row = el("div", "kv-row");
@@ -2280,8 +2305,8 @@ function flattenLayers(trees) {
let idx = sIdx++;
screenNames.push(s.layer_name || s.class_name || "screen_" + idx);
let maxLocal = 0;
function walk(obj, ld) {
let c = obj.coords || { x1: 0, y1: 0, x2: 0, y2: 0 };
function walk(obj, ld, parentHidden) {
let c = obj.coords || { x1: 0, y1: 0, x2: 0, y2: 0 }, hidden = parentHidden || (obj.flags_list?.includes("HIDDEN") ?? !1);
layers.push({
addr: obj.addr,
class_name: obj.class_name || "obj",
@@ -2293,10 +2318,11 @@ function flattenLayers(trees) {
localDepth: ld,
child_count: obj.child_count || 0,
style_count: obj.style_count || 0,
screenIdx: idx
}), maxLocal = Math.max(maxLocal, ld), obj.children?.forEach((ch) => walk(ch, ld + 1));
screenIdx: idx,
hidden
}), maxLocal = Math.max(maxLocal, ld), obj.children?.forEach((ch) => walk(ch, ld + 1, hidden));
}
walk(s, 0), screenMaxLocal[idx] = maxLocal, globalOffset += maxLocal + C.SCREEN_GAP;
walk(s, 0, !1), screenMaxLocal[idx] = maxLocal, globalOffset += maxLocal + C.SCREEN_GAP;
})), { layers, screenNames, screenMaxLocal };
}
function resolveCSSColor(cssVar) {
@@ -2340,7 +2366,9 @@ function build3DScene(container, trees, displays, dispObjs) {
btn.dataset.on = v ? "0" : "1", btn.classList.toggle("active", !v);
}, btn;
}, controls = el("div", "scene-controls"), toggle3d = makeToggle("3D", !0), toggleBorders = makeToggle("Borders", !0), bufToggles = [], toggleOrtho = makeToggle("Ortho", !1);
controls.append(toggle3d, toggleBorders), bufImages.forEach((bi) => {
controls.append(toggle3d, toggleBorders);
let toggleHidden = makeToggle("Hidden", !1);
controls.appendChild(toggleHidden), bufImages.forEach((bi) => {
let btn = makeToggle(bi.label, !0), thumb = (() => {
let _e = document.createElement("img");
return _e.setAttribute("draggable", "false"), _e.setAttribute("style", "height:1.2em;border-radius:2px;vertical-align:middle;image-rendering:pixelated"), _e;
@@ -2474,7 +2502,7 @@ function build3DScene(container, trees, displays, dispObjs) {
depthRange.min = visMax, depthMinSlider.value = String(visMax);
}
function updateDepths(spreadOv, rangeOv) {
let bordersOn = toggleBorders.dataset.on === "1", range = rangeOv ?? depthRange, screenOffset = computeScreenOffsets();
let bordersOn = toggleBorders.dataset.on === "1", showHidden = toggleHidden.dataset.on === "1", range = rangeOv ?? depthRange, screenOffset = computeScreenOffsets();
currentSpread = spreadOv ?? (is3d ? Number(spreadSlider.value) : 0.1);
let compressedIdx = 0, compressedDepth = {}, seenDepths = /* @__PURE__ */ new Set;
layers.forEach((l) => {
@@ -2483,7 +2511,7 @@ function build3DScene(container, trees, displays, dispObjs) {
seenDepths.add(gd), compressedDepth[gd] = compressedIdx++;
}), layers.forEach((l, idx) => {
let sl = sceneLayers[idx], gd = screenOffset[l.screenIdx] !== void 0 ? screenOffset[l.screenIdx] + l.localDepth : -1, inRange = gd >= Math.round(range.min) && gd <= Math.round(range.max);
sl.visible = bordersOn && layerVisible[l.screenIdx] && inRange, sl.depth = (compressedDepth[gd] ?? 0) * currentSpread;
sl.visible = bordersOn && layerVisible[l.screenIdx] && inRange && (!l.hidden || showHidden), sl.depth = (compressedDepth[gd] ?? 0) * currentSpread;
}), sceneBufs.forEach((b) => {
b.depth = -currentSpread * 0.5;
}), renderer.markDirty();
@@ -2551,7 +2579,7 @@ function build3DScene(container, trees, displays, dispObjs) {
}), toggleOrtho.addEventListener("click", () => {
let ortho = toggleOrtho.dataset.on === "1";
animateTo({ persp: ortho ? 0 : 1 });
}), toggleBorders.addEventListener("click", () => updateVisibility()), bufToggles.forEach((btn) => btn.addEventListener("click", () => updateVisibility()));
}), toggleBorders.addEventListener("click", () => updateVisibility()), toggleHidden.addEventListener("click", () => updateVisibility()), bufToggles.forEach((btn) => btn.addEventListener("click", () => updateVisibility()));
let dragging = !1, lastX = 0, lastY = 0;
viewport.onmousedown = (e) => {
if (inScreensaver)
@@ -2721,7 +2749,7 @@ function build3DScene(container, trees, displays, dispObjs) {
btn.dataset.on = on ? "1" : "0", btn.classList.toggle("active", on);
};
return resetBtn.onclick = () => {
is3d = !0, setToggle(toggle3d, !0), setToggle(toggleBorders, !0), setToggle(toggleOrtho, !1), bufToggles.forEach((btn) => setToggle(btn, !0)), screenNames.forEach((name, i) => {
is3d = !0, setToggle(toggle3d, !0), setToggle(toggleBorders, !0), setToggle(toggleOrtho, !1), setToggle(toggleHidden, !1), bufToggles.forEach((btn) => setToggle(btn, !0)), screenNames.forEach((name, i) => {
layerVisible[i] = name === "act_scr" || screenNames.length === 1;
}), layerBtns.forEach((b, i) => b.classList.toggle("active", layerVisible[i])), spreadSlider.disabled = !1, focusedAddr = null, updateDepthSliderRange(), updateVisibility();
let visMax = Number(depthMaxSlider.max);
@@ -3085,7 +3113,7 @@ class UiDashboard extends BaseComponent {
customElements.define("ui-dashboard", UiDashboard);
// src/app.ts
document.getElementById("about").innerHTML = `<span>uinspy v${"0.4.6"}</span><span>·</span><span>Built ${"2026-03-27 20:04 GMT+8"}</span><span>·</span><span>${"14ea6ea"}</span><span>·</span><span>${"Canvas2D"}</span><span>·</span><a href="https://github.com/W-Mai/uinspy" target="_blank" rel="noopener">GitHub</a><span>·</span><a href="https://lvgl.io" target="_blank" rel="noopener">LVGL</a><span>·</span><span>MIT</span>`;
document.getElementById("about").innerHTML = `<span>uinspy v${"0.4.6"}</span><span>·</span><span>Built ${"2026-04-01 13:27 GMT+8"}</span><span>·</span><span>${"8f35af9"}</span><span>·</span><span>${"Canvas2D"}</span><span>·</span><a href="https://github.com/W-Mai/uinspy" target="_blank" rel="noopener">GitHub</a><span>·</span><a href="https://lvgl.io" target="_blank" rel="noopener">LVGL</a><span>·</span><span>MIT</span>`;
</script>
</body>
</html>
+4
View File
@@ -8,6 +8,8 @@ from .core import (
INDEV_TYPE_NAMES,
LVGroup,
LVObjClass,
OBJ_FLAG_NAMES,
decode_obj_flags,
LVSubject,
LVObserver,
SUBJECT_TYPE_NAMES,
@@ -96,6 +98,8 @@ __all__ = [
"INDEV_TYPE_NAMES",
"LVGroup",
"LVObjClass",
"OBJ_FLAG_NAMES",
"decode_obj_flags",
"LVSubject",
"LVObserver",
"SUBJECT_TYPE_NAMES",
@@ -3,6 +3,7 @@ from .lv_global import curr_inst
from .lv_indev import LVIndev, INDEV_TYPE_NAMES
from .lv_group import LVGroup
from .lv_obj_class import LVObjClass
from .lv_obj_flag_consts import OBJ_FLAG_NAMES, decode_obj_flags
from .lv_observer import LVSubject, LVObserver
from .lv_observer_consts import SUBJECT_TYPE_NAMES
@@ -16,6 +17,8 @@ __all__ = [
"INDEV_TYPE_NAMES",
"LVGroup",
"LVObjClass",
"OBJ_FLAG_NAMES",
"decode_obj_flags",
"LVSubject",
"LVObserver",
"SUBJECT_TYPE_NAMES",
+14
View File
@@ -89,6 +89,18 @@ class LVObject(Value):
def y2(self):
return int(self.coords.y2)
@property
def flags_raw(self) -> int:
"""Return raw flags bitmask, 0 if corrupted."""
raw = self.safe_field("flags", 0)
return int(raw)
@property
def flags_list(self) -> list[str]:
"""Return decoded flag names."""
from .lv_obj_flag_consts import decode_obj_flags
return decode_obj_flags(self.flags_raw)
@property
def child_cnt(self) -> int:
"""Return child count, 0 if corrupted."""
@@ -153,6 +165,8 @@ class LVObject(Value):
}, {"x1": 0, "y1": 0, "x2": 0, "y2": 0}),
("child_count", lambda s: s.child_cnt, 0),
("style_count", lambda s: int(s.style_cnt), 0),
("flags", lambda s: s.flags_raw, 0),
("flags_list", lambda s: s.flags_list, []),
("parent_addr", lambda s: ptr_or_none(s.super_value("parent"))),
("group_addr", lambda s: s._get_group_addr()),
])
@@ -0,0 +1,44 @@
"""
Auto-generated object flag constants from LVGL headers.
Do not edit manually. Regenerate from the GDB script root with:
python3 scripts/generate_all.py
"""
OBJ_FLAG_NAMES = {
1: "HIDDEN",
2: "CLICKABLE",
4: "CLICK_FOCUSABLE",
8: "CHECKABLE",
16: "SCROLLABLE",
32: "SCROLL_ELASTIC",
64: "SCROLL_MOMENTUM",
128: "SCROLL_ONE",
256: "SCROLL_CHAIN_HOR",
512: "SCROLL_CHAIN_VER",
1024: "SCROLL_ON_FOCUS",
2048: "SCROLL_WITH_ARROW",
4096: "SNAPPABLE",
8192: "PRESS_LOCK",
16384: "EVENT_BUBBLE",
32768: "GESTURE_BUBBLE",
65536: "ADV_HITTEST",
131072: "IGNORE_LAYOUT",
262144: "FLOATING",
524288: "SEND_DRAW_TASK_EVENTS",
1048576: "OVERFLOW_VISIBLE",
2097152: "EVENT_TRICKLE",
4194304: "STATE_TRICKLE",
8388608: "LAYOUT_1",
16777216: "LAYOUT_2",
33554432: "WIDGET_1",
67108864: "WIDGET_2",
134217728: "USER_1",
268435456: "USER_2",
536870912: "USER_3",
1073741824: "USER_4",
}
def decode_obj_flags(raw: int) -> list[str]:
"""Decode a bitmask of lv_obj_flag_t into a list of flag names."""
return [name for bit, name in OBJ_FLAG_NAMES.items() if raw & bit]
+55
View File
@@ -68,6 +68,61 @@ def parse_enum(path: Path, enum_type: str, prefix: str,
return entries
def parse_bitmask_enum(path: Path, enum_type: str, prefix: str,
skip: set[str] | None = None) -> dict[int, str]:
"""Parse a C typedef enum with bitmask values (1u << N) from a header.
Only entries with explicit ``(1u << N)`` assignments are collected.
Entries whose value references other enum members (aliases / combos)
are silently skipped.
Args:
path: Path to the C header file.
enum_type: The typedef name (e.g. "lv_obj_flag_t").
prefix: Enum member prefix to strip (e.g. "LV_OBJ_FLAG_").
skip: Optional set of full enum member names to skip.
Returns:
Dict mapping int value -> short name string.
"""
text = path.read_text()
skip = skip or set()
pattern = rf"\}}\s*{re.escape(enum_type)}\s*;"
m = re.search(rf"typedef\s+enum\s*\{{(.*?){pattern}", text, re.DOTALL)
if not m:
raise RuntimeError(f"Cannot find {enum_type} enum in {path}")
entries = {}
for line in m.group(1).splitlines():
line = line.strip().rstrip(",")
if (
not line
or line.startswith("/*")
or line.startswith("//")
or line.startswith("*")
or line.startswith("#")
):
continue
# Match: NAME = (1u << N) or NAME = (1 << N)
match = re.match(
rf"({re.escape(prefix)}\w+)\s*=\s*\(1u?\s*<<\s*(\d+)\)", line
)
if not match:
continue
name = match.group(1)
if name in skip:
continue
bit = int(match.group(2))
short = name.removeprefix(prefix)
entries[1 << bit] = short
return entries
def generate_dict_module(
description: str,
dicts: dict[str, dict],
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""Generate object flag constant tables from LVGL headers."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from enum_parser import parse_bitmask_enum, generate_dict_module
LVGL_SRC = Path(__file__).parent.parent.parent.parent.parent / "src"
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "core" / "lv_obj_flag_consts.py"
def main():
obj_flags = parse_bitmask_enum(
LVGL_SRC / "core" / "lv_obj.h",
"lv_obj_flag_t",
"LV_OBJ_FLAG_",
)
# Add decode helper after the dict
src = generate_dict_module(
"object flag constants from LVGL headers",
{"OBJ_FLAG_NAMES": obj_flags},
)
src += (
"\ndef decode_obj_flags(raw: int) -> list[str]:\n"
' """Decode a bitmask of lv_obj_flag_t into a list of flag names."""\n'
" return [name for bit, name in OBJ_FLAG_NAMES.items() if raw & bit]\n"
)
OUTPUT.write_text(src)
print(f"Generated {OUTPUT.name} ({len(obj_flags)} obj flags)")
if __name__ == "__main__":
main()