fix: Rust-side window creation + RDP tab switch layout delay
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m51s
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m51s
Tool/help/editor/detach windows: - Moved ALL child window creation from JS-side WebviewWindow to Rust-side WebviewWindowBuilder via new open_child_window command. JS WebviewWindow on macOS WKWebView was creating windows that never fully initialized — the webview content process failed silently. Rust-side creation uses the proper main thread context. - All four call sites (tool, help, editor, detach) now use invoke() - Errors surface as alert() instead of silent failure RDP tab switch: - Immediate force_refresh on tab activation for instant visual feedback - 300ms delayed dimension check (was double-rAF which was too fast) - If dimensions changed, resize + 500ms delayed refresh for clean repaint - Fixes 3/4 resolution rendering after copilot panel toggle Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d462381cce
commit
703ebdd557
@ -14,3 +14,4 @@ pub mod updater;
|
|||||||
pub mod tools_commands_r2;
|
pub mod tools_commands_r2;
|
||||||
pub mod workspace_commands;
|
pub mod workspace_commands;
|
||||||
pub mod docker_commands;
|
pub mod docker_commands;
|
||||||
|
pub mod window_commands;
|
||||||
|
|||||||
24
src-tauri/src/commands/window_commands.rs
Normal file
24
src-tauri/src/commands/window_commands.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use tauri::AppHandle;
|
||||||
|
use tauri::WebviewWindowBuilder;
|
||||||
|
|
||||||
|
/// Open a child window from the Rust side using WebviewWindowBuilder.
|
||||||
|
/// This is more reliable than JS-side WebviewWindow on macOS WKWebView.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn open_child_window(
|
||||||
|
app_handle: AppHandle,
|
||||||
|
label: String,
|
||||||
|
title: String,
|
||||||
|
url: String,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let webview_url = tauri::WebviewUrl::App(url.into());
|
||||||
|
WebviewWindowBuilder::new(&app_handle, &label, webview_url)
|
||||||
|
.title(&title)
|
||||||
|
.inner_size(width, height)
|
||||||
|
.resizable(true)
|
||||||
|
.center()
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to create window '{}': {}", label, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -234,6 +234,7 @@ pub fn run() {
|
|||||||
commands::updater::check_for_updates,
|
commands::updater::check_for_updates,
|
||||||
commands::workspace_commands::save_workspace, commands::workspace_commands::load_workspace,
|
commands::workspace_commands::save_workspace, commands::workspace_commands::load_workspace,
|
||||||
commands::docker_commands::docker_list_containers, commands::docker_commands::docker_list_images, commands::docker_commands::docker_list_volumes, commands::docker_commands::docker_action,
|
commands::docker_commands::docker_list_containers, commands::docker_commands::docker_list_images, commands::docker_commands::docker_list_volumes, commands::docker_commands::docker_action,
|
||||||
|
commands::window_commands::open_child_window,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@ -204,44 +204,45 @@ onBeforeUnmount(() => {
|
|||||||
if (resizeTimeout) { clearTimeout(resizeTimeout); resizeTimeout = null; }
|
if (resizeTimeout) { clearTimeout(resizeTimeout); resizeTimeout = null; }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus canvas, re-check dimensions, and force full frame on tab switch
|
// Focus canvas, re-check dimensions, and force full frame on tab switch.
|
||||||
|
// Uses 300ms delay to let the flex layout fully settle (copilot panel toggle, etc.)
|
||||||
watch(
|
watch(
|
||||||
() => props.isActive,
|
() => props.isActive,
|
||||||
(active) => {
|
(active) => {
|
||||||
if (!active || !canvasRef.value) return;
|
if (!active || !canvasRef.value) return;
|
||||||
|
|
||||||
// Wait for layout to settle after tab becomes visible
|
// Immediate focus so keyboard works right away
|
||||||
requestAnimationFrame(() => {
|
if (keyboardGrabbed.value) canvasRef.value.focus();
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const wrapper = canvasWrapper.value;
|
|
||||||
const canvas = canvasRef.value;
|
|
||||||
if (!wrapper || !canvas) return;
|
|
||||||
|
|
||||||
const { width: cw, height: ch } = wrapper.getBoundingClientRect();
|
// Immediate force refresh to show SOMETHING while we check dimensions
|
||||||
const newW = Math.round(cw) & ~1;
|
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
||||||
const newH = Math.round(ch);
|
|
||||||
|
|
||||||
// If container size differs from canvas resolution, resize the RDP session
|
// Delayed dimension check — layout needs time to settle
|
||||||
if (newW >= 200 && newH >= 200 && (newW !== canvas.width || newH !== canvas.height)) {
|
setTimeout(() => {
|
||||||
invoke("rdp_resize", {
|
const wrapper = canvasWrapper.value;
|
||||||
sessionId: props.sessionId,
|
const canvas = canvasRef.value;
|
||||||
width: newW,
|
if (!wrapper || !canvas) return;
|
||||||
height: newH,
|
|
||||||
}).then(() => {
|
const { width: cw, height: ch } = wrapper.getBoundingClientRect();
|
||||||
|
const newW = Math.round(cw) & ~1;
|
||||||
|
const newH = Math.round(ch);
|
||||||
|
|
||||||
|
if (newW >= 200 && newH >= 200 && (newW !== canvas.width || newH !== canvas.height)) {
|
||||||
|
invoke("rdp_resize", {
|
||||||
|
sessionId: props.sessionId,
|
||||||
|
width: newW,
|
||||||
|
height: newH,
|
||||||
|
}).then(() => {
|
||||||
|
if (canvas) {
|
||||||
canvas.width = newW;
|
canvas.width = newW;
|
||||||
canvas.height = newH;
|
canvas.height = newH;
|
||||||
setTimeout(() => {
|
}
|
||||||
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
setTimeout(() => {
|
||||||
}, 200);
|
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
||||||
}).catch(() => {});
|
}, 500);
|
||||||
} else {
|
}).catch(() => {});
|
||||||
// Same size — just refresh the frame
|
}
|
||||||
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
}, 300);
|
||||||
}
|
|
||||||
|
|
||||||
if (keyboardGrabbed.value) canvas.focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -133,20 +133,14 @@ async function detachTab(): Promise<void> {
|
|||||||
session.active = false;
|
session.active = false;
|
||||||
|
|
||||||
// Open a new Tauri window for this session
|
// Open a new Tauri window for this session
|
||||||
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
try {
|
||||||
const label = `detached-${session.id.substring(0, 8)}-${Date.now()}`;
|
await invoke("open_child_window", {
|
||||||
const wv = new WebviewWindow(label, {
|
label: `detached-${session.id.substring(0, 8)}-${Date.now()}`,
|
||||||
title: `${session.name} — Wraith`,
|
title: `${session.name} — Wraith`,
|
||||||
width: 900,
|
url: `index.html#/detached-session?sessionId=${session.id}&name=${encodeURIComponent(session.name)}&protocol=${session.protocol}`,
|
||||||
height: 600,
|
width: 900, height: 600,
|
||||||
resizable: true,
|
});
|
||||||
center: true,
|
} catch (err) { console.error("Detach window error:", err); }
|
||||||
visible: false,
|
|
||||||
focus: true,
|
|
||||||
url: `index.html#/detached-session?sessionId=${session.id}&name=${encodeURIComponent(session.name)}&protocol=${session.protocol}`,
|
|
||||||
});
|
|
||||||
wv.once("tauri://created", () => { wv.show(); });
|
|
||||||
wv.once("tauri://error", (e) => { console.error("Detach window error:", e); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMenuTab(): void {
|
function closeMenuTab(): void {
|
||||||
|
|||||||
@ -367,20 +367,14 @@ function closeHelpMenuDeferred(): void {
|
|||||||
|
|
||||||
async function handleHelpAction(page: string): Promise<void> {
|
async function handleHelpAction(page: string): Promise<void> {
|
||||||
showHelpMenu.value = false;
|
showHelpMenu.value = false;
|
||||||
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
try {
|
||||||
const label = `help-${page}-${Date.now()}`;
|
await invoke("open_child_window", {
|
||||||
const wv = new WebviewWindow(label, {
|
label: `help-${page}-${Date.now()}`,
|
||||||
title: `Wraith — Help`,
|
title: "Wraith — Help",
|
||||||
width: 750,
|
url: `index.html#/tool/help?page=${page}`,
|
||||||
height: 600,
|
width: 750, height: 600,
|
||||||
resizable: true,
|
});
|
||||||
center: true,
|
} catch (err) { console.error("Help window error:", err); alert("Window error: " + String(err)); }
|
||||||
visible: false,
|
|
||||||
focus: true,
|
|
||||||
url: `index.html#/tool/help?page=${page}`,
|
|
||||||
});
|
|
||||||
wv.once("tauri://created", () => { wv.show(); });
|
|
||||||
wv.once("tauri://error", (e) => { console.error("Help window error:", e); alert("Window error: " + JSON.stringify(e.payload)); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleToolAction(tool: string): Promise<void> {
|
async function handleToolAction(tool: string): Promise<void> {
|
||||||
@ -394,8 +388,6 @@ async function handleToolAction(tool: string): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
|
||||||
|
|
||||||
const toolConfig: Record<string, { title: string; width: number; height: number }> = {
|
const toolConfig: Record<string, { title: string; width: number; height: number }> = {
|
||||||
"network-scanner": { title: "Network Scanner", width: 800, height: 600 },
|
"network-scanner": { title: "Network Scanner", width: 800, height: 600 },
|
||||||
"port-scanner": { title: "Port Scanner", width: 700, height: 500 },
|
"port-scanner": { title: "Port Scanner", width: 700, height: 500 },
|
||||||
@ -416,20 +408,14 @@ async function handleToolAction(tool: string): Promise<void> {
|
|||||||
|
|
||||||
const sessionId = activeSessionId.value || "";
|
const sessionId = activeSessionId.value || "";
|
||||||
|
|
||||||
// Open tool in a new Tauri window — create hidden, show after webview confirms ready
|
try {
|
||||||
const label = `tool-${tool}-${Date.now()}`;
|
await invoke("open_child_window", {
|
||||||
const wv = new WebviewWindow(label, {
|
label: `tool-${tool}-${Date.now()}`,
|
||||||
title: `Wraith — ${config.title}`,
|
title: `Wraith — ${config.title}`,
|
||||||
width: config.width,
|
url: `index.html#/tool/${tool}?sessionId=${sessionId}`,
|
||||||
height: config.height,
|
width: config.width, height: config.height,
|
||||||
resizable: true,
|
});
|
||||||
center: true,
|
} catch (err) { console.error("Tool window error:", err); alert("Tool window error: " + String(err)); }
|
||||||
visible: false,
|
|
||||||
focus: true,
|
|
||||||
url: `index.html#/tool/${tool}?sessionId=${sessionId}`,
|
|
||||||
});
|
|
||||||
wv.once("tauri://created", () => { wv.show(); });
|
|
||||||
wv.once("tauri://error", (e) => { console.error("Tool window error:", e); alert("Window error: " + JSON.stringify(e.payload)); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFileMenuAction(action: string): Promise<void> {
|
async function handleFileMenuAction(action: string): Promise<void> {
|
||||||
@ -449,23 +435,14 @@ function handleThemeSelect(theme: ThemeDefinition): void {
|
|||||||
async function handleOpenFile(entry: FileEntry): Promise<void> {
|
async function handleOpenFile(entry: FileEntry): Promise<void> {
|
||||||
if (!activeSessionId.value) return;
|
if (!activeSessionId.value) return;
|
||||||
try {
|
try {
|
||||||
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
|
||||||
const fileName = entry.path.split("/").pop() || entry.path;
|
const fileName = entry.path.split("/").pop() || entry.path;
|
||||||
const label = `editor-${Date.now()}`;
|
|
||||||
const sessionId = activeSessionId.value;
|
const sessionId = activeSessionId.value;
|
||||||
|
await invoke("open_child_window", {
|
||||||
const wv = new WebviewWindow(label, {
|
label: `editor-${Date.now()}`,
|
||||||
title: `${fileName} — Wraith Editor`,
|
title: `${fileName} — Wraith Editor`,
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
resizable: true,
|
|
||||||
center: true,
|
|
||||||
visible: false,
|
|
||||||
focus: true,
|
|
||||||
url: `index.html#/tool/editor?sessionId=${sessionId}&path=${encodeURIComponent(entry.path)}`,
|
url: `index.html#/tool/editor?sessionId=${sessionId}&path=${encodeURIComponent(entry.path)}`,
|
||||||
|
width: 800, height: 600,
|
||||||
});
|
});
|
||||||
wv.once("tauri://created", () => { wv.show(); });
|
|
||||||
wv.once("tauri://error", (e) => { console.error("Editor window error:", e); alert("Window error: " + JSON.stringify(e.payload)); });
|
|
||||||
} catch (err) { console.error("Failed to open editor:", err); }
|
} catch (err) { console.error("Failed to open editor:", err); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user