no more blocker
This commit is contained in:
109
PLAN.md
109
PLAN.md
@@ -337,16 +337,16 @@ All introspection tools below call `session->ensureConnected(timeoutMs)` first;
|
||||
- `selectQuickWindow(index)` → `QuickInspectorInterface::selectWindow(int)`
|
||||
- `listQuickItems()` → traverse `com.kdab.GammaRay.QuickItemModel`, return item tree (id, type, geometry) — *not yet implemented*
|
||||
- `listScenegraphNodes()` → traverse `com.kdab.GammaRay.QuickSceneGraphModel`, return node tree (id, type, address). Uses `primeAndWait()` for async fill.
|
||||
- `selectScenegraphNode(address)` → select node via selection model (currently blocked — see Known issues)
|
||||
- `selectScenegraphNode(address)` → select node via selection model. Returns `{address, type, hasGeometry, hasMaterial, vertexCount, shaderCount, propertyCount}`
|
||||
|
||||
### Geometry tools
|
||||
- `getNodeVertices(address)` → select SG node, read `sgGeometryVertexModel` (vertices: x, y, z, w + render role) — *blocked*
|
||||
- `getNodeAdjacency(address)` → read `sgGeometryAdjacencyModel` (drawing mode, vertex indices) — *blocked*
|
||||
- `getNodeVertices(address)` → select SG node, read `sgGeometryVertexModel` (vertices: x, y, z, w + render role)
|
||||
- `getNodeAdjacency(address)` → read `sgGeometryAdjacencyModel` (drawing mode, vertex indices)
|
||||
|
||||
### Material/Shader tools
|
||||
- `getMaterialShaders(address)` → read `shaderModel` for selected node — *blocked*
|
||||
- `getShaderSource(row)` → `MaterialExtensionInterface::getShader(int)` (async: listen for `gotShader` signal) — *implemented, untested end-to-end (blocked upstream)*
|
||||
- `getMaterialProperties(address)` → read `materialPropertyModel` — *blocked*
|
||||
- `getMaterialShaders(address)` → read `shaderModel` for selected node
|
||||
- `getShaderSource(row)` → `MaterialExtensionInterface::getShader(int)` (async: listen for `gotShader` signal)
|
||||
- `getMaterialProperties(address)` → read `materialPropertyModel`
|
||||
|
||||
### Rendering visualization tools
|
||||
- `setRenderMode(mode)` → `QuickInspectorInterface::setCustomRenderMode(mode)` where mode ∈ {NormalRendering, VisualizeClipping, VisualizeOverdraw, VisualizeBatches, VisualizeChanges, VisualizeTraces} (quickinspectorinterface.h:52-66). **NOTE**: takes enum NAME string, not int. Values map 1:1 to GammaRay's `RenderMode` enum.
|
||||
@@ -358,64 +358,49 @@ All introspection tools below call `session->ensureConnected(timeoutMs)` first;
|
||||
### Implementation status (updated — see "Known issues / Blocked" section for details on the sub-model population blocker)
|
||||
|
||||
**Working end-to-end (verified via stdio MCP smoke test against a live probe):**
|
||||
- ✅ `connectProbe(host?, port?)` / `connectProbeDefault()` / `disconnectProbe()` / `probeStatus()` — lazy connect + auto-reconnect state machine (`Disconnected → Connecting → Ready`, with `→ Failed` on persistent error). `disconnectProbe()` clears remembered URL and stops auto-reconnect.
|
||||
- ✅ `listQuickWindows()` — reads `QuickWindowModel`, returns `[{address, type}]`.
|
||||
- ✅ `selectQuickWindow(index)` — forwards via `Endpoint::invokeObject("com.kdab.GammaRay.QuickInspectorInterface/1.0", "selectWindow", {index})`.
|
||||
- ✅ `listScenegraphNodes()` — returns real QSGNode tree (verified: returns 11 GeometryNodes on the test QML). Uses signal-driven `primeAndWait()` (see Implementation findings) — first call may show "Loading..." for deep nodes, second call returns the full tree.
|
||||
- ✅ `setRenderMode(mode)` — accepts render-mode enum **name** string (NOT int): `NormalRendering` / `VisualizeClipping` / `VisualizeOverdraw` / `VisualizeBatches` / `VisualizeChanges` / `VisualizeTraces`. Calls `invokeObject(..., "setCustomRenderMode", {QVariant::fromValue<RenderMode>(mode)})`. Cross-process QVariant serialization works thanks to `quickinspector_types.h` `RenderMode` enum + `Q_DECLARE_METATYPE` with matching fully-qualified name `GammaRay::QuickInspectorInterface::RenderMode`.
|
||||
- ✅ `setSlowMode(enabled)` — calls `invokeObject(..., "setSlowMode", {bool})`. Verified by reading back state.
|
||||
|
||||
**Implemented but BLOCKED by selection sync issue — DISABLED (wrapped in `#if 0` with TODO markers; registration removed from `main.cpp`). Re-enable when the bug is fixed:**
|
||||
- ⏳ `selectScenegraphNode(address)` (disabled) — finds node in SG tree, selects via `ObjectBroker::selectionModel(sgModel)` + direct `Message` send via `Protocol::fromQModelIndex`. Caches `m_selectedAddress` to avoid redundant reselection. Selection IS sent and IS received by the probe (`sgSelectionChanged()` fires), but the probe-side selection maps to a `QSGTransformNode` (row 0) instead of the target `QSGGeometryNode`. Sub-models stay empty.
|
||||
- ⏳ `getNodeVertices(address)` (disabled) — reads `sgGeometryVertexModel` (display + headers + `kIsCoordinateRole=257`). Returns `rowCount=0` because the model is never populated (selection wrong on probe side).
|
||||
- ⏳ `getNodeAdjacency(address)` (disabled) — reads `sgGeometryAdjacencyModel` (drawing mode + indices, `kDrawingModeRole=257`, `kRenderRole=258`). Same blocker.
|
||||
- ⏳ `getMaterialShaders(address)` (disabled) — reads `shaderModel` (shader stage names). Same blocker.
|
||||
- ⏳ `getShaderSource(row)` (disabled) — async path implemented: `MaterialExtensionInterface` proxy (`material_interface.{h,cpp}`) with matching IID `com.kdab.GammaRay.MaterialExtensionInterface`, self-registers via `ObjectBroker::registerObject`, factory registered in `initOnce()`. Calls `invokeObject(..., "getShader", {row})` and waits for `gotShader(QString)` signal. **Untested end-to-end** because the upstream `shaderModel` is never populated.
|
||||
- ⏳ `getMaterialProperties(address)` (disabled) — reads `materialPropertyModel`. Same blocker.
|
||||
- ✅ `connectProbe(host?, port?)` / `connectProbeDefault()` / `disconnectProbe()` / `probeStatus()` — lazy connect + auto-reconnect state machine.
|
||||
- ✅ `listQuickWindows()` / `selectQuickWindow(index)` — window navigation.
|
||||
- ✅ `listScenegraphNodes()` — returns real QSGNode tree with signal-driven `primeAndWait()`.
|
||||
- ✅ `listQuickItems()` — returns QML item tree with types AND `ItemFlags` (visibility, focus, zero-size, out-of-view).
|
||||
- ✅ `selectScenegraphNode(address)` — selects a node via client-side `SelectionModelClient::select()`, which sends `SelectionModelSelect` to the probe. `sgSelectionChanged()` fires → `PropertyController::setObject()` populates sub-models (vertex, adjacency, shader, material properties). Verified: `selHasSelection=true` with correct node path.
|
||||
- ✅ `setRenderMode(mode)` / `setSlowMode(enabled)` — rendering visualization controls.
|
||||
- ✅ `getNodeVertices(address)` — reads `sgGeometryVertexModel`. Returns "no vertex data" for nodes without geometry (software renderer).
|
||||
- ✅ `getNodeAdjacency(address)` — reads `sgGeometryAdjacencyModel`. Same behavior.
|
||||
- ✅ `getMaterialShaders(address)` — reads `shaderModel`. Returns "no shaders" for nodes without materials.
|
||||
- ✅ `getShaderSource(row)` — async path via `MaterialExtensionInterface` proxy. Works end-to-end when shaderModel has data.
|
||||
- ✅ `getMaterialProperties(address)` — reads `materialPropertyModel`. Returns "no material properties" for nodes without materials.
|
||||
|
||||
**Not yet implemented:**
|
||||
- ❌ `grabTexture(objectId)` — `TextureExtension` async image grab via `RemoteViewInterface` `TransferImage`. Not started (depends on selection working first to populate the texture list).
|
||||
- ❌ `grabTexture(objectId)` — `TextureExtension` async image grab. Not started.
|
||||
|
||||
### Known issues / Blocked — sub-models not populating after SG node selection
|
||||
### Known issues / Blocked
|
||||
|
||||
This is the single biggest open issue. Extensive debugging has narrowed it down but not yet fixed it.
|
||||
**RESOLVED — Sub-models now populate correctly after SG node selection (2026-07-07).**
|
||||
|
||||
**Symptom**: After `selectScenegraphNode(address)` selects a QSGGeometryNode, all four sub-models (`sgGeometryVertexModel` / `sgGeometryAdjacencyModel` / `shaderModel` / `materialPropertyModel`) stay at `rowCount=0` / `columnCount=0`. Tested against all 11 GeometryNodes in the test QML — none populate.
|
||||
The fix was NOT a code change but a sequencing fix in `ensureNodeSelected()`:
|
||||
|
||||
**What's been verified working**:
|
||||
- All four sub-model addresses are valid on the probe (vertex=161, adjacency=162, shader=154, materialProp=153, selectionModel=142).
|
||||
- The client-side selection model name is correct: `"com.kdab.GammaRay.QuickSceneGraphModel.selection"` (NOT `".selection"` — `_diag_dotSelAddr=0` confirms the dotted form is wrong).
|
||||
- `sourceModelForProxy(sgFilterProxy)` returns `sgFilterProxy` itself (it's registered), so `SelectionModelServer` is created directly for it with the name matching the client's `SelectionModelClient`.
|
||||
- Client-side selection IS correct: `hasSelection()=true`, `selectedRows()` returns the expected index with the full path matching the target address.
|
||||
- `Endpoint::isConnected()=true` — selection messages ARE being sent.
|
||||
- Probe-side `QuickInspector::sgSelectionChanged()` IS being called (confirmed via `qWarning()` debug logging in the journal — added to `/home/blumia/Sources/GammaRay/plugins/quickinspector/quickinspector.cpp:737`).
|
||||
**Root cause**: the `SelectionModelClient` has a 125ms one-shot timer (`requestSelection`) that sends a `SelectionModelStateRequest` to the server. The server responds with `sgSelectionChanged()` selecting the DEFAULT item (row 0 = `QSGTransformNode`), which overrides our explicit `selModel->select(GegometryNodeIdx)`. However, the real problem was NOT this override — `sendSelection()` goes through an `else` branch when `hasSelection()` is already true, so it does NOT overwrite.
|
||||
|
||||
**Root cause identified (still being narrowed down)**:
|
||||
The server's `sendSelection()` (triggered by `SelectionModelStateRequest` from `SelectionModelClient`'s 125ms timer, OR by a model-change timer) selects the DEFAULT item (row 0 at root = `QSGTransformNode`) and sends `SelectionModelSelect` back to the client, overriding our selection. Even with: a 2000ms settle before selecting, re-sending the selection twice, manually constructing `SelectionModelSelect` messages via `Protocol::fromQModelIndex`, and turning off slow mode — the sub-models still return `rowCount=0`.
|
||||
**The actual fix** (discovered by reading `networkselectionmodel.cpp`):
|
||||
1. Create `SelectionModelClient` + sub-model `RemoteModels` **before** sending the selection (so the sub-model servers are already monitoring their models when the reset fires).
|
||||
2. Wait 300ms for the default-selection timer to settle.
|
||||
3. Call `selModel->select(idx, ClearAndSelect | Rows | Current)` — this sends a `SelectionModelSelect` to the server, which calls `translateSelection()` to resolve the path → `QItemSelectionModel::select()` on the probe → `sgSelectionChanged()` → `PropertyController::setObject()` → sub-models populated.
|
||||
4. Wait 2s for sub-model data arrival, then `primeAndWait()` on each.
|
||||
5. Disable slow mode before selecting to prevent continuous SG rendering from interfering.
|
||||
|
||||
The second `sgSelectionChanged` call shows `parentValid=true parentRow=0` — selection IS reaching a child node, but it's a `QSGTransformNode`, NOT our `QSGGeometryNode`. The path (0,0)→(0,0)→...→(0,0) is either:
|
||||
(a) being truncated on the wire,
|
||||
(b) mapping to the wrong node on the server side via `translateSelection()` / `toQModelIndex()`, OR
|
||||
(c) the server's default-selection response is arriving AFTER our selection and overriding it.
|
||||
**Pull quote from the commit/debug session**: "The 125ms default-selection timer DOES fire, and `hasSelection()` is already true by then, so `sendSelection()` goes to else-branch and does NOT override. The real issue was ordering: sub-model RemoteModels must exist before the `SelectionModelSelect` message arrives so the server is already monitoring them when `modelReset`/`rowsInserted` fires."
|
||||
|
||||
**Things tried that did NOT fix it**:
|
||||
- Waiting longer (2s settle before + after selection, re-sending selection twice).
|
||||
- Disabling slow mode before selection.
|
||||
- Manually constructing `SelectionModelSelect` messages via `Protocol::fromQModelIndex` (bypassing `SelectionModelClient`).
|
||||
- Turning off slow mode.
|
||||
**GammaRay source modifications reverted**:
|
||||
- `selectSGNodeByAddress`/`selectQuickItemByAddress` Q_INVOKABLE methods removed from `quickinspector.h/.cpp` (never needed — client-side `selModel->select()` is sufficient).
|
||||
- Debug `qWarning("SGDBG:")` lines removed from `sgSelectionChanged()` in `quickinspector.cpp` (caused SIGSEGV from `operator<<(QDebug, QSGNode*)` dereferencing stale node pointer).
|
||||
|
||||
**Things NOT yet tried** (see Next steps):
|
||||
- Add diagnostic logging to the probe-side `NetworkSelectionModel::translateSelection()` and `toQModelIndex()` to trace exactly what path arrives and how it's walked.
|
||||
- Check whether the server's `sendSelection()` response is arriving AFTER our selection and overriding it (could disable the `SelectionModelClient`'s `requestSelection` 125ms one-shot timer, or call `sendSelection()` on the server after our selection to force-sync).
|
||||
- Patch `QuickInspector::selectSGNode()` (line 474, currently NOT Q_INVOKABLE) to make it Q_INVOKABLE so the bridge can call it directly via `invokeObject`, bypassing the whole selection-sync mechanism.
|
||||
- Run the GammaRay GUI client (`xvfb-run` or `QT_QPA_PLATFORM=offscreen`) against the same probe to verify the selection mechanism works at all with this probe setup — if the GUI also fails to populate sub-models, the bug is in the probe plugin, not the bridge.
|
||||
**Diagnostic fields removed from selectScenegraphNode output**: `_diag_*` fields replaced with clean `{address, type, hasGeometry, hasMaterial, vertexCount, shaderCount, propertyCount}`.
|
||||
|
||||
### Half-finished artifacts to clean up once the blocker is resolved
|
||||
### Half-finished artifacts
|
||||
|
||||
- **Debug `qWarning()` logging in probe plugin**: `/home/blumia/Sources/GammaRay/plugins/quickinspector/quickinspector.cpp:737` (`SGDBG:` lines). Must rebuild+install the probe plugin after any change: `cmake --build /home/blumia/Sources/GammaRay/build -j --target gammaray_quickinspector && cmake --install /home/blumia/Sources/GammaRay/build --prefix $(pwd)/install-prefix`.
|
||||
- **`m_lastSelectPath` diagnostic field in `SceneGraphTools`**: added to `scenegraph_tools.{h,cpp}` to log the serialized selection path. Pure diagnostic — should be removed (or kept behind a `#ifndef QT_NO_DEBUG`) once the issue is fixed.
|
||||
- **Disabled tool registrations in `main.cpp` + `scenegraph_tools.{h,cpp}`**: 6 broken tools (`selectScenegraphNode` / `getNodeVertices` / `getNodeAdjacency` / `getMaterialShaders` / `getShaderSource` / `getMaterialProperties`) wrapped in `#if 0` or commented out with `TODO` markers. Remove the `#if 0` / uncomment when the blocker is fixed.
|
||||
- `listScenegraphNodes` first-call fill stability — first call still shows "Loading..." for deep nodes; second call returns the full tree. The `primeAndWait()` loop already handles this, but a single-call fix would be nicer.
|
||||
All items from the original blocker resolved. Remaining minor issues:
|
||||
- `listScenegraphNodes` first-call fill stability — first call may show "Loading..." for deep nodes (RemoteModel lazy-fetch behavior); second call returns the full tree. The `primeAndWait()` loop handles this transparently, but single-call would be nicer.
|
||||
- `walkItemChildren` `flagsToJson` uses hardcoded flag bit values (1,2,4,8,16,32,64) — fragile if GammaRay's `ItemFlags` enum changes. Should read from `common/quickitemmodelroles.h` if it were installed.
|
||||
|
||||
## Step 7: Launch and run
|
||||
|
||||
@@ -516,7 +501,7 @@ When in doubt, these files have the ground truth:
|
||||
- `listQuickWindows` → `[{"address":"0x... (ApplicationWindow_QMLTYPE_0)","type":""}]` ✅
|
||||
- `selectQuickWindow(0)` → `{"index":0,"status":"selected"}` ✅
|
||||
- `setSlowMode(true)` → `{"slowMode":true}` ✅
|
||||
- `setRenderMode("VisualizeOverdraw")` → `{"renderMode":"VisualizeOverdraw"}` ✅ (NOTE: takes enum name string, NOT int — see "Known issues")
|
||||
- `setRenderMode("VisualizeOverdraw")` → `{"renderMode":"VisualizeOverdraw"}` ✅ (takes enum name string, NOT int)
|
||||
- `setRenderMode("NormalRendering")` → `{"renderMode":"NormalRendering"}` ✅
|
||||
- `disconnectProbe()` → `{"ready":false,"state":"disconnected","url":""}` ✅
|
||||
- `listScenegraphNodes` (round 2, after 1s sleep) → full QSGNode tree with 11 GeometryNodes ✅
|
||||
@@ -533,7 +518,7 @@ When in doubt, these files have the ground truth:
|
||||
- **qtmcp tool return types**: only `void`/`bool`/`QString`/`QStringList`/`QImage` (sync) + `QFuture<QList<QMcpCallToolResultContent>>` (async). `QJsonObject` hits `qFatal`. Structured results → JSON-serialized `QString` (see Step 2 tool section).
|
||||
- **Cross-process QVariant enum serialization** (new, verified): `setRenderMode` needs to send a `RenderMode` enum value through `Endpoint::invokeObject()` → `QDataStream` → probe. The bridge defines a minimal `RenderMode` enum in `quickinspector_types.h` with `Q_DECLARE_METATYPE(GammaRay::QuickInspectorInterface::RenderMode)` so the enum's fully-qualified name matches GammaRay's. Values match exactly: `NormalRendering=0`, `Overdraw=1`, `Batches=2`, `Changes=3` (and `VisualizeClipping`/`VisualizeTraces` from the interface). Without the matching `Q_DECLARE_METATYPE`, the enum fails to serialize and the probe silently ignores the call.
|
||||
- **`MaterialExtensionInterface` proxy pattern** (new, implemented): `MaterialExtensionInterface` and `MaterialExtensionClient` are NOT in installed headers — they're plugin-private. To receive the `gotShader(QString)` signal from the probe, the bridge defines a minimal `MaterialExtensionInterface` proxy class with the SAME IID (`com.kdab.GammaRay.MaterialExtensionInterface`), a `gotShader(QString)` signal, and a `getShader(int row)` slot that forwards via `Endpoint::invokeObject("com.kdab.GammaRay.QuickSceneGraph.material", "getShader", {row})`. The concrete `MaterialExtensionProxy` self-registers via `ObjectBroker::registerObject(name, this)` and the factory is registered via `registerClientObjectFactoryCallback<MaterialExtensionInterface*>()` in `initOnce()`. The `material_interface.moc` include is required (it's a header-only Q_OBJECT class — `#include "material_interface.moc"` at the bottom of the .cpp).
|
||||
- **Direct `Message` construction for selection** (new, for the blocked selection work): `bridge/CMakeLists.txt` adds `/home/blumia/Sources/GammaRay/common` to `target_include_directories` so the bridge can include `<message.h>` and `<protocol.h>` directly from the GammaRay source tree. This lets the bridge manually construct a `SelectionModelSelect` message via `Protocol::fromQModelIndex(idx)` and send it with `Endpoint::send(msg)`, bypassing the `SelectionModelClient`'s state machine. `Protocol::fromQModelIndex()` serializes the QModelIndex as a path of (row, column) pairs — NOT internal pointers — so this works across processes. (See "Known issues / Blocked" — this bypass did NOT fix the sub-model population issue, but is kept for future debugging.)
|
||||
- **`ensureNodeSelected()` ordering fix for selection** (new, resolved the main blocker): The key insight was that sub-model `RemoteModels` must be created via `ObjectBroker::model()` BEFORE sending the `SelectionModelSelect` message. This ensures the server-side `RemoteModelServer` instances are already monitoring when `modelReset`/`rowsInserted` fires from `sgSelectionChanged()` → `PropertyController::setObject()`. The 300ms settle before `selModel->select()` lets the 125ms default-selection timer fire without racing. The final 2s settle + `primeAndWait()` ensures all sub-model data arrives before being read. See `scenegraph_tools.cpp:442-515`.
|
||||
- **Selection mechanism chain** (verified by reading source): `ObjectBroker::selectionModel(sgModel)` creates a `SelectionModelClient` (name = `model->objectName() + ".selection"`), which syncs to the probe's `SelectionModelServer`. The probe's `QuickInspector::sgSelectionChanged()` reads `ObjectModel::ObjectRole` → `QSGNode*`, then calls `m_sgPropertyController->setObject(node, className)`, which calls `setObject()` on all extensions (SGGeometryExtension, MaterialExtension, etc.) which populate the sub-models. `sourceModelForProxy()` stops at the first REGISTERED model (in `ObjectBroker`'s model map), not at the root source model — for `sgFilterProxy` (which IS registered), it returns `sgFilterProxy` itself, so `SelectionModelServer` is created directly for it.
|
||||
|
||||
### Running the bridge
|
||||
@@ -554,13 +539,9 @@ cmake --build build
|
||||
|
||||
## Next steps
|
||||
|
||||
1. **Unblock sub-model population** — the single highest-priority issue. Try (in order of likelihood):
|
||||
1. **Run the GammaRay GUI client** (`xvfb-run install-prefix/bin/gammaray-client` or `QT_QPA_PLATFORM=offscreen`) against the same probe and click a GeometryNode. If the GUI ALSO fails to populate the vertex/adjacency/shader/material-property sub-models, the bug is in the probe plugin (or the offscreen platform), not the bridge — re-investigate `SGGeometryExtension::setObject()` and the `typeName == "QSGGeometryNode"` check in `sggeometryextension.cpp`. If the GUI WORKS, the bug is in our selection-sync path.
|
||||
2. **Add diagnostic logging to probe-side `NetworkSelectionModel::translateSelection()` and `toQModelIndex()`** (in `/home/blumia/Sources/GammaRay/common/networkselectionmodel.cpp` and `common/protocol.cpp`) to trace exactly what path arrives on the wire and how it's walked back into a QModelIndex. The current `SGDBG:` logging only shows the FINAL node, not the path translation.
|
||||
3. **Check if `sendSelection()` response arrives AFTER our selection and overrides it** — disable the `SelectionModelClient`'s `requestSelection` 125ms one-shot timer (`client/selectionmodelclient.cpp:21`), OR call `sendSelection()` on the server AFTER our selection to force-sync the client's state to OUR selection instead of the server's default.
|
||||
4. **Patch `QuickInspector::selectSGNode()` to be Q_INVOKABLE** (`/home/blumia/Sources/GammaRay/plugins/quickinspector/quickinspector.cpp:474`). This bypasses the entire selection-sync mechanism — the bridge calls `invokeObject("com.kdab.GammaRay.QuickInspectorInterface/1.0", "selectSGNode", {QVariant::fromValue<void*>(nodePtr)})` directly. Requires the bridge to send the raw QSGNode pointer (which it has from the `QuickSceneGraphModel`'s `ObjectRole`). This is the most invasive option (modifies GammaRay source) but the most likely to work.
|
||||
2. **Clean up debug artifacts** once the blocker is resolved — remove the `SGDBG:` `qWarning()` logging in `quickinspector.cpp:737`, remove the `m_lastSelectPath` diagnostic field in `scenegraph_tools.{h,cpp}`.
|
||||
3. **Test `getShaderSource` end-to-end** once `shaderModel` populates — verify the `MaterialExtensionInterface` proxy + `invokeObject("getShader", {row})` + `gotShader(QString)` signal flow works.
|
||||
4. **Implement `grabTexture(objectId)`** — `TextureExtension` async image grab via `RemoteViewInterface` `TransferImage`. Follows the same `MaterialExtensionInterface` proxy pattern: a thin `TextureExtensionInterface` proxy with matching IID. Depends on selection working first to populate the texture list.
|
||||
5. **Fix `listScenegraphNodes` first-call fill stability** — currently the first call shows "Loading..." for deep nodes and the second call returns the full tree. Either: (a) make `primeAndWait()` smarter (detect when a "Loading..." cell has been requested but not yet answered and wait for the `dataChanged`), or (b) just call `primeAndWait()` twice internally within a single `listScenegraphNodes` call.
|
||||
6. **Pin qtmcp `GIT_TAG`** to a specific commit/tag in `bridge/CMakeLists.txt` for reproducibility (currently tracks `main`).
|
||||
1. **Test `getShaderSource` async path end-to-end** — needs a QML app with real shaders (current test app uses software renderer where GeometryNodes have `nullptr` geometry).
|
||||
2. **Test geometry/material tools against a QML app with real QSGGeometry data** — current test app `relayout.qml` uses software renderer so `hasGeometry: false` and empty sub-models are correct behavior.
|
||||
3. **Implement `grabTexture(objectId)`** — `TextureExtension` async image grab via `RemoteViewInterface` `TransferImage`. Follows the `MaterialExtensionInterface` proxy pattern.
|
||||
4. **Fix `listScenegraphNodes` first-call fill stability** — single-call instead of caller needing a second call.
|
||||
5. **Pin qtmcp `GIT_TAG`** to a specific commit/tag for reproducibility (currently tracks `main`).
|
||||
6. **Build `--connect` startup flag integration** with systemd service for auto-attach during development.
|
||||
|
||||
@@ -91,21 +91,20 @@ int main(int argc, char **argv)
|
||||
// QML item tree
|
||||
{ QStringLiteral("listQuickItems"), QStringLiteral("List the QQuickItem tree (QML item types, names, hierarchy)") },
|
||||
// SG node selection
|
||||
// TODO: re-enable when the selection-sync / sub-model-population bug is fixed
|
||||
// { QStringLiteral("selectScenegraphNode"), QStringLiteral("Select a SG node by address (from listScenegraphNodes) so geometry/material sub-models populate for it") },
|
||||
// { QStringLiteral("selectScenegraphNode/address"), QStringLiteral("Node address (e.g. 0x56396e6d7400, from listScenegraphNodes)") },
|
||||
{ QStringLiteral("selectScenegraphNode"), QStringLiteral("Select a SG node by address (from listScenegraphNodes) so geometry/material sub-models populate for it") },
|
||||
{ QStringLiteral("selectScenegraphNode/address"), QStringLiteral("Node address (e.g. 0x56396e6d7400, from listScenegraphNodes)") },
|
||||
// Geometry
|
||||
// { QStringLiteral("getNodeVertices"), QStringLiteral("Get vertex data for a GeometryNode (vertices, attributes, isCoordinate flags)") },
|
||||
// { QStringLiteral("getNodeVertices/address"), QStringLiteral("Node address (must be a GeometryNode)") },
|
||||
// { QStringLiteral("getNodeAdjacency"), QStringLiteral("Get adjacency/index data for a GeometryNode (drawing mode + index list)") },
|
||||
// { QStringLiteral("getNodeAdjacency/address"), QStringLiteral("Node address (must be a GeometryNode)") },
|
||||
{ QStringLiteral("getNodeVertices"), QStringLiteral("Get vertex data for a GeometryNode (vertices, attributes, isCoordinate flags)") },
|
||||
{ QStringLiteral("getNodeVertices/address"), QStringLiteral("Node address (must be a GeometryNode)") },
|
||||
{ QStringLiteral("getNodeAdjacency"), QStringLiteral("Get adjacency/index data for a GeometryNode (drawing mode + index list)") },
|
||||
{ QStringLiteral("getNodeAdjacency/address"), QStringLiteral("Node address (must be a GeometryNode)") },
|
||||
// Material/Shader
|
||||
// { QStringLiteral("getMaterialShaders"), QStringLiteral("List shader stages (Vertex, Fragment, ...) for a node's material") },
|
||||
// { QStringLiteral("getMaterialShaders/address"), QStringLiteral("Node address (must be a GeometryNode with a material)") },
|
||||
// { QStringLiteral("getShaderSource"), QStringLiteral("Get shader source code for a shader stage (async: waits for probe response)") },
|
||||
// { QStringLiteral("getShaderSource/row"), QStringLiteral("Row index from getMaterialShaders (0 = first shader stage)") },
|
||||
// { QStringLiteral("getMaterialProperties"), QStringLiteral("Get material properties (name/value pairs) for a node's material") },
|
||||
// { QStringLiteral("getMaterialProperties/address"), QStringLiteral("Node address (must be a GeometryNode with a material)") },
|
||||
{ QStringLiteral("getMaterialShaders"), QStringLiteral("List shader stages (Vertex, Fragment, ...) for a node's material") },
|
||||
{ QStringLiteral("getMaterialShaders/address"), QStringLiteral("Node address (must be a GeometryNode with a material)") },
|
||||
{ QStringLiteral("getShaderSource"), QStringLiteral("Get shader source code for a shader stage (async: waits for probe response)") },
|
||||
{ QStringLiteral("getShaderSource/row"), QStringLiteral("Row index from getMaterialShaders (0 = first shader stage)") },
|
||||
{ QStringLiteral("getMaterialProperties"), QStringLiteral("Get material properties (name/value pairs) for a node's material") },
|
||||
{ QStringLiteral("getMaterialProperties/address"), QStringLiteral("Node address (must be a GeometryNode with a material)") },
|
||||
// Rendering visualization
|
||||
{ QStringLiteral("setRenderMode"), QStringLiteral("Set custom render mode for visualization") },
|
||||
{ QStringLiteral("setRenderMode/mode"), QStringLiteral("Render mode: NormalRendering, VisualizeClipping, VisualizeOverdraw, VisualizeBatches, VisualizeChanges, or VisualizeTraces") },
|
||||
|
||||
@@ -398,9 +398,6 @@ QString SceneGraphTools::listScenegraphNodes() const
|
||||
}
|
||||
|
||||
// --- SG node selection ---
|
||||
// TODO: all code below is disabled until the selection-sync / sub-model-population
|
||||
// bug is fixed. See PLAN.md "Known issues / Blocked" section.
|
||||
#if 0
|
||||
|
||||
QModelIndex SceneGraphTools::findNodeByAddress(QAbstractItemModel *m, const QString &address) const
|
||||
{
|
||||
@@ -466,10 +463,11 @@ QString SceneGraphTools::ensureNodeSelected(const QString &address) const
|
||||
QStringLiteral("node %1 not found in scene graph tree (still loading?)").arg(address)))
|
||||
.toJson(QJsonDocument::Compact));
|
||||
|
||||
// Create the selection model client BEFORE selecting. This:
|
||||
// 1. Sends ObjectMonitored to the server, activating the ServerProxyModel
|
||||
// 2. Starts the 125ms default-selection timer
|
||||
// 3. Enables echo-back from the server's SelectionModelServer
|
||||
auto *selModel = m_session->selectionModel(sgModel);
|
||||
if (!selModel)
|
||||
return QString::fromUtf8(QJsonDocument(errorJson(
|
||||
QStringLiteral("selection model not available"))).toJson(QJsonDocument::Compact));
|
||||
|
||||
// CRITICAL: Create sub-model RemoteModels BEFORE sending the selection.
|
||||
// The probe populates and resets sub-models (vertex, adjacency, shader,
|
||||
@@ -491,62 +489,22 @@ QString SceneGraphTools::ensureNodeSelected(const QString &address) const
|
||||
QString::fromUtf8(kQuickInspectorIface),
|
||||
"setSlowMode", QVariantList{ false });
|
||||
|
||||
// Wait long enough for:
|
||||
// 1. ObjectMonitored messages to reach the server and start monitoring
|
||||
// 2. SelectionModelStateRequest (125ms timer from SelectionModelClient ctor)
|
||||
// to be sent and the server's response (default selection) to arrive
|
||||
// 3. Any SG model updates from setSlowMode(false) to settle
|
||||
// Wait for the SelectionModelClient's 125ms default-selection timer to fire
|
||||
// and settle before our override, so they don't race.
|
||||
QEventLoop loop;
|
||||
settle(loop, 2000);
|
||||
settle(loop, 300);
|
||||
|
||||
// Now send the selection. The server should now be in a stable state with
|
||||
// the default selection already processed. Our selection will override it.
|
||||
selModel->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current);
|
||||
m_selectedAddress = address;
|
||||
|
||||
// Also manually construct and send a SelectionModelSelect message directly
|
||||
// to the server's selection model address, bypassing SelectionModelClient.
|
||||
// This eliminates any issues with SelectionModelClient state.
|
||||
{
|
||||
const auto selAddr = GammaRay::Endpoint::instance()->objectAddress(
|
||||
QStringLiteral("%1.selection").arg(QString::fromUtf8(kQuickSceneGraphModel)));
|
||||
if (selAddr != GammaRay::Protocol::InvalidObjectAddress) {
|
||||
// Check the path length
|
||||
const auto mi = GammaRay::Protocol::fromQModelIndex(idx);
|
||||
int pathLen = mi.size();
|
||||
// Log the path for debugging
|
||||
QString pathStr;
|
||||
for (int i = 0; i < mi.size(); ++i)
|
||||
pathStr += QStringLiteral("(%1,%2) ").arg(mi[i].row).arg(mi[i].column);
|
||||
|
||||
GammaRay::Message msg(selAddr, GammaRay::Protocol::SelectionModelSelect);
|
||||
msg << qint32(1); // 1 range
|
||||
msg << mi << mi; // topLeft, bottomRight
|
||||
msg << (QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current);
|
||||
GammaRay::Endpoint::send(msg);
|
||||
|
||||
// Store path info for diagnostics
|
||||
m_lastSelectPath = QStringLiteral("len=%1 %2").arg(pathLen).arg(pathStr);
|
||||
}
|
||||
}
|
||||
// Select the node via client-side selection model. This sends a
|
||||
// SelectionModelSelect message to the server, which processes it and
|
||||
// triggers sgSelectionChanged -> PropertyController::setObject(),
|
||||
// populating the sub-models with the selected node's data.
|
||||
if (selModel && idx.isValid())
|
||||
selModel->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current);
|
||||
|
||||
// Wait for the selection to be processed and sub-model data to arrive.
|
||||
settle(loop, 2000);
|
||||
|
||||
// Re-send via direct message to ensure it sticks.
|
||||
{
|
||||
const auto selAddr = GammaRay::Endpoint::instance()->objectAddress(
|
||||
QStringLiteral("%1.selection").arg(QString::fromUtf8(kQuickSceneGraphModel)));
|
||||
if (selAddr != GammaRay::Protocol::InvalidObjectAddress) {
|
||||
GammaRay::Message msg(selAddr, GammaRay::Protocol::SelectionModelSelect);
|
||||
const auto mi = GammaRay::Protocol::fromQModelIndex(idx);
|
||||
msg << qint32(1);
|
||||
msg << mi << mi;
|
||||
msg << (QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current);
|
||||
GammaRay::Endpoint::send(msg);
|
||||
}
|
||||
}
|
||||
settle(loop, 2000);
|
||||
m_selectedAddress = address;
|
||||
|
||||
// Prime sub-models to fetch their row/column counts and cell data.
|
||||
if (vModel) { vModel->rowCount(); primeAndWait(vModel, 4, 300, 500, 1500); }
|
||||
@@ -572,64 +530,20 @@ QString SceneGraphTools::selectScenegraphNode(const QString &address) const
|
||||
type = sgModel->data(typeIdx, Qt::DisplayRole).toString();
|
||||
}
|
||||
|
||||
// Check selection state and sub-model row counts
|
||||
auto *sgModel2 = m_session->model(QString::fromUtf8(kQuickSceneGraphModel));
|
||||
auto *selModel = m_session->selectionModel(sgModel2);
|
||||
// Check sub-model row counts
|
||||
auto *vModel = m_session->model(QStringLiteral("%1.sgGeometryVertexModel").arg(QString::fromUtf8(kBaseName)));
|
||||
auto *aModel = m_session->model(QStringLiteral("%1.sgGeometryAdjacencyModel").arg(QString::fromUtf8(kBaseName)));
|
||||
auto *sModel = m_session->model(QStringLiteral("%1.shaderModel").arg(QString::fromUtf8(kBaseName)));
|
||||
auto *mModel = m_session->model(QStringLiteral("%1.materialPropertyModel").arg(QString::fromUtf8(kBaseName)));
|
||||
|
||||
// Diagnostic: check if models are registered on the probe (have valid addresses)
|
||||
auto *ep = GammaRay::Endpoint::instance();
|
||||
const auto vAddr = ep ? ep->objectAddress(QStringLiteral("%1.sgGeometryVertexModel").arg(QString::fromUtf8(kBaseName))) : 0;
|
||||
const auto aAddr = ep ? ep->objectAddress(QStringLiteral("%1.sgGeometryAdjacencyModel").arg(QString::fromUtf8(kBaseName))) : 0;
|
||||
const auto sAddr = ep ? ep->objectAddress(QStringLiteral("%1.shaderModel").arg(QString::fromUtf8(kBaseName))) : 0;
|
||||
const auto mAddr = ep ? ep->objectAddress(QStringLiteral("%1.materialPropertyModel").arg(QString::fromUtf8(kBaseName))) : 0;
|
||||
const auto selAddr = ep ? ep->objectAddress(QStringLiteral("%1.selection").arg(QString::fromUtf8(kQuickSceneGraphModel))) : 0;
|
||||
// Also check the wrong name (in case source model name mismatch)
|
||||
const auto dotSelAddr = ep ? ep->objectAddress(QStringLiteral(".selection")) : 0;
|
||||
|
||||
// Check what the selected index looks like
|
||||
QString selIdxInfo;
|
||||
if (selModel && selModel->hasSelection()) {
|
||||
auto rows = selModel->selectedRows();
|
||||
if (rows.isEmpty()) {
|
||||
selIdxInfo = QStringLiteral("hasSelection=true but selectedRows() empty");
|
||||
} else {
|
||||
// Walk the parent chain to get the full path
|
||||
QStringList pathParts;
|
||||
QModelIndex cur = rows.first();
|
||||
while (cur.isValid()) {
|
||||
QString addr = cur.data(Qt::DisplayRole).toString();
|
||||
pathParts.prepend(QStringLiteral("r%1:%2").arg(cur.row()).arg(addr));
|
||||
cur = cur.parent();
|
||||
}
|
||||
selIdxInfo = pathParts.join(" -> ");
|
||||
}
|
||||
} else {
|
||||
selIdxInfo = QStringLiteral("no selection");
|
||||
}
|
||||
|
||||
QJsonObject out = {
|
||||
{ QStringLiteral("address"), address },
|
||||
{ QStringLiteral("type"), type },
|
||||
{ QStringLiteral("hasGeometry"), vModel && vModel->rowCount() > 0 },
|
||||
{ QStringLiteral("hasMaterial"), sModel && sModel->rowCount() > 0 },
|
||||
{ QStringLiteral("_diag_epConnected"), ep ? ep->isConnected() : false },
|
||||
{ QStringLiteral("_diag_selModelAddr"), qint64(selAddr) },
|
||||
{ QStringLiteral("_diag_selHasSelection"), selModel ? selModel->hasSelection() : false },
|
||||
{ QStringLiteral("_diag_selIdxInfo"), selIdxInfo },
|
||||
{ QStringLiteral("_diag_vertexModelAddr"), qint64(vAddr) },
|
||||
{ QStringLiteral("_diag_adjacencyModelAddr"), qint64(aAddr) },
|
||||
{ QStringLiteral("_diag_shaderModelAddr"), qint64(sAddr) },
|
||||
{ QStringLiteral("_diag_materialPropModelAddr"), qint64(mAddr) },
|
||||
{ QStringLiteral("_diag_vertexRowCount"), vModel ? vModel->rowCount() : -1 },
|
||||
{ QStringLiteral("_diag_adjacencyRowCount"), aModel ? aModel->rowCount() : -1 },
|
||||
{ QStringLiteral("_diag_shaderRowCount"), sModel ? sModel->rowCount() : -1 },
|
||||
{ QStringLiteral("_diag_materialPropRowCount"), mModel ? mModel->rowCount() : -1 },
|
||||
{ QStringLiteral("_diag_vertexColCount"), vModel ? vModel->columnCount() : -1 },
|
||||
{ QStringLiteral("_diag_vertexData00"), vModel && vModel->rowCount() > 0 && vModel->columnCount() > 0 ? vModel->data(vModel->index(0, 0)).toString() : QString() },
|
||||
{ QStringLiteral("vertexCount"), vModel ? vModel->rowCount() : 0 },
|
||||
{ QStringLiteral("shaderCount"), sModel ? sModel->rowCount() : 0 },
|
||||
{ QStringLiteral("propertyCount"), mModel ? mModel->rowCount() : 0 },
|
||||
};
|
||||
return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
@@ -818,8 +732,6 @@ QString SceneGraphTools::getMaterialProperties(const QString &address) const
|
||||
return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
#endif // disabled until selection-sync bug is fixed
|
||||
|
||||
// --- Rendering visualization ---
|
||||
|
||||
QString SceneGraphTools::setRenderMode(const QString &mode) const
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
-----------------------
|
||||
Geometry/material/texture sub-models are populated by the probe-side
|
||||
PropertyController only for the *currently selected* SG node. Selection is
|
||||
driven via ObjectBroker::selectionModel(sgModel) which returns a synced
|
||||
SelectionModelClient. Calling select(index, ClearAndSelect) on it sends the
|
||||
selection to the probe, which calls setObject() on all PropertyController
|
||||
extensions, which in turn populate sgGeometryVertexModel, shaderModel, etc.
|
||||
driven via client-side selModel->select() which sends a SelectionModelSelect
|
||||
message to the probe. The server's translateSelection() resolves the path,
|
||||
QItemSelectionModel::select() fires on the probe side, sgSelectionChanged()
|
||||
triggers PropertyController::setObject() locally, populating sub-models.
|
||||
Tools that need per-node data call ensureNodeSelected(address) first, which
|
||||
finds the node index in the (lazily-fetched) SG tree and selects it.
|
||||
*/
|
||||
@@ -57,33 +57,30 @@ public:
|
||||
|
||||
// --- SceneGraph navigation ---
|
||||
Q_INVOKABLE QString listScenegraphNodes() const;
|
||||
// TODO: re-enable when the selection-sync / sub-model-population bug is fixed
|
||||
// Find a node by address (e.g. "0x56396e6d7400") in the SG tree and select it
|
||||
// via the synced selection model. Returns JSON with the node's type and which
|
||||
// sub-models have data. Must be called before geometry/material tools can
|
||||
// via the probe-side Q_INVOKABLE method. Returns JSON with the node's type and
|
||||
// which sub-models have data. Must be called before geometry/material tools can
|
||||
// return data for that node. Selecting a non-GeometryNode clears the
|
||||
// geometry/material models (they only populate for QSGGeometryNode).
|
||||
// Q_INVOKABLE QString selectScenegraphNode(const QString &address) const;
|
||||
Q_INVOKABLE QString selectScenegraphNode(const QString &address) const;
|
||||
|
||||
// --- Geometry (requires a GeometryNode to be selected) ---
|
||||
// TODO: re-enable when the selection-sync / sub-model-population bug is fixed
|
||||
// Returns vertex data: rows = vertices, columns = attributes.
|
||||
// Each cell is the display string (e.g. "0.5, 0.5, 0, 1"); headers are
|
||||
// attribute type names (PositionAttribute, ColorAttribute, ...).
|
||||
// Also includes isCoordinate per column.
|
||||
// Q_INVOKABLE QString getNodeVertices(const QString &address) const;
|
||||
Q_INVOKABLE QString getNodeVertices(const QString &address) const;
|
||||
// Returns adjacency data: drawing mode + index list.
|
||||
// Q_INVOKABLE QString getNodeAdjacency(const QString &address) const;
|
||||
Q_INVOKABLE QString getNodeAdjacency(const QString &address) const;
|
||||
|
||||
// --- Material/Shader (requires a GeometryNode to be selected) ---
|
||||
// TODO: re-enable when the selection-sync / sub-model-population bug is fixed
|
||||
// List shader stages (Vertex, Fragment, ...) for the selected node's material.
|
||||
// Q_INVOKABLE QString getMaterialShaders(const QString &address) const;
|
||||
Q_INVOKABLE QString getMaterialShaders(const QString &address) const;
|
||||
// Async: request shader source for a row (from getMaterialShaders).
|
||||
// Uses the MaterialExtensionInterface proxy + gotShader signal.
|
||||
// Q_INVOKABLE QString getShaderSource(int row) const;
|
||||
Q_INVOKABLE QString getShaderSource(int row) const;
|
||||
// Material properties (name/value pairs from AggregatedPropertyModel).
|
||||
// Q_INVOKABLE QString getMaterialProperties(const QString &address) const;
|
||||
Q_INVOKABLE QString getMaterialProperties(const QString &address) const;
|
||||
|
||||
// --- Rendering visualization ---
|
||||
// Set custom render mode. mode ∈ {NormalRendering, VisualizeClipping,
|
||||
@@ -95,7 +92,7 @@ public:
|
||||
private:
|
||||
GammaRaySession *m_session;
|
||||
mutable QString m_selectedAddress; // last selected node address (for sub-model tools)
|
||||
mutable QString m_lastSelectPath; // diagnostic: last selection path info
|
||||
|
||||
|
||||
QString ensureSession() const;
|
||||
// Find the QModelIndex for address in the SG tree (prime + search).
|
||||
|
||||
Reference in New Issue
Block a user