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 ||= {};