The Design tab's MCP socket must not wait for someone to click the tab

Building the Design tab on first use (1750c52211) leaves m_design_panel null
until a human selects the tab. McpControl::handle_on_main refused every verb
while it was null, so start_mcp_control_if_enabled() opened the socket and
then answered "Design panel not ready" to everything — for the whole session
if nobody clicked. That is precisely the headless case the socket exists for:
the click-test rig drives this app over it with no window manager and no user.

MainFrame::ensure_design_panel() now builds the panel on demand and returns
it; the tab activation and the MCP dispatcher both go through it, so there is
one construction site rather than two. Safe to build wx controls there: that
handler is dispatched on the main thread by CallAfter, as its own comment
says.

The startup saving is untouched — a launch that never opens the tab and never
speaks MCP still builds nothing.

Verified: libslic3r_gui builds clean 745/745; fork parity green with snaporca,
which carries the same fix (McpControl.cpp is a byte-identical shared source).
This commit is contained in:
Tommaso Bianchi
2026-08-28 14:11:23 +02:00
parent 149ae6c7fe
commit cdd41e230d
3 changed files with 24 additions and 8 deletions
+5 -2
View File
@@ -1990,9 +1990,12 @@ json action_set_feature_expr(DesignPanel* panel, const json& params)
std::string handle_on_main(const std::string& method, const json& params, const json& id)
{
MainFrame* mf = wxGetApp().mainframe;
if (!mf || !mf->m_design_panel)
// The panel is built on first use, and in a headless session nobody clicks the tab that
// would build it -- so build it here rather than refusing. Safe: this runs on the main
// thread (see the CallAfter that dispatches us).
DesignPanel* panel = mf ? mf->ensure_design_panel() : nullptr;
if (!panel)
return rpc_error(id, -32001, "Design panel not ready");
DesignPanel* panel = mf->m_design_panel;
// Stale-id guard, checked here rather than in each handler.
//