fix(control,mcp): auto-restore minimized window on capture, add switch_document alias, and export schemas

This commit is contained in:
Karim Jerbi committed 2026-10-01 17:06:16 +01:00
1 parent 3075784f63
commit 3c1c1364c6
4 files changed
+81 -8

No files matched your search

+7 -6
View File
@@ -665,7 +665,7 @@ impl OpenCADStudio {
);
}
if self.tabs[self.active_tab].id != id
&& !matches!(op, "activate" | "close" | "entities_copy_to")
&& !matches!(op, "activate" | "switch_document" | "close" | "entities_copy_to")
{
return (
failure(
@@ -743,7 +743,7 @@ impl OpenCADStudio {
let client = req["client_id"].as_str().unwrap_or("default").to_owned();
if tab.active_cmd.is_some()
&& (self.control.owner.as_ref() != Some(&(tab.id, client.clone()))
|| matches!(op, "run" | "start" | "new" | "open" | "activate"))
|| matches!(op, "run" | "start" | "new" | "open" | "activate" | "switch_document"))
&& op != "cancel"
{
return (
@@ -774,7 +774,7 @@ impl OpenCADStudio {
);
}
}
let doc = if matches!(op, "new" | "open" | "activate") {
let doc = if matches!(op, "new" | "open" | "activate" | "switch_document") {
None
} else {
Some(tab.id)
@@ -803,7 +803,7 @@ impl OpenCADStudio {
if self.tabs[self.active_tab].active_cmd.is_some() {
self.control.owner = Some((self.tabs[self.active_tab].id, client));
}
if matches!(op, "new" | "activate")
if matches!(op, "new" | "activate" | "switch_document")
&& self
.control
.pending
@@ -876,7 +876,7 @@ impl OpenCADStudio {
"open" => self.update(Message::OpenExternal(std::path::PathBuf::from(string(
req, "path",
)?))),
"activate" => {
"activate" | "switch_document" => {
let i = self
.tabs
.iter()
@@ -1058,6 +1058,7 @@ impl OpenCADStudio {
.main_window
.ok_or_else(|| failure("gui_required", "Capture requires a GUI window"))?;
let path = string(req, "path")?.to_owned();
crate::sys::restore_window_if_minimized();
// Optional framing / selection adjustments prior to snapshot:
if let Some(handles) = req.get("highlight_handles").and_then(Value::as_array) {
@@ -1169,7 +1170,7 @@ impl OpenCADStudio {
if pending.document_id.is_none()
&& matches!(
pending.request["op"].as_str(),
Some("new" | "open" | "activate")
Some("new" | "open" | "activate" | "switch_document")
)
&& pending.origin_document != self.tabs[self.active_tab].id
{
+30 -2
View File
@@ -52,6 +52,7 @@ const EXECUTE_OPS: &[&str] = &[
"new",
"open",
"activate",
"switch_document",
"run",
"start",
"input",
@@ -93,6 +94,7 @@ const BATCH_STEP_OPS: &[&str] = &[
"new",
"open",
"activate",
"switch_document",
"run",
"start",
"input",
@@ -2815,7 +2817,33 @@ mod tests {
assert!(schema["$defs"]["window"].is_object());
assert!(schema["$defs"]["batch_step"].is_object());
let json_str = schema.to_string();
// Generates cleanly without exponential blowup: compact ~15KB to 30KB
assert!(json_str.len() < 40_000, "schema size is {} bytes", json_str.len());
// Generates cleanly without exponential blowup: compact ~15KB to 45KB
assert!(json_str.len() < 45_000, "schema size is {} bytes", json_str.len());
}
#[test]
fn export_agent_tool_schemas() {
let tools = tool_definitions();
let home = std::env::var("USERPROFILE")
.or_else(|_| std::env::var("HOME"))
.unwrap_or_default();
let base_dir = std::path::PathBuf::from(home)
.join(".gemini")
.join("antigravity")
.join("mcp")
.join("opencadstudio");
if base_dir.exists() {
for tool in tools.as_array().unwrap() {
let name = tool["name"].as_str().unwrap();
let schema = json!({
"name": name,
"description": tool["description"],
"parameters": tool["inputSchema"]
});
let file_path = base_dir.join(format!("{name}.json"));
let pretty = serde_json::to_string_pretty(&schema).unwrap();
let _ = std::fs::write(&file_path, pretty);
}
}
}
}
+12
View File
@@ -116,6 +116,17 @@ pub static ACTIVATE: OpDef = OpDef {
example: r#"{"op":"activate","document_id":1}"#,
};
pub static SWITCH_DOCUMENT: OpDef = OpDef {
name: "switch_document",
doc: "Alias for activate. Switch the active drawing tab to another open document.",
batchable: true,
params: &[
req("document_id", Ty::Int, "Target document ID to activate."),
],
rules: &[],
example: r#"{"op":"switch_document","document_id":1}"#,
};
pub static RUN: OpDef = OpDef {
name: "run",
doc: "Execute a command string with prompt answers separated by spaces (e.g. 'LINE 0,0 10,10').",
@@ -612,6 +623,7 @@ pub static OPS: &[&OpDef] = &[
&NEW,
&OPEN,
&ACTIVATE,
&SWITCH_DOCUMENT,
&RUN,
&START,
&INPUT,
+32
View File
@@ -194,6 +194,38 @@ pub fn reveal_in_file_manager(path: &std::path::Path) -> Result<(), String> {
}
}
/// If the main application window is minimized, restore it so that viewport captures succeed.
#[cfg(target_os = "windows")]
pub fn restore_window_if_minimized() {
use windows_sys::Win32::Foundation::{HWND, LPARAM};
use windows_sys::Win32::System::Threading::GetCurrentProcessId;
use windows_sys::Win32::UI::WindowsAndMessaging::{
EnumWindows, GetWindowThreadProcessId, IsIconic, IsWindowVisible, SetForegroundWindow,
ShowWindow, SW_RESTORE,
};
type BOOL = i32;
unsafe {
let current_pid = GetCurrentProcessId();
unsafe extern "system" fn enum_wnd(hwnd: HWND, lparam: LPARAM) -> BOOL {
let target_pid = lparam as u32;
let mut wnd_pid: u32 = 0;
GetWindowThreadProcessId(hwnd, &mut wnd_pid);
if wnd_pid == target_pid && IsWindowVisible(hwnd) != 0 {
if IsIconic(hwnd) != 0 {
ShowWindow(hwnd, SW_RESTORE);
SetForegroundWindow(hwnd);
}
}
1
}
EnumWindows(Some(enum_wnd), current_pid as LPARAM);
}
}
#[cfg(not(target_os = "windows"))]
pub fn restore_window_if_minimized() {}
/// Copy the rendered web canvas during the frame callback, before the browser
/// clears its drawing buffer. Canvas readback avoids Iced's synchronous GPU map.
#[cfg(target_arch = "wasm32")]