app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
//! WHICH VERB PUT THAT DIALOG UP? (0.11.1)
//!
//! MEASURED 2026-10-15 on adom_drew2: a SOLIDWORKS File ▸ Open dialog (title
//! "Open ", `#32770`, owned by the SOLIDWORKS main frame, file name empty)
//! appeared between 14:57 and 15:00Z, DURING a bridge battery, with Drew away
//! from the machine — and from then on every `swx_stop` was
//! `stop_blocked_by_dialog`. The census called it `kind:"unknown"`,
//! `bridgeCaused:false`: it could not say where it came from, and so the
//! session the bridge had started could never be ended by the bridge.
//!
//! So every verb is now BRACKETED by a census of SOLIDWORKS' dialogs: one
//! before it runs, one after. A dialog first seen in the after-census, that
//! the before-census (taken after the verb was marked in flight) proved
//! ABSENT, appeared DURING that verb — `appearedDuring`. Sightings persist
//! (data root, `dialog-sightings.json`) with the time the dialog was first
//! seen, the last time it was proved absent, and the user's last input. A
//! dialog first seen outside a bracket stays UNATTRIBUTED: the bridge never
//! claims what it cannot prove.
//!
//! Policy is unchanged — the bridge never answers a user's dialog — with ONE
//! carve-out: the standard Windows FILE dialog, with an EMPTY file name, that
//! the bridge PROVABLY provoked (appeared during a bridge verb, no user input
//! since that verb began). Cancelling that is a no-op the bridge owes the
//! user; `swx_dismiss_blocking_dialogs` may do it, ledgered with the
//! attribution. Anything with text typed into it is the user's.
use serde_json::{json, Value};
use std::sync::Mutex;
/// One verb in flight: what, and since when (ms since the epoch).
#[derive(Clone, Debug, PartialEq)]
pub struct InFlight {
pub id: u64,
pub verb: String,
pub started_ms: u64,
}
static IN_FLIGHT: Mutex<Vec<InFlight>> = Mutex::new(Vec::new());
static NEXT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
static FILE_LOCK: Mutex<()> = Mutex::new(());
fn now_ms() -> u64 {
crate::launchos::epoch_ms() as u64
}
fn sightings_path() -> std::path::PathBuf {
crate::scratch::root().join("dialog-sightings.json")
}
/// The persisted sightings (hwnd → what is known about its appearance).
pub fn sightings() -> Vec<Value> {
std::fs::read_to_string(sightings_path()).ok().and_then(|t| serde_json::from_str::<Vec<Value>>(&t).ok()).unwrap_or_default()
}
fn save(v: &[Value]) {
let _ = std::fs::write(sightings_path(), serde_json::to_string(v).unwrap_or_else(|_| "[]".into()));
}
/// The dialogs SOLIDWORKS is showing now: every top-level window of its
/// process(es) and their CEF children that is a `#32770`, or is a visible,
/// non-tool window OWNED by another SOLIDWORKS window. `None` = could not look.
/// Deterministic: the native window list, every time — never ab's.
pub fn census_windows(os: &dyn crate::launchos::LaunchOs) -> Option<(Vec<crate::launchos::WindowInfo>, Vec<u32>)> {
let procs = os.processes().ok()?;
let sw: Vec<&crate::launchos::ProcInfo> = procs.iter().filter(|p| p.is("SLDWORKS.exe")).collect();
let mut pids: Vec<u32> = Vec::new();
for p in &sw {
pids.push(p.pid);
pids.extend(procs.iter().filter(|c| c.is("swCefSubProc.exe") && c.ppid == Some(p.pid)).map(|c| c.pid));
}
let mut all = Vec::new();
for pid in &pids {
all.extend(os.windows_of_pid(*pid).ok()?);
}
let ours: std::collections::HashSet<i64> = all.iter().map(|w| w.hwnd).collect();
let rows = all
.iter()
.filter(|w| {
w.is_dialog()
|| (w.visible
&& w.owner.map(|o| ours.contains(&o)).unwrap_or(false)
&& !w.exstyle.map(|e| e & crate::launchos::WS_EX_TOOLWINDOW != 0).unwrap_or(false))
})
.cloned()
.collect();
Some((rows, sw.iter().map(|p| p.pid).collect()))
}
/// OBSERVE: record every dialog not seen before, attributed to the verbs in
/// flight; strike off sightings whose window is gone. Returns the sightings of
/// the dialogs that are up now.
pub fn observe(os: &dyn crate::launchos::LaunchOs) -> Option<Vec<Value>> {
let (rows, _) = census_windows(os)?;
let _g = FILE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut seen = sightings();
let last_ok = seen.iter().filter_map(|s| s.get("censusMs").and_then(|v| v.as_u64())).max();
let now = now_ms();
let flight: Vec<InFlight> = IN_FLIGHT.lock().unwrap_or_else(|e| e.into_inner()).clone();
let last_input_ms = os.seconds_since_last_input().ok().map(|s| now.saturating_sub(s * 1000));
let up: std::collections::HashSet<i64> = rows.iter().map(|w| w.hwnd).collect();
// Gone windows are forgotten; the census time moves on for the rest.
seen.retain(|s| s.get("hwnd").and_then(|h| h.as_i64()).map(|h| up.contains(&h)).unwrap_or(false));
let mut current = Vec::new();
for w in &rows {
if let Some(s) = seen.iter_mut().find(|s| s["hwnd"] == w.hwnd && s["title"] == w.title.as_str()) {
s["lastSeenMs"] = json!(now);
current.push(s.clone());
continue;
}
// A window under a REUSED hwnd with a new title is a new sighting.
seen.retain(|s| s["hwnd"] != w.hwnd);
let s = json!({
"hwnd": w.hwnd,
"title": w.title,
"className": w.class_name,
"owner": w.owner,
"firstSeenMs": now,
"lastSeenMs": now,
// The last census that could see SOLIDWORKS' windows and did not
// contain this one: it appeared after that.
"absentAtMs": last_ok,
"appearedDuring": flight.iter().map(|f| json!({"verb": f.verb, "startedMs": f.started_ms})).collect::<Vec<_>>(),
"lastInputMs": last_input_ms,
});
current.push(s.clone());
seen.push(s);
}
// Every sighting carries the time of the latest census, so the next new
// dialog knows when it was last proved absent.
let mut out = seen;
out.push(json!({"hwnd": Value::Null, "censusMs": now}));
out.retain(|s| !s["hwnd"].is_null() || s.get("censusMs").and_then(|v| v.as_u64()) == Some(now));
save(&out);
Some(current)
}
/// IS THIS SIGHTING PROVABLY THE BRIDGE'S? It appeared during a bridge verb —
/// proved ABSENT at or after that verb began, first seen while it was in
/// flight — and the user gave no input from that verb's start until `now`
/// (`last_input_now_ms`: the latest input as read now). Returns the verb.
pub fn bridge_caused(s: &Value, last_input_now_ms: Option<u64>) -> Option<String> {
let absent = s.get("absentAtMs").and_then(|v| v.as_u64())?;
let input_then = s.get("lastInputMs").and_then(|v| v.as_u64())?;
let input_now = last_input_now_ms?;
// The most recent verb first: a retry in flight beside an earlier route's
// grace window is the one that raised it.
s.get("appearedDuring")?.as_array()?.iter().rev().find_map(|d| {
let started = d.get("startedMs").and_then(|v| v.as_u64())?;
let verb = d.get("verb").and_then(|v| v.as_str())?;
(started <= absent && input_then < started && input_now < started).then(|| verb.to_string())
})
}
/// What is known about one dialog's appearance, for a report.
pub fn attribution(hwnd: i64, os: &dyn crate::launchos::LaunchOs) -> Value {
let Some(s) = sightings().into_iter().find(|s| s["hwnd"] == hwnd) else {
return json!({"known": false, "why": "no census saw this window appear: it was up before the bridge first looked, so where it came from cannot be said"});
};
let now = now_ms();
let input_now = os.seconds_since_last_input().ok().map(|x| now.saturating_sub(x * 1000));
let during = s.get("appearedDuring").and_then(|d| d.as_array()).cloned().unwrap_or_default();
let caused = bridge_caused(&s, input_now);
json!({
"known": true,
"firstSeenMs": s["firstSeenMs"],
"ageSeconds": s.get("firstSeenMs").and_then(|v| v.as_u64()).map(|f| now.saturating_sub(f) / 1000),
"appearedDuring": if during.is_empty() { Value::Null } else { json!(during.iter().filter_map(|d| d.get("verb").cloned()).collect::<Vec<_>>()) },
"appearedBetweenVerbs": during.is_empty(),
"provedAbsentAtMs": s["absentAtMs"],
"bridgeCaused": caused.is_some(),
"bridgeCausedBy": caused,
"userInputSinceItAppeared": match (input_now, s.get("firstSeenMs").and_then(|v| v.as_u64())) {
(Some(i), Some(f)) => json!(i >= f),
_ => Value::Null,
},
})
}
/// A verb begins: marked in flight FIRST, then the census — so the census
/// that proves a dialog absent is one taken after the verb began.
pub fn begin(verb: &str) -> u64 {
let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
IN_FLIGHT.lock().unwrap_or_else(|e| e.into_inner()).push(InFlight { id, verb: verb.to_string(), started_ms: now_ms() });
let os = crate::launchos::os();
let _ = observe(os.as_ref());
id
}
/// A verb ends: the census FIRST (a dialog it raised is attributed to it),
/// then it leaves the in-flight list. New dialogs are ledgered.
pub fn end(id: u64) -> Vec<Value> {
let os = crate::launchos::os();
let before: std::collections::HashSet<i64> = sightings().iter().filter_map(|s| s.get("hwnd").and_then(|h| h.as_i64())).collect();
let now = observe(os.as_ref()).unwrap_or_default();
IN_FLIGHT.lock().unwrap_or_else(|e| e.into_inner()).retain(|f| f.id != id);
let fresh: Vec<Value> = now.into_iter().filter(|s| s.get("hwnd").and_then(|h| h.as_i64()).map(|h| !before.contains(&h)).unwrap_or(false)).collect();
for s in &fresh {
crate::ledger::record("dialog_appeared", json!({"hwnd": s["hwnd"], "title": s["title"], "className": s["className"],
"appearedDuring": s["appearedDuring"], "owner": s["owner"]}));
}
fresh
}
/// Cancel every dialog that is up, provably raised by the LAUNCH supervisor
/// (`launch.invoke` / `launch.retry`), and is the standard file dialog with an
/// empty name — what a re-invoke at a starting SOLIDWORKS raises (measured
/// 2026-10-15). Ledgered; a launch never leaves the session blocked by its own
/// Open dialog. Everything else is left alone.
pub fn cancel_launch_file_dialogs(os: &dyn crate::launchos::LaunchOs) -> Vec<Value> {
let mut out = Vec::new();
let Some(up) = observe(os) else { return out };
for s in up {
let Some(h) = s.get("hwnd").and_then(|h| h.as_i64()) else { continue };
if let Ok((verb, sighting)) = may_cancel(h, os) {
if !verb.starts_with("launch.") {
continue;
}
let r = os.cancel_dialog(h);
crate::ledger::record(
"dialog_cancelled",
json!({"hwnd": h, "title": sighting["title"], "raisedDuring": verb, "by": "the launch supervisor",
"sighting": sighting, "ok": r.is_ok(), "error": r.as_ref().err()}),
);
out.push(json!({"hwnd": h, "title": sighting["title"], "raisedDuring": verb, "ok": r.is_ok()}));
}
}
out
}
/// A launch route, bracketed like a verb: in flight from its invoke until
/// `grace` after it ends, watched (and its own empty File ▸ Open cancelled)
/// all that time on a thread of its own.
pub struct LaunchBracket {
id: u64,
grace: std::time::Duration,
}
impl LaunchBracket {
pub fn begin(label: &str, grace: std::time::Duration) -> LaunchBracket {
LaunchBracket { id: begin(label), grace }
}
}
impl Drop for LaunchBracket {
fn drop(&mut self) {
let (id, grace) = (self.id, self.grace);
std::thread::spawn(move || {
let os = crate::launchos::os();
let until = std::time::Instant::now() + grace;
loop {
let _ = cancel_launch_file_dialogs(os.as_ref());
if std::time::Instant::now() >= until {
break;
}
std::thread::sleep(std::time::Duration::from_millis(500));
}
end(id);
});
}
}
/// Does `dispatch` bracket this verb? Every known verb except the ones that
/// only read the bridge's own state (they never touch SOLIDWORKS).
pub fn brackets(verb: &str) -> bool {
!matches!(verb, "swx_progress" | "swx_action_ledger" | "swx_verb_times" | "swx_describe")
}
/// The standard Windows FILE dialog with an empty name, provoked by the bridge:
/// the one dialog the bridge may cancel. `Err` says why not.
pub fn may_cancel(hwnd: i64, os: &dyn crate::launchos::LaunchOs) -> Result<(String, Value), String> {
let s = sightings().into_iter().find(|s| s["hwnd"] == hwnd).ok_or("no census saw it appear: its origin is unknown")?;
let now = now_ms();
let input_now = os.seconds_since_last_input().ok().map(|x| now.saturating_sub(x * 1000));
let verb = bridge_caused(&s, input_now).ok_or(
"it is not PROVABLY the bridge's: it did not appear during a bridge verb, or the user has given input since that verb began",
)?;
let fd = os.file_dialog(hwnd).map_err(|e| format!("whether it is a file dialog could not be read ({e})"))?;
if !fd.standard {
return Err("it is not the standard Windows file dialog".into());
}
match fd.file_name.as_deref() {
None => return Err("its file-name box could not be read".into()),
Some(t) if !t.trim().is_empty() => return Err(format!("its file-name box holds text ({} characters): somebody typed into it", t.chars().count())),
_ => {}
}
Ok((verb, s))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_dialog_proved_absent_after_a_verb_began_with_no_input_since_is_the_bridges() {
let s = json!({"absentAtMs": 1000, "lastInputMs": 500, "appearedDuring": [{"verb": "swx_start", "startedMs": 900}]});
assert_eq!(bridge_caused(&s, Some(500)), Some("swx_start".to_string()));
// Input after the verb began: the user may have opened it.
assert_eq!(bridge_caused(&s, Some(950)), None, "input since the verb began");
assert_eq!(bridge_caused(&json!({"absentAtMs": 1000, "lastInputMs": 950, "appearedDuring": [{"verb": "v", "startedMs": 900}]}), Some(950)), None);
// Last proved absent BEFORE the verb began: it may predate the verb.
assert_eq!(bridge_caused(&json!({"absentAtMs": 800, "lastInputMs": 500, "appearedDuring": [{"verb": "v", "startedMs": 900}]}), Some(500)), None);
// Appeared between verbs, or never proved absent, or input unreadable.
assert_eq!(bridge_caused(&json!({"absentAtMs": 1000, "lastInputMs": 500, "appearedDuring": []}), Some(500)), None);
assert_eq!(bridge_caused(&json!({"absentAtMs": null, "lastInputMs": 500, "appearedDuring": [{"verb": "v", "startedMs": 900}]}), Some(500)), None);
assert_eq!(bridge_caused(&s, None), None, "an unreadable input time proves nothing");
}
}