fix(eeschema): collab sync of symbol/junction/label moves; bump wxwidgets (toolbar focus)

The changed-path used the virtual SCH_ITEM::Move(), which silently no-ops from the
apply/CallAfter context (asyncify call_indirect mis-dispatch) for every non-wire item —
so moving a symbol synced on the sender but not the peer. Devirtualize Move() with an
explicit class-qualified call (moveItemTo), which is statically bound (a plain call, not
call_indirect) and executes. Verified: a symbol move now propagates. Also bumps wxwidgets
to ea599f7 (toolbar clicks no longer steal canvas keyboard focus).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergő Törcsvári 2026-06-04 15:18:12 +02:00
commit 2d1d7c4681
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
2 changed files with 34 additions and 3 deletions

View file

@ -29,6 +29,7 @@
#include <sch_no_connect.h> #include <sch_no_connect.h>
#include <sch_text.h> #include <sch_text.h>
#include <sch_label.h> #include <sch_label.h>
#include <sch_symbol.h>
#include <sch_shape.h> #include <sch_shape.h>
#include <eda_shape.h> #include <eda_shape.h>
#include <stroke_params.h> #include <stroke_params.h>
@ -329,6 +330,37 @@ SCHEMATIC* ensureBridge()
namespace { namespace {
// Move an item to an absolute position for the `changed` path. SCH_ITEM::Move() is virtual;
// dispatching it through the vtable from the apply/CallAfter context hits the asyncify
// call_indirect mis-dispatch and silently NO-OPS (so symbols/junctions/labels never moved on
// the peer — only SCH_LINE worked, via its direct SetStart/EndPoint path). GetPosition() reads
// fine (it's a plain virtual read; see 0003 / eeschema_collab_asyncify_apply). The fix:
// devirtualize Move() with an explicit class-qualified call, which is statically bound — a
// plain wasm `call`, not an instrumented call_indirect — so it actually executes.
void moveItemTo( SCH_ITEM* aItem, const VECTOR2I& aNewPos )
{
VECTOR2I delta = aNewPos - aItem->GetPosition();
if( delta == VECTOR2I( 0, 0 ) )
return;
switch( aItem->Type() )
{
case SCH_SYMBOL_T: static_cast<SCH_SYMBOL*>( aItem )->SCH_SYMBOL::Move( delta ); break;
case SCH_JUNCTION_T: static_cast<SCH_JUNCTION*>( aItem )->SCH_JUNCTION::Move( delta ); break;
case SCH_NO_CONNECT_T: static_cast<SCH_NO_CONNECT*>( aItem )->SCH_NO_CONNECT::Move( delta ); break;
case SCH_TEXT_T: static_cast<SCH_TEXT*>( aItem )->SCH_TEXT::Move( delta ); break;
case SCH_LABEL_T:
case SCH_GLOBAL_LABEL_T:
case SCH_HIER_LABEL_T:
static_cast<SCH_LABEL_BASE*>( aItem )->SCH_LABEL_BASE::Move( delta );
break;
default:
aItem->Move( delta ); // virtual fallback (may no-op in the apply context)
break;
}
}
// The actual model mutation, via SCH_COMMIT so connectivity/ERC recompute as for a UI // The actual model mutation, via SCH_COMMIT so connectivity/ERC recompute as for a UI
// edit. (Editor write ops like SCH_ITEM::Move are called through invoke_vii, whose // edit. (Editor write ops like SCH_ITEM::Move are called through invoke_vii, whose
// asyncify-instrumented dynCall trampoline traps on a stale type — fixed at the JS shim // asyncify-instrumented dynCall trampoline traps on a stale type — fixed at the JS shim
@ -372,8 +404,7 @@ void doApply( SCH_EDIT_FRAME* aFrame, const json& aDelta )
} }
else if( j.contains( "x" ) && j.contains( "y" ) ) else if( j.contains( "x" ) && j.contains( "y" ) )
{ {
VECTOR2I newPos( j["x"].get<int>(), j["y"].get<int>() ); moveItemTo( item, VECTOR2I( j["x"].get<int>(), j["y"].get<int>() ) );
item->Move( newPos - item->GetPosition() );
} }
staged = true; staged = true;

@ -1 +1 @@
Subproject commit c27fe8bf038154ec055d929bf8c6bfa01c0ef2fb Subproject commit ea599f7f82bd0de597db2b4665802ae3fba02083