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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
"""Win32 window management via ctypes — find, position, and close windows."""
import ctypes
import ctypes.wintypes
import time
user32 = ctypes.windll.user32
# Make DPI-aware so coordinates match physical pixels (screenshots/highlights)
try:
user32.SetProcessDPIAware()
except Exception:
pass
# Constants
SW_RESTORE = 9
SW_SHOWNOACTIVATE = 4
SWP_NOZORDER = 0x0004
SWP_NOACTIVATE = 0x0010
WM_CLOSE = 0x0010
VK_MENU = 0x12 # Alt key
KEYEVENTF_KEYUP = 0x0002
def get_screen_size() -> tuple:
"""Return (width, height) of the primary monitor."""
return user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
def find_windows_by_title(substring: str) -> list:
"""Find all visible windows containing substring in title.
Returns list of (hwnd, title) tuples.
"""
results = []
def callback(hwnd, _lparam):
if user32.IsWindowVisible(hwnd):
length = user32.GetWindowTextLengthW(hwnd)
if length > 0:
buf = ctypes.create_unicode_buffer(length + 1)
user32.GetWindowTextW(hwnd, buf, length + 1)
if substring.lower() in buf.value.lower():
results.append((hwnd, buf.value))
return True
WNDENUMPROC = ctypes.WINFUNCTYPE(
ctypes.c_bool, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM
)
user32.EnumWindows(WNDENUMPROC(callback), 0)
return results
def position_window(hwnd: int, x: int, y: int, w: int, h: int):
"""Move and resize a window."""
user32.ShowWindow(hwnd, SW_RESTORE)
user32.SetWindowPos(hwnd, 0, x, y, w, h, SWP_NOZORDER)
def bring_to_front(hwnd: int):
"""Bring a window to the foreground using the Alt-key trick."""
# Press and release Alt to allow SetForegroundWindow from background
user32.keybd_event(VK_MENU, 0, 0, 0)
user32.keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0)
user32.SetForegroundWindow(hwnd)
def close_window_by_title(substring: str, timeout: float = 5.0) -> bool:
"""Find and close the first window matching the title substring."""
windows = find_windows_by_title(substring)
for hwnd, _title in windows:
user32.PostMessageW(hwnd, WM_CLOSE, 0, 0)
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
time.sleep(0.3)
if not user32.IsWindow(hwnd) or not user32.IsWindowVisible(hwnd):
return True
return False
def position_kicad_left(narration_width: int = 480) -> int | None:
"""Find the active KiCad editor and position it on the left side.
Returns the hwnd if found, None otherwise.
"""
screen_w, screen_h = get_screen_size()
kicad_w = screen_w - narration_width
# Try to find KiCad windows in priority order
for title_part in [
"PCB Editor",
"pcbnew",
"Schematic Editor",
"eeschema",
"Symbol Editor",
"Footprint Editor",
"KiCad",
]:
windows = find_windows_by_title(title_part)
for hwnd, title in windows:
# Skip tiny windows and the narration panel itself
if "Adom" in title and "Tour" in title:
continue
position_window(hwnd, 0, 0, kicad_w, screen_h)
bring_to_front(hwnd)
return hwnd
return None