mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-09 20:27:41 +08:00
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:
@@ -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>
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user