Agent Zero AI framework

Fix Desktop cursor and canvas resize handoff

Make the embedded Xpra Desktop use the browser cursor as the only visible cursor by suppressing the shadow pointer overlay and pointer-position renderer without blocking pointer input. Prefer the active Office host iframe when choosing the Desktop frame, then force resize recovery during modal-to-canvas docking so the Xpra desktop, window, and canvas refill the canvas after handoff.

Alessandro committed May 2, 2026 at 18:05 UTC 2f5f98521d10e08ab21f4b8767110454aacf72ec
2 files changed +76
plugins/_office/extensions/webui/right_canvas_register_surfaces/register-office.js
+13
@@ -28,11 +28,24 @@ export default async function registerOfficeSurface(canvas) {
28 icon: "desktop_windows",
29 order: 20,
30 modalPath: "/plugins/_office/webui/main.html",
31 + async beginDockHandoff() {
32 + const office = globalThis.Alpine?.store?.("office");
33 + office?.beforeDesktopHostHandoff?.();
34 + },
35 + async finishDockHandoff(payload = {}) {
36 + const office = globalThis.Alpine?.store?.("office");
37 + if (payload.opened !== false) office?.afterDesktopHostShown?.({ source: "dock" });
38 + },
39 + async cancelDockHandoff() {
40 + const office = globalThis.Alpine?.store?.("office");
41 + office?.cancelDesktopHostHandoff?.();
42 + },
43 async open(payload = {}) {
44 const panel = await waitForElement('[data-surface-id="office"] .office-panel');
45 const office = globalThis.Alpine?.store?.("office");
46 await office?.onMount?.(panel, { mode: "canvas" });
47 await office?.onOpen?.(payload);
48 + office?.afterDesktopHostShown?.({ source: payload?.source || "canvas" });
49 },
50 async close(payload = {}) {
51 const office = globalThis.Alpine?.store?.("office");
plugins/_office/webui/office-store.js
+63
@@ -1043,6 +1043,8 @@ const model = {
1043
1044 desktopFrame(preferred = null) {
1045 if (this.isUsableDesktopFrame(preferred)) return preferred;
1046 + const rootFrame = this._root?.querySelector?.("[data-office-desktop-frame]");
1047 + if (this.isUsableDesktopFrame(rootFrame)) return rootFrame;
1048 const frames = this.desktopFrames();
1049 return frames
1050 .filter((frame) => this.isUsableDesktopFrame(frame))
@@ -1077,6 +1079,34 @@ const model = {
1079 }
1080 },
1081
1082 + afterDesktopHostShown() {
1083 + if (!this.hasOfficialOffice()) return;
1084 + this._desktopResizeKey = "";
1085 + this._desktopResizeSuspended = false;
1086 + this._desktopResizePending = false;
1087 + this.restoreDesktopFrames();
1088 + this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() });
1089 + for (const delay of [720, 1280]) {
1090 + globalThis.setTimeout(() => {
1091 + this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() });
1092 + }, delay);
1093 + }
1094 + },
1095 +
1096 + beforeDesktopHostHandoff() {
1097 + this.stopDesktopResizeObserver();
1098 + this.stopXpraDesktopPrime();
1099 + this._desktopResizeKey = "";
1100 + this._desktopResizeSuspended = true;
1101 + this._desktopResizePending = true;
1102 + },
1103 +
1104 + cancelDesktopHostHandoff() {
1105 + this._desktopResizeSuspended = false;
1106 + this._desktopResizePending = false;
1107 + this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() });
1108 + },
1109 +
1110 onDesktopFrameLoaded(event = null) {
1111 if (event?.target?.getAttribute?.("src") === "about:blank") return;
1112 this.error = "";
@@ -1267,6 +1297,7 @@ const model = {
1297 const client = remoteWindow.client;
1298 if (!client) return false;
1299 this.installXpraDesktopClientPatches(remoteWindow, client);
1300 + this.installXpraDesktopCursorPatches(remoteWindow, remoteDocument, client);
1301 this.installXpraDesktopKeyboardBridge(frame, remoteWindow, remoteDocument, client);
1302 const container = client.container || remoteDocument?.querySelector?.("#screen");
1303 if (!container) return false;
@@ -1439,6 +1470,11 @@ const model = {
1470 .windowbuttons {
1471 display: none !important;
1472 }
1473 + #shadow_pointer {
1474 + display: none !important;
1475 + visibility: hidden !important;
1476 + opacity: 0 !important;
1477 + }
1478 .window,
1479 .window.border,
1480 .window.desktop,
@@ -1470,6 +1506,33 @@ const model = {
1506 remoteDocument.head?.appendChild(style);
1507 },
1508
1509 + installXpraDesktopCursorPatches(remoteWindow, remoteDocument, client) {
1510 + if (!remoteWindow || !remoteDocument || !client) return;
1511 + const hideShadowPointer = () => {
1512 + const pointer = remoteDocument.getElementById?.("shadow_pointer");
1513 + pointer?.style?.setProperty("display", "none", "important");
1514 + pointer?.style?.setProperty("visibility", "hidden", "important");
1515 + pointer?.style?.setProperty("opacity", "0", "important");
1516 + };
1517 + hideShadowPointer();
1518 +
1519 + const pointerPacket = remoteWindow.PACKET_TYPES?.pointer_position || "pointer-position";
1520 + if (!client.__a0XpraDesktopCursorPatched) {
1521 + if (typeof client._process_pointer_position === "function") {
1522 + client.__a0OriginalProcessPointerPosition = client._process_pointer_position;
1523 + }
1524 + client._process_pointer_position = function patchedProcessPointerPosition(packet) {
1525 + hideShadowPointer();
1526 + this.__a0LastPointerPosition = packet;
1527 + return false;
1528 + };
1529 + client.__a0XpraDesktopCursorPatched = true;
1530 + }
1531 + if (client.packet_handlers && pointerPacket) {
1532 + client.packet_handlers[pointerPacket] = client._process_pointer_position;
1533 + }
1534 + },
1535 +
1536 installXpraDesktopFramePatches(remoteWindow, remoteDocument) {
1537 if (!remoteWindow || !remoteDocument) return;
1538 remoteWindow.__a0XpraDesktopFramePatches ||= {};