app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
master
Colby Knox
Restore 25 files to their state before PR #61: the wiki's pr revert left them truncated to the diff's context
4f3a0f4
8d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
//! macOS implementation (owner: Kyle). Headless parts are complete: the app bundle's
//! kicad-cli, ~/Library/Preferences/kicad/<ver>, ~/Documents/KiCad/<ver>. Window control
//! on macOS is `open -a`, osascript and the Accessibility API; add them here in phase 3
//! and flip the capability flags.
use std::path::{Path, PathBuf};
use std::process::Command;
use serde_json::{json, Value};
use crate::{install_from_cli, major_minor, Capabilities, KicadInstall, Platform};
pub struct Native;
fn home() -> Option<PathBuf> {
std::env::var("HOME").ok().map(PathBuf::from)
}
impl Platform for Native {
fn capabilities(&self) -> Capabilities {
Capabilities {
os: "macos",
owner: "Kyle (macOS Hydrogen and Adom Bridge)",
kicad_cli: true,
window_control: false,
ui_automation: false,
dialog_sweep: false,
focus_etiquette: false,
ipc_api: false,
silent_install: false,
}
}
fn cli_file_name(&self) -> &'static str {
"kicad-cli"
}
fn find_installs(&self) -> Vec<KicadInstall> {
let mut out = Vec::new();
let mut candidates = vec![PathBuf::from("/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli")];
if let Some(h) = home() {
candidates.push(h.join("Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli"));
}
for (i, c) in candidates.iter().enumerate() {
if let Some(inst) = install_from_cli(c, "", "app bundle", i == 1) {
out.push(inst);
}
}
out
}
fn config_dir(&self, version: &str) -> Option<PathBuf> {
Some(home()?.join("Library/Preferences/kicad").join(major_minor(version)))
}
fn user_dir(&self, version: &str) -> Option<PathBuf> {
Some(home()?.join("Documents/KiCad").join(major_minor(version)))
}
fn quiet_command(&self, cmd: &mut Command) {
cmd.stdin(std::process::Stdio::null());
}
fn config_root(&self) -> Option<PathBuf> {
Some(home()?.join("Library/Preferences/kicad"))
}
fn user_root(&self) -> Option<PathBuf> {
Some(home()?.join("Documents/KiCad"))
}
fn documents_dir(&self) -> Option<PathBuf> {
Some(home()?.join("Documents"))
}
fn process_alive(&self, pid: u32) -> Option<bool> {
let mut cmd = Command::new("kill");
cmd.args(["-0", &pid.to_string()]);
self.quiet_command(&mut cmd);
Some(cmd.output().ok()?.status.success())
}
// ---- Phase 3b: kicad_upgrade. No silent installer path here: KiCad on macOS comes
// from the .dmg on downloads.kicad.org or Homebrew. The verb explains that; these four keep the facts honest.
fn elevation_info(&self) -> Result<Value, String> {
let mut cmd = Command::new("id");
cmd.arg("-u");
self.quiet_command(&mut cmd);
let euid: Option<u32> = cmd.output().ok().filter(|o| o.status.success()).and_then(|o| String::from_utf8_lossy(&o.stdout).trim().parse().ok());
let root = euid.map(|u| u == 0);
Ok(json!({
"platform": "darwin",
"user": std::env::var("USER").or_else(|_| std::env::var("LOGNAME")).unwrap_or_default(),
"euid": euid,
"isAdmin": root,
"uacEnabled": Value::Null,
"consentPrompt": Value::Null,
"canInstallAllUsersSilently": root,
"note": "no UAC on macOS; isAdmin is euid == 0 (a root shell), which a normal Hydrogen session never is",
}))
}
fn installer_kind(&self) -> &'static str {
"dmg"
}
fn installer_download_url(&self, _version: &str) -> Option<String> {
None
}
// ---- Freerouting: the 2.4.1 macOS release is a dmg, which the bridge does not mount
// or drive. Kyle: a zip or a plain app bundle asset would slot in here like linux.rs.
// Until then the user installs Freerouting themselves and sets ADOM_FREEROUTING_EXE.
fn freerouting_bundle(&self) -> Option<(String, u64, &'static str)> {
None
}
fn freerouting_extract(&self, _archive: &Path, _dest: &Path, _unzip: &dyn Fn(&Path, &Path) -> Result<(), String>) -> Result<PathBuf, String> {
Err("Freerouting on macOS ships as a dmg; the bridge does not drive dmg installs (owner: Kyle). Install it yourself and set ADOM_FREEROUTING_EXE.".into())
}
}