From 16d179f8e83743824d7fca4e926ed3f311a58f45 Mon Sep 17 00:00:00 2001 From: byGalax Date: Wed, 22 Apr 2026 22:21:39 +0200 Subject: [PATCH] fix(call): Response return-type can't be wrapped in Result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous commit had the command typed as Result. Turns out that forces Tauri to JSON-serialise the variant wrapper around the Response body — the frontend gets a JSON object instead of the raw ArrayBuffer, the runtime check for byteLength fails, and every thumbnail comes back as null. Changed the return type to `tauri::ipc::Response` directly. Bad source ids and capture failures now funnel into an empty byte buffer; the JS side still detects "no thumbnail" via `byteLength === 0` so the contract stays the same. Frontend also widens the invoke-result typing to ArrayBuffer | Uint8Array | number[] so an older WebView2 that happens to deserialise as an array still works, and normalises into a plain ArrayBuffer before constructing the Blob to sidestep a TS SharedArrayBuffer incompatibility. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/desktop/src-tauri/src/screen_sources.rs | 19 +++++++------ apps/desktop/src/lib/screenSources.ts | 29 ++++++++++++++++++-- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src-tauri/src/screen_sources.rs b/apps/desktop/src-tauri/src/screen_sources.rs index 95a0184..55873bb 100644 --- a/apps/desktop/src-tauri/src/screen_sources.rs +++ b/apps/desktop/src-tauri/src/screen_sources.rs @@ -83,22 +83,23 @@ pub fn capture_screen_source_thumbnail(source_id: String) -> Result`): +// a Result wrapper forces Tauri to JSON-serialise the variant so the +// frontend gets a JSON object instead of raw bytes. Failures — bad id +// format, capture errors, source vanished — all funnel into an empty +// byte buffer; the caller treats `byteLength === 0` as the "no thumbnail" +// signal. #[tauri::command] -pub fn capture_screen_source_thumbnail_bytes( - source_id: String, -) -> Result { +pub fn capture_screen_source_thumbnail_bytes(source_id: String) -> tauri::ipc::Response { let bytes = if let Some(raw) = source_id.strip_prefix("screen:").and_then(strip_zero_suffix) { capture_monitor_bytes_by_id(raw).unwrap_or_default() } else if let Some(raw) = source_id.strip_prefix("window:").and_then(strip_zero_suffix) { capture_window_bytes_by_id(raw).unwrap_or_default() } else { - return Err(format!("unknown source id format: {source_id}")); + eprintln!("capture_screen_source_thumbnail_bytes: unknown id format: {source_id}"); + Vec::new() }; - Ok(tauri::ipc::Response::new(bytes)) + tauri::ipc::Response::new(bytes) } fn strip_zero_suffix(s: &str) -> Option<&str> { diff --git a/apps/desktop/src/lib/screenSources.ts b/apps/desktop/src/lib/screenSources.ts index c6828ff..127acc5 100644 --- a/apps/desktop/src/lib/screenSources.ts +++ b/apps/desktop/src/lib/screenSources.ts @@ -60,18 +60,41 @@ export async function captureScreenSourceThumbnail( // in a Blob so callers can hand it straight to `URL.createObjectURL` — // never touches base64 on either side. Returns null when the Rust side // produced zero bytes (capture failed, source vanished). +// +// Tauri's invoke may resolve with different shapes depending on runtime +// version — accept ArrayBuffer, Uint8Array, or a number[] fallback so a +// WebView2 variant that doesn't speak the binary channel still works. export async function captureScreenSourceThumbnailBytes( sourceId: string, ): Promise { if (!isTauriRuntime()) return null; try { const { invoke } = await import('@tauri-apps/api/core'); - const result = await invoke( + const result = await invoke( 'capture_screen_source_thumbnail_bytes', { sourceId }, ); - if (!result || result.byteLength === 0) return null; - return new Blob([result], { type: 'image/jpeg' }); + let bytes: ArrayBuffer | Uint8Array | null = null; + if (result instanceof ArrayBuffer) { + bytes = result; + } else if (result instanceof Uint8Array) { + bytes = result; + } else if (Array.isArray(result) && result.length > 0) { + bytes = new Uint8Array(result); + } + if (!bytes || bytes.byteLength === 0) { + return null; + } + // Normalise to ArrayBuffer so Blob() accepts the part without TS + // complaining about SharedArrayBuffer-backed Uint8Array flavours. + const buffer = + bytes instanceof ArrayBuffer + ? bytes + : (bytes.buffer.slice( + bytes.byteOffset, + bytes.byteOffset + bytes.byteLength, + ) as ArrayBuffer); + return new Blob([buffer], { type: 'image/jpeg' }); } catch (err: unknown) { console.warn('capture_screen_source_thumbnail_bytes failed', { sourceId, err }); return null;