Same fixes as the previous commits (run no egui pass at all when
nothing will be shown), but the mechanisms are shared instead of
duplicated:
* `Context::run_frame(input, show_ui, f)` is the one entry point for
integrations: one `FramePhase::Logic` (always, outside any pass),
then one `FramePhase::Ui` per pass (none when `show_ui` is false).
`Context::run_logic` is now a thin wrapper around it, and the
logic-outside-of-pass sequencing lives in egui, not in each backend.
* egui itself buffers the input of pass-less frames
(`ViewportState::pending_raw_input`) and prepends it to the next
pass. This replaces `EpiIntegration::pending_raw_input` and the web
backend's `input.raw.append`, so an integration cannot lose input.
* `FullOutput` is now `{ platform_output, viewport_commands,
pass_output: Option<PassOutput> }`, and `LogicOutput` is gone.
One-shot viewport commands (imperative) are separated from
`ViewportOutput` (which viewports should exist - declarative), so
each backend has exactly one command-handling path, shared by frames
with and without a pass. `pass_output: None` encodes "no pass ran:
paint nothing, leave the viewports alone" in the type.
* The glow/wgpu `!show_ui` early-return blocks are gone: a hidden root
viewport flows through the same tail as a visible one, with the
paint and viewport-structure steps gated on `pass_output`.
Behavioral fixes that fall out:
* `App::logic` now sees the current window state in visible frames
too (it was one frame stale outside the hidden path).
* Auto-save keeps working while a window is minimized or occluded.
* Commands sent to a freshly created viewport apply in the same frame.
* The Wayland resize workaround now also covers commands from
pass-less frames.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
egui_inspection
Inspection for egui apps.
egui_inspection defines a wire protocol and an [egui::Plugin] (InspectionPlugin) that
serves it. An external inspector — such as the
egui_mcp MCP server — connects and can:
- read the app's AccessKit tree (
GetTree), - inject input events (
HandleEvents— clicks, typing, scrolling, …), - capture a screenshot on request (
Screenshot), - resize the window (
Resize).
The protocol is strictly request → response, which maps cleanly onto both a TCP socket and a unary RPC (so the same machinery can be tunnelled over another transport).
Screenshots need a visible window. Reading the tree and injecting input work even while the app is in the background, but capturing a screenshot requires a rendered frame — which the OS won't produce for a fully-occluded or minimized window (notably on macOS, where the GPU surface isn't available). Bring the window to the foreground to capture it; the
Screenshotrequest times out otherwise.
What it's for
egui_inspection is the shared foundation for tools that observe or drive an egui app from
the outside. Anything that speaks the protocol (over TCP, or another transport) can be a
consumer:
egui_mcp— an MCP server that exposes the app to AI agents and other tooling: query the widget tree, click / type / scroll, take screenshots.- An egui inspector GUI (planned) — a visual debugger that connects to a running app to browse its widget tree and drive it interactively.
- Test inspection & frame streaming (planned) — attach to
egui_kittesttests, and stream frames for live mirroring of an app's window.
Enabling it in an eframe app
Enable eframe's inspection feature, then set the EGUI_INSPECTION env var at runtime. It's
either truthy, falsy, or a bind address:
EGUI_INSPECTION=1 cargo run --features inspection # binds 127.0.0.1:5719
EGUI_INSPECTION=0.0.0.0:5719 cargo run --features inspection # reachable across devices
When the variable is unset or falsy (0 / false), inspection is completely off
(production-safe).
⚠️ Binding a non-loopback address exposes full control of the app — and its screenshots — to anyone who can reach the port, with no authentication. A warning is logged when you do so. Prefer loopback + an SSH tunnel for remote debugging.
Using the plugin directly
# let ctx = egui::Context::default();
ctx.add_plugin(egui_inspection::InspectionPlugin::new(Some("my app".to_owned())));
egui_inspection::serve(&ctx, "127.0.0.1:5719").unwrap();