From 2f5f98521d10e08ab21f4b8767110454aacf72ec Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Sat, 2 May 2026 18:05:39 +0200 Subject: [PATCH] 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. --- .../register-office.js | 13 ++++ plugins/_office/webui/office-store.js | 63 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/plugins/_office/extensions/webui/right_canvas_register_surfaces/register-office.js b/plugins/_office/extensions/webui/right_canvas_register_surfaces/register-office.js index 65d436c8d..c6196dfc0 100644 --- a/plugins/_office/extensions/webui/right_canvas_register_surfaces/register-office.js +++ b/plugins/_office/extensions/webui/right_canvas_register_surfaces/register-office.js @@ -28,11 +28,24 @@ export default async function registerOfficeSurface(canvas) { icon: "desktop_windows", order: 20, modalPath: "/plugins/_office/webui/main.html", + async beginDockHandoff() { + const office = globalThis.Alpine?.store?.("office"); + office?.beforeDesktopHostHandoff?.(); + }, + async finishDockHandoff(payload = {}) { + const office = globalThis.Alpine?.store?.("office"); + if (payload.opened !== false) office?.afterDesktopHostShown?.({ source: "dock" }); + }, + async cancelDockHandoff() { + const office = globalThis.Alpine?.store?.("office"); + office?.cancelDesktopHostHandoff?.(); + }, async open(payload = {}) { const panel = await waitForElement('[data-surface-id="office"] .office-panel'); const office = globalThis.Alpine?.store?.("office"); await office?.onMount?.(panel, { mode: "canvas" }); await office?.onOpen?.(payload); + office?.afterDesktopHostShown?.({ source: payload?.source || "canvas" }); }, async close(payload = {}) { const office = globalThis.Alpine?.store?.("office"); diff --git a/plugins/_office/webui/office-store.js b/plugins/_office/webui/office-store.js index 5bb535c80..2b04a2291 100644 --- a/plugins/_office/webui/office-store.js +++ b/plugins/_office/webui/office-store.js @@ -1043,6 +1043,8 @@ const model = { desktopFrame(preferred = null) { if (this.isUsableDesktopFrame(preferred)) return preferred; + const rootFrame = this._root?.querySelector?.("[data-office-desktop-frame]"); + if (this.isUsableDesktopFrame(rootFrame)) return rootFrame; const frames = this.desktopFrames(); return frames .filter((frame) => this.isUsableDesktopFrame(frame)) @@ -1077,6 +1079,34 @@ const model = { } }, + afterDesktopHostShown() { + if (!this.hasOfficialOffice()) return; + this._desktopResizeKey = ""; + this._desktopResizeSuspended = false; + this._desktopResizePending = false; + this.restoreDesktopFrames(); + this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() }); + for (const delay of [720, 1280]) { + globalThis.setTimeout(() => { + this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() }); + }, delay); + } + }, + + beforeDesktopHostHandoff() { + this.stopDesktopResizeObserver(); + this.stopXpraDesktopPrime(); + this._desktopResizeKey = ""; + this._desktopResizeSuspended = true; + this._desktopResizePending = true; + }, + + cancelDesktopHostHandoff() { + this._desktopResizeSuspended = false; + this._desktopResizePending = false; + this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() }); + }, + onDesktopFrameLoaded(event = null) { if (event?.target?.getAttribute?.("src") === "about:blank") return; this.error = ""; @@ -1267,6 +1297,7 @@ const model = { const client = remoteWindow.client; if (!client) return false; this.installXpraDesktopClientPatches(remoteWindow, client); + this.installXpraDesktopCursorPatches(remoteWindow, remoteDocument, client); this.installXpraDesktopKeyboardBridge(frame, remoteWindow, remoteDocument, client); const container = client.container || remoteDocument?.querySelector?.("#screen"); if (!container) return false; @@ -1439,6 +1470,11 @@ const model = { .windowbuttons { display: none !important; } + #shadow_pointer { + display: none !important; + visibility: hidden !important; + opacity: 0 !important; + } .window, .window.border, .window.desktop, @@ -1470,6 +1506,33 @@ const model = { remoteDocument.head?.appendChild(style); }, + installXpraDesktopCursorPatches(remoteWindow, remoteDocument, client) { + if (!remoteWindow || !remoteDocument || !client) return; + const hideShadowPointer = () => { + const pointer = remoteDocument.getElementById?.("shadow_pointer"); + pointer?.style?.setProperty("display", "none", "important"); + pointer?.style?.setProperty("visibility", "hidden", "important"); + pointer?.style?.setProperty("opacity", "0", "important"); + }; + hideShadowPointer(); + + const pointerPacket = remoteWindow.PACKET_TYPES?.pointer_position || "pointer-position"; + if (!client.__a0XpraDesktopCursorPatched) { + if (typeof client._process_pointer_position === "function") { + client.__a0OriginalProcessPointerPosition = client._process_pointer_position; + } + client._process_pointer_position = function patchedProcessPointerPosition(packet) { + hideShadowPointer(); + this.__a0LastPointerPosition = packet; + return false; + }; + client.__a0XpraDesktopCursorPatched = true; + } + if (client.packet_handlers && pointerPacket) { + client.packet_handlers[pointerPacket] = client._process_pointer_position; + } + }, + installXpraDesktopFramePatches(remoteWindow, remoteDocument) { if (!remoteWindow || !remoteDocument) return; remoteWindow.__a0XpraDesktopFramePatches ||= {};