Mercurial
comparison infinite_canvas/web_surface_web.c @ 280:49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 19:14:53 -0700 |
| parents | 8d560f50ed4c |
| children |
comparison
equal
deleted
inserted
replaced
| 279:b3b547563ec7 | 280:49e9e591c9bb |
|---|---|
| 5 | 5 |
| 6 EM_JS(void, Canvas_Web_Begin_Frame, (), { | 6 EM_JS(void, Canvas_Web_Begin_Frame, (), { |
| 7 document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => { | 7 document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => { |
| 8 surface.dataset.canvasActive = "false"; | 8 surface.dataset.canvasActive = "false"; |
| 9 }); | 9 }); |
| 10 }); | |
| 11 | |
| 12 EM_JS(void, Canvas_Web_Dictation_Init, (), { | |
| 13 if (Module.canvasDictationInitialized) return; | |
| 14 Module.canvasDictationInitialized = true; | |
| 15 Module.canvasDictationQueue = []; | |
| 16 window.addEventListener("message", event => { | |
| 17 if (event.data?.type !== "zenbu.dictation.event") return; | |
| 18 const frame = document.getElementById("canvas-dictation-transport"); | |
| 19 if (!frame || frame.contentWindow !== event.source) return; | |
| 20 Module.canvasDictationQueue.push({ | |
| 21 kind: String(event.data.kind || ""), | |
| 22 text: String(event.data.text || ""), | |
| 23 }); | |
| 24 }); | |
| 25 }); | |
| 26 | |
| 27 EM_JS(void, Canvas_Web_Dictation_Set_Active, (int active), { | |
| 28 let frame = document.getElementById("canvas-dictation-transport"); | |
| 29 if (!frame) { | |
| 30 frame = document.createElement("iframe"); | |
| 31 frame.id = "canvas-dictation-transport"; | |
| 32 frame.title = "Canvas dictation transport"; | |
| 33 frame.allow = "microphone"; | |
| 34 frame.sandbox = | |
| 35 "allow-same-origin allow-scripts"; | |
| 36 frame.style.position = "fixed"; | |
| 37 frame.style.left = "-2px"; | |
| 38 frame.style.top = "-2px"; | |
| 39 frame.style.width = "1px"; | |
| 40 frame.style.height = "1px"; | |
| 41 frame.style.opacity = "0"; | |
| 42 frame.style.pointerEvents = "none"; | |
| 43 frame.src = "http://127.0.0.1:8090/?canvas=1"; | |
| 44 document.body.appendChild(frame); | |
| 45 } | |
| 46 const send = () => frame.contentWindow.postMessage( | |
| 47 { | |
| 48 type: "zenbu.dictation.set-active", | |
| 49 active: active !== 0, | |
| 50 }, | |
| 51 "*"); | |
| 52 frame.addEventListener("load", send, {once: true}); | |
| 53 send(); | |
| 54 }); | |
| 55 | |
| 56 EM_JS(void, Canvas_Web_Dictation_Commit, (), { | |
| 57 const frame = document.getElementById("canvas-dictation-transport"); | |
| 58 frame?.contentWindow?.postMessage( | |
| 59 {type: "zenbu.dictation.commit"}, | |
| 60 "*"); | |
| 61 }); | |
| 62 | |
| 63 EM_JS(int, Canvas_Web_Dictation_Consume, ( | |
| 64 char *p_text, | |
| 65 int text_capacity), { | |
| 66 const event = Module.canvasDictationQueue?.shift(); | |
| 67 if (!event) return 0; | |
| 68 stringToUTF8(event.text, p_text, text_capacity); | |
| 69 if (event.kind === "status") return 1; | |
| 70 if (event.kind === "partial") return 2; | |
| 71 if (event.kind === "final") return 3; | |
| 72 return 0; | |
| 10 }); | 73 }); |
| 11 | 74 |
| 12 EM_JS(void, Canvas_Web_Create_Or_Update, ( | 75 EM_JS(void, Canvas_Web_Create_Or_Update, ( |
| 13 uint32 entity_id, | 76 uint32 entity_id, |
| 14 const char *p_url, | 77 const char *p_url, |
| 168 if (content && content.dataset.blobUrl) { | 231 if (content && content.dataset.blobUrl) { |
| 169 URL.revokeObjectURL(content.dataset.blobUrl); | 232 URL.revokeObjectURL(content.dataset.blobUrl); |
| 170 } | 233 } |
| 171 surface.remove(); | 234 surface.remove(); |
| 172 }); | 235 }); |
| 236 document.getElementById("canvas-dictation-transport")?.remove(); | |
| 173 }); | 237 }); |
| 174 | 238 |
| 175 static Rectangle Canvas_Web_Content_Bounds( | 239 static Rectangle Canvas_Web_Content_Bounds( |
| 176 const Canvas_Camera *p_camera, | 240 const Canvas_Camera *p_camera, |
| 177 const Canvas_Scene *p_scene, | 241 const Canvas_Scene *p_scene, |
| 234 { | 298 { |
| 235 memset(p_surface, 0, sizeof(*p_surface)); | 299 memset(p_surface, 0, sizeof(*p_surface)); |
| 236 p_surface->p_arena = p_arena; | 300 p_surface->p_arena = p_arena; |
| 237 p_surface->dark_mode = dark_mode; | 301 p_surface->dark_mode = dark_mode; |
| 238 p_surface->initialized = TRUE; | 302 p_surface->initialized = TRUE; |
| 303 Canvas_Web_Dictation_Init(); | |
| 239 return TRUE; | 304 return TRUE; |
| 240 } | 305 } |
| 241 | 306 |
| 242 void Canvas_Web_Surface_Set_Dark_Mode( | 307 void Canvas_Web_Surface_Set_Dark_Mode( |
| 243 Canvas_Web_Surface *p_surface, | 308 Canvas_Web_Surface *p_surface, |
| 367 { | 432 { |
| 368 (void)p_surface; | 433 (void)p_surface; |
| 369 Canvas_Web_History_Go(entity_id, 1); | 434 Canvas_Web_History_Go(entity_id, 1); |
| 370 } | 435 } |
| 371 | 436 |
| 437 void Canvas_Web_Surface_Set_Dictation_Active( | |
| 438 Canvas_Web_Surface *p_surface, | |
| 439 boolean active) | |
| 440 { | |
| 441 (void)p_surface; | |
| 442 Canvas_Web_Dictation_Set_Active(active); | |
| 443 } | |
| 444 | |
| 445 void Canvas_Web_Surface_Commit_Dictation( | |
| 446 Canvas_Web_Surface *p_surface) | |
| 447 { | |
| 448 (void)p_surface; | |
| 449 Canvas_Web_Dictation_Commit(); | |
| 450 } | |
| 451 | |
| 452 Canvas_Dictation_Event Canvas_Web_Surface_Consume_Dictation( | |
| 453 Canvas_Web_Surface *p_surface, | |
| 454 char *p_text, | |
| 455 size_t text_capacity) | |
| 456 { | |
| 457 (void)p_surface; | |
| 458 return (Canvas_Dictation_Event)Canvas_Web_Dictation_Consume( | |
| 459 p_text, | |
| 460 (int)text_capacity); | |
| 461 } | |
| 462 | |
| 372 boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface) | 463 boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface) |
| 373 { | 464 { |
| 374 (void)p_surface; | 465 (void)p_surface; |
| 375 return FALSE; | 466 return FALSE; |
| 376 } | 467 } |