feat(call): screenshare stays on the call bar in Firefox/Safari via the in-frame prompt (#43)
CI / Build & Quality Checks (pull_request) Successful in 4m18s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 11s
CI / Playwright smoke (e2e) (pull_request) Successful in 11m27s

With a fork that reports `screensharePrompt` (element-call lotus-screenshare-
prompt), the call bar and status bar keep their screenshare button on every
engine. Where the click can't be delegated, starting asks the fork to show
"Share your screen?" inside the call frame (io.lotus.prompt_screenshare)
instead of our own confirm; its Share click starts the share. Stopping works
from the bar as before (no click needed in the frame). Chromium is unchanged.

- useScreenshareMode: hidden (older fork's corner button) | prompt | direct.
- The room's call policy is also pushed to the fork in prompt mode, so the
  prompt never opens where sharing is forbidden.
- Picture-in-picture: the "Return to call" overlay covers the frame; while
  the fork reports the prompt open it lets clicks through to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-27 12:48:12 -04:00
co-authored by Claude Opus 5.5
parent 773e41311e
commit 8520de232d
5 changed files with 118 additions and 31 deletions
+57 -8
View File
@@ -261,6 +261,15 @@ export class CallControl extends EventEmitter implements CallControlState {
private frameScreenshareListeners = new Set<() => void>();
// [Gitea #43] The fork draws the "Share your screen?" prompt inside the frame
// on request (io.lotus.prompt_screenshare), so where the click can't be
// delegated our call-bar button asks for that instead of starting the share.
private _screensharePrompt = false;
// [Gitea #43] The fork's in-frame prompt is showing: anything the host lays
// over the frame (the picture-in-picture overlay) must let clicks through.
private _screensharePromptOpen = false;
// [Gitea #43] Whether the room's call policy allows screensharing; the fork
// hides its in-frame button when it doesn't.
private frameScreenshareAllowed = true;
@@ -274,7 +283,32 @@ export class CallControl extends EventEmitter implements CallControlState {
return this._frameScreenshare;
}
/** Subscribe to `frameScreenshare` changes. Returns an unsubscribe. */
/**
* Starting a share must go through the fork's in-frame prompt: this engine
* can't delegate the click (Firefox, Safari, WebKitGTK) and the fork has
* the prompt. Stopping never needs it.
*/
public get screenshareNeedsPrompt(): boolean {
return this._screensharePrompt && !canDelegateCapability();
}
/** The fork's in-frame "Share your screen?" prompt is showing. */
public get screensharePromptOpen(): boolean {
return this._screensharePromptOpen;
}
/**
* Ask the fork to show "Share your screen?" inside the call frame; its Share
* button starts the share with the frame's own click.
*/
public promptScreenshare(): void {
this.sendForkAction('io.lotus.prompt_screenshare', {});
}
/**
* Subscribe to `frameScreenshare` / `screenshareNeedsPrompt` /
* `screensharePromptOpen` changes. Returns an unsubscribe.
*/
public onFrameScreenshareChange(cb: () => void): () => void {
this.frameScreenshareListeners.add(cb);
return () => {
@@ -289,7 +323,8 @@ export class CallControl extends EventEmitter implements CallControlState {
}
private sendFrameScreenshareAllowed(): void {
if (!this.joined || !this.forkReady || !this._frameScreenshare) return;
if (!this.joined || !this.forkReady) return;
if (!this._frameScreenshare && !this._screensharePrompt) return;
this.sendForkAction('io.lotus.set_frame_screenshare', {
visible: this.frameScreenshareAllowed,
});
@@ -324,17 +359,31 @@ export class CallControl extends EventEmitter implements CallControlState {
/** [Gitea #43] The fork's `io.lotus.controls_state` report. */
public onControlsState(data: unknown) {
if (typeof data !== 'object' || data === null) return;
const { screensharing, layout, frameScreenshare } = data as {
screensharing?: unknown;
layout?: unknown;
frameScreenshare?: unknown;
};
const { screensharing, layout, frameScreenshare, screensharePrompt, screensharePromptOpen } =
data as {
screensharing?: unknown;
layout?: unknown;
frameScreenshare?: unknown;
screensharePrompt?: unknown;
screensharePromptOpen?: unknown;
};
const firstReport = !this.forkReady;
this.forkReady = true;
const frame = frameScreenshare === true;
if (frame !== this._frameScreenshare) {
const prompt = screensharePrompt === true;
const promptOpen = screensharePromptOpen === true;
if (promptOpen !== this._screensharePromptOpen) {
this._screensharePromptOpen = promptOpen;
if (frame === this._frameScreenshare && prompt === this._screensharePrompt) {
this.frameScreenshareListeners.forEach((l) => l());
}
}
if (frame !== this._frameScreenshare || prompt !== this._screensharePrompt) {
const promptTurnedOn = prompt && !this._screensharePrompt;
this._frameScreenshare = frame;
this._screensharePrompt = prompt;
this.frameScreenshareListeners.forEach((l) => l());
if (promptTurnedOn && !firstReport) this.sendFrameScreenshareAllowed();
}
if (firstReport) {
this.sendHotkeyCodes();