style: apply Black formatting to all Python files
Add [tool.black] config to pyproject.toml and Black hook to .pre-commit-config.yaml (rev 26.3.1), then auto-format all Python source and test files with line-length=100, target-version=py310. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,77 +3,82 @@ KiCAD Process Management Utilities
|
||||
|
||||
Detects if KiCAD is running and provides auto-launch functionality.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
import platform
|
||||
import time
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from pathlib import Path
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
import platform
|
||||
import time
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KiCADProcessManager:
|
||||
"""Manages KiCAD process detection and launching"""
|
||||
|
||||
@staticmethod
|
||||
def _windows_list_processes() -> List[dict]:
|
||||
"""List running processes on Windows using Toolhelp API."""
|
||||
processes: List[dict] = []
|
||||
try:
|
||||
TH32CS_SNAPPROCESS = 0x00000002
|
||||
try:
|
||||
ulong_ptr = wintypes.ULONG_PTR # type: ignore[attr-defined]
|
||||
except AttributeError:
|
||||
ulong_ptr = ctypes.c_ulonglong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_ulong
|
||||
|
||||
class PROCESSENTRY32W(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwSize", wintypes.DWORD),
|
||||
("cntUsage", wintypes.DWORD),
|
||||
("th32ProcessID", wintypes.DWORD),
|
||||
("th32DefaultHeapID", ulong_ptr),
|
||||
("th32ModuleID", wintypes.DWORD),
|
||||
("cntThreads", wintypes.DWORD),
|
||||
("th32ParentProcessID", wintypes.DWORD),
|
||||
("pcPriClassBase", wintypes.LONG),
|
||||
("dwFlags", wintypes.DWORD),
|
||||
("szExeFile", wintypes.WCHAR * wintypes.MAX_PATH),
|
||||
]
|
||||
|
||||
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
||||
Process32FirstW = ctypes.windll.kernel32.Process32FirstW
|
||||
Process32NextW = ctypes.windll.kernel32.Process32NextW
|
||||
CloseHandle = ctypes.windll.kernel32.CloseHandle
|
||||
|
||||
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||||
if snapshot == wintypes.HANDLE(-1).value:
|
||||
return processes
|
||||
|
||||
entry = PROCESSENTRY32W()
|
||||
entry.dwSize = ctypes.sizeof(PROCESSENTRY32W)
|
||||
|
||||
if Process32FirstW(snapshot, ctypes.byref(entry)):
|
||||
while True:
|
||||
processes.append({
|
||||
"pid": str(entry.th32ProcessID),
|
||||
"name": entry.szExeFile,
|
||||
"command": entry.szExeFile
|
||||
})
|
||||
if not Process32NextW(snapshot, ctypes.byref(entry)):
|
||||
break
|
||||
|
||||
CloseHandle(snapshot)
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing Windows processes: {e}")
|
||||
|
||||
return processes
|
||||
|
||||
@staticmethod
|
||||
def is_running() -> bool:
|
||||
class KiCADProcessManager:
|
||||
"""Manages KiCAD process detection and launching"""
|
||||
|
||||
@staticmethod
|
||||
def _windows_list_processes() -> List[dict]:
|
||||
"""List running processes on Windows using Toolhelp API."""
|
||||
processes: List[dict] = []
|
||||
try:
|
||||
TH32CS_SNAPPROCESS = 0x00000002
|
||||
try:
|
||||
ulong_ptr = wintypes.ULONG_PTR # type: ignore[attr-defined]
|
||||
except AttributeError:
|
||||
ulong_ptr = (
|
||||
ctypes.c_ulonglong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_ulong
|
||||
)
|
||||
|
||||
class PROCESSENTRY32W(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwSize", wintypes.DWORD),
|
||||
("cntUsage", wintypes.DWORD),
|
||||
("th32ProcessID", wintypes.DWORD),
|
||||
("th32DefaultHeapID", ulong_ptr),
|
||||
("th32ModuleID", wintypes.DWORD),
|
||||
("cntThreads", wintypes.DWORD),
|
||||
("th32ParentProcessID", wintypes.DWORD),
|
||||
("pcPriClassBase", wintypes.LONG),
|
||||
("dwFlags", wintypes.DWORD),
|
||||
("szExeFile", wintypes.WCHAR * wintypes.MAX_PATH),
|
||||
]
|
||||
|
||||
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
||||
Process32FirstW = ctypes.windll.kernel32.Process32FirstW
|
||||
Process32NextW = ctypes.windll.kernel32.Process32NextW
|
||||
CloseHandle = ctypes.windll.kernel32.CloseHandle
|
||||
|
||||
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||||
if snapshot == wintypes.HANDLE(-1).value:
|
||||
return processes
|
||||
|
||||
entry = PROCESSENTRY32W()
|
||||
entry.dwSize = ctypes.sizeof(PROCESSENTRY32W)
|
||||
|
||||
if Process32FirstW(snapshot, ctypes.byref(entry)):
|
||||
while True:
|
||||
processes.append(
|
||||
{
|
||||
"pid": str(entry.th32ProcessID),
|
||||
"name": entry.szExeFile,
|
||||
"command": entry.szExeFile,
|
||||
}
|
||||
)
|
||||
if not Process32NextW(snapshot, ctypes.byref(entry)):
|
||||
break
|
||||
|
||||
CloseHandle(snapshot)
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing Windows processes: {e}")
|
||||
|
||||
return processes
|
||||
|
||||
@staticmethod
|
||||
def is_running() -> bool:
|
||||
"""
|
||||
Check if KiCAD is currently running
|
||||
|
||||
@@ -87,27 +92,21 @@ class KiCADProcessManager:
|
||||
# Check for actual pcbnew/kicad binaries (not python scripts)
|
||||
# Use exact process name matching to avoid matching our own kicad_interface.py
|
||||
result = subprocess.run(
|
||||
["pgrep", "-x", "pcbnew|kicad"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["pgrep", "-x", "pcbnew|kicad"], capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
# Also check with -f for full path matching, but exclude our script
|
||||
result = subprocess.run(
|
||||
["pgrep", "-f", "/pcbnew|/kicad"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["pgrep", "-f", "/pcbnew|/kicad"], capture_output=True, text=True
|
||||
)
|
||||
# Double-check it's not our own process
|
||||
if result.returncode == 0:
|
||||
pids = result.stdout.strip().split('\n')
|
||||
pids = result.stdout.strip().split("\n")
|
||||
for pid in pids:
|
||||
try:
|
||||
cmdline = subprocess.run(
|
||||
["ps", "-p", pid, "-o", "command="],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["ps", "-p", pid, "-o", "command="], capture_output=True, text=True
|
||||
)
|
||||
if "kicad_interface.py" not in cmdline.stdout:
|
||||
return True
|
||||
@@ -117,18 +116,16 @@ class KiCADProcessManager:
|
||||
|
||||
elif system == "Darwin": # macOS
|
||||
result = subprocess.run(
|
||||
["pgrep", "-f", "KiCad|pcbnew"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["pgrep", "-f", "KiCad|pcbnew"], capture_output=True, text=True
|
||||
)
|
||||
return result.returncode == 0
|
||||
|
||||
elif system == "Windows":
|
||||
processes = KiCADProcessManager._windows_list_processes()
|
||||
for proc in processes:
|
||||
name = (proc.get("name") or "").lower()
|
||||
if name in ("pcbnew.exe", "kicad.exe"):
|
||||
return True
|
||||
elif system == "Windows":
|
||||
processes = KiCADProcessManager._windows_list_processes()
|
||||
for proc in processes:
|
||||
name = (proc.get("name") or "").lower()
|
||||
if name in ("pcbnew.exe", "kicad.exe"):
|
||||
return True
|
||||
return False
|
||||
|
||||
else:
|
||||
@@ -151,14 +148,14 @@ class KiCADProcessManager:
|
||||
|
||||
# Try to find executable in PATH first
|
||||
for cmd in ["pcbnew", "kicad"]:
|
||||
result = subprocess.run(
|
||||
["which", cmd] if system != "Windows" else ["where", cmd],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="mbcs" if system == "Windows" else None,
|
||||
errors="ignore" if system == "Windows" else None,
|
||||
timeout=5 if system == "Windows" else None
|
||||
)
|
||||
result = subprocess.run(
|
||||
["which", cmd] if system != "Windows" else ["where", cmd],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="mbcs" if system == "Windows" else None,
|
||||
errors="ignore" if system == "Windows" else None,
|
||||
timeout=5 if system == "Windows" else None,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
path = result.stdout.strip().split("\n")[0]
|
||||
logger.info(f"Found KiCAD executable: {path}")
|
||||
@@ -232,7 +229,7 @@ class KiCADProcessManager:
|
||||
cmd,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
else:
|
||||
# Unix: Use nohup or start in background
|
||||
@@ -240,7 +237,7 @@ class KiCADProcessManager:
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True
|
||||
start_new_session=True,
|
||||
)
|
||||
|
||||
# Wait for process to start
|
||||
@@ -275,28 +272,30 @@ class KiCADProcessManager:
|
||||
|
||||
try:
|
||||
if system in ["Linux", "Darwin"]:
|
||||
result = subprocess.run(
|
||||
["ps", "aux"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(["ps", "aux"], capture_output=True, text=True)
|
||||
for line in result.stdout.split("\n"):
|
||||
# Only match actual KiCAD binaries, not our MCP server processes
|
||||
if ("pcbnew" in line.lower() or "kicad" in line.lower()) and "kicad_interface.py" not in line and "grep" not in line:
|
||||
if (
|
||||
("pcbnew" in line.lower() or "kicad" in line.lower())
|
||||
and "kicad_interface.py" not in line
|
||||
and "grep" not in line
|
||||
):
|
||||
# More specific check: must have /pcbnew or /kicad in the path
|
||||
if "/pcbnew" in line or "/kicad" in line or "KiCad.app" in line:
|
||||
parts = line.split()
|
||||
if len(parts) >= 11:
|
||||
processes.append({
|
||||
"pid": parts[1],
|
||||
"name": parts[10],
|
||||
"command": " ".join(parts[10:])
|
||||
})
|
||||
processes.append(
|
||||
{
|
||||
"pid": parts[1],
|
||||
"name": parts[10],
|
||||
"command": " ".join(parts[10:]),
|
||||
}
|
||||
)
|
||||
|
||||
elif system == "Windows":
|
||||
for proc in KiCADProcessManager._windows_list_processes():
|
||||
name = (proc.get("name") or "").lower()
|
||||
if "pcbnew" in name or "kicad" in name:
|
||||
elif system == "Windows":
|
||||
for proc in KiCADProcessManager._windows_list_processes():
|
||||
name = (proc.get("name") or "").lower()
|
||||
if "pcbnew" in name or "kicad" in name:
|
||||
processes.append(proc)
|
||||
|
||||
except Exception as e:
|
||||
@@ -304,6 +303,7 @@ class KiCADProcessManager:
|
||||
|
||||
return processes
|
||||
|
||||
|
||||
def check_and_launch_kicad(project_path: Optional[Path] = None, auto_launch: bool = True) -> dict:
|
||||
"""
|
||||
Check if KiCAD is running and optionally launch it
|
||||
@@ -325,7 +325,7 @@ def check_and_launch_kicad(project_path: Optional[Path] = None, auto_launch: boo
|
||||
"running": True,
|
||||
"launched": False,
|
||||
"processes": processes,
|
||||
"message": "KiCAD is already running"
|
||||
"message": "KiCAD is already running",
|
||||
}
|
||||
|
||||
if not auto_launch:
|
||||
@@ -333,7 +333,7 @@ def check_and_launch_kicad(project_path: Optional[Path] = None, auto_launch: boo
|
||||
"running": False,
|
||||
"launched": False,
|
||||
"processes": [],
|
||||
"message": "KiCAD is not running (auto-launch disabled)"
|
||||
"message": "KiCAD is not running (auto-launch disabled)",
|
||||
}
|
||||
|
||||
# Try to launch
|
||||
@@ -345,5 +345,5 @@ def check_and_launch_kicad(project_path: Optional[Path] = None, auto_launch: boo
|
||||
"launched": success,
|
||||
"processes": manager.get_process_info() if success else [],
|
||||
"message": "KiCAD launched successfully" if success else "Failed to launch KiCAD",
|
||||
"project": str(project_path) if project_path else None
|
||||
"project": str(project_path) if project_path else None,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user