app
Adom Desktop - KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge — multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
param(
[Parameter(Mandatory=$true)]
[long]$SymEditorHwnd,
[Parameter(Mandatory=$true)]
[long]$SearchEditHwnd,
[Parameter(Mandatory=$true)]
[string]$SymbolName
)
# ============================================================================
# BACKGROUND-ONLY symbol navigation.
#
# The previous version did ALL of this just to type one symbol name:
# * an Alt-key trick to steal foreground permission,
# * ShowWindow(SW_RESTORE) + SetForegroundWindow -> took over the screen,
# * SetCursorPos + mouse_event -> MOVED THE USER'S MOUSE,
# * SendKeys::SendWait -> typed into whatever had focus.
# If the user was working, we typed on top of them.
# (John, 2026-07-20: "when you take over my screen to type into the kicad window
# it's crazy rude" — he was running five other projects at the time.)
#
# Everything below is WINDOW-targeted instead of FOCUS-targeted, so it works
# while KiCad sits behind other apps and the user keeps typing elsewhere:
# 1. UIA ValuePattern.SetValue() — set the search box text with no focus.
# 2. WM_SETTEXT via SendMessage — fallback for controls without ValuePattern.
# 3. PostMessage(WM_KEYDOWN/UP) — keys delivered to a specific hwnd, never to
# the shared foreground input queue.
# No keybd_event, no SendKeys, no mouse, no SetForegroundWindow. Ever.
# ============================================================================
Add-Type -AssemblyName UIAutomationClient
Add-Type -AssemblyName UIAutomationTypes
Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class Win32Nav {
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);
[DllImport("user32.dll")]
public static extern bool PostMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowTextLengthW(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
"@
$WM_SETTEXT = 0x000C
$WM_KEYDOWN = 0x0100
$WM_KEYUP = 0x0101
$VK_RETURN = 0x0D
$hwnd = [IntPtr]$SymEditorHwnd
$editHwnd = [IntPtr]$SearchEditHwnd
$method = "none"
# SW_SHOWNOACTIVATE (4): un-minimise so the window can be screenshotted, but do
# NOT activate it. This is the strongest thing the bridge is allowed to do.
[Win32Nav]::ShowWindow($hwnd, 4) | Out-Null
# --- 1. UIA ValuePattern: set the text on the control itself, no focus -------
try {
$el = [System.Windows.Automation.AutomationElement]::FromHandle($editHwnd)
if ($el -ne $null) {
$vp = $null
if ($el.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$vp)) {
$vp.SetValue($SymbolName)
$method = "uia-valuepattern"
}
}
} catch { }
# --- 2. Fallback: WM_SETTEXT straight to the edit control -------------------
if ($method -eq "none") {
try {
[Win32Nav]::SendMessageW($editHwnd, $WM_SETTEXT, [IntPtr]::Zero, $SymbolName) | Out-Null
$method = "wm_settext"
} catch { }
}
if ($method -eq "none") {
Write-Output "ERROR:Could not set search text without focus (no ValuePattern, WM_SETTEXT failed)"
exit 1
}
Start-Sleep -Milliseconds 250
# --- 3. Commit the filter: Enter POSTED to the edit control ------------------
# PostMessage targets that control's own message queue, so it needs no focus and
# cannot land in another app if the user switches windows mid-call.
[Win32Nav]::PostMessageW($editHwnd, $WM_KEYDOWN, [IntPtr]$VK_RETURN, [IntPtr]::Zero) | Out-Null
[Win32Nav]::PostMessageW($editHwnd, $WM_KEYUP, [IntPtr]$VK_RETURN, [IntPtr]::Zero) | Out-Null
Start-Sleep -Milliseconds 900
# NOTE: loading the highlighted symbol into the canvas (the old Ctrl+Shift+E) is
# deliberately NOT done here. A ctrl chord can't be delivered reliably by
# PostMessage without focus, and the caller has a better fully-background route:
# the embedded-plugin WM_COMMAND pathway. This script only does the focus-free part.
$len = [Win32Nav]::GetWindowTextLengthW($hwnd)
if ($len -gt 0) {
$sb = New-Object System.Text.StringBuilder($len + 1)
[Win32Nav]::GetWindowTextW($hwnd, $sb, $len + 1) | Out-Null
$title = $sb.ToString()
if ($title -match [regex]::Escape($SymbolName)) {
Write-Output "OK:$title|method=$method"
} elseif ($title -notmatch "no symbol loaded") {
Write-Output "PARTIAL:$title|method=$method"
} else {
Write-Output "NOLOAD:$title|method=$method"
}
} else {
Write-Output "PARTIAL:|method=$method"
}