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.
This commit is contained in:
Alessandro 2026-05-02 18:05:39 +02:00
parent 74dcb32814
commit 2f5f98521d
2 changed files with 76 additions and 0 deletions

View file

@ -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");

View file

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