feat(call): screenshare from inside the frame where delegation is missing; host stops reading the call frame (#43)
CI / Build & Quality Checks (push) Successful in 4m32s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 12s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 16m32s
CI / Build & Quality Checks (push) Successful in 4m32s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 12s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 16m32s
Firefox, Safari and the WebKitGTK desktop app can't hand the user's click to the call frame (no Capability Delegation), and getDisplayMedia needs it. The host used to click EC's hidden footer button through the DOM instead, which dies with same-origin. Now (pins element-call-embedded 0.25.0-lotus.20): - those engines get `lotusFrameScreenshare`, the fork shows EC's own screenshare button in the frame, and the call bar and status bar hide theirs once controls_state reports `frameScreenshare`; the screenshare-audio mute stays; - the room's call policy is pushed with io.lotus.set_frame_screenshare, so the frame button hides where the server would refuse a share, like ours; - Chromium keeps the delegated io.lotus.set_screenshare from the host bar. Removed the fallbacks for forks older than lotus.14, which read or clicked EC's DOM: the screenshare/layout/settings/reactions/leave button lookups and their MutationObservers, the frame-window hotkey binding, and the speaking/muted tile scrape in useCallSpeakers (io.lotus.call_state is the only source now). getCallDocument is gone; the host's only handle on the frame is postMessage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ba48e95993
commit
49dec686f1
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { atom, useSetAtom } from 'jotai';
|
||||
import { CallEmbed, useCallControlState } from '../plugins/call';
|
||||
import { useSetting } from '../state/hooks/settings';
|
||||
@@ -105,32 +105,6 @@ const isInteractive = (el: HTMLElement): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind listeners to the EC iframe's window and keep them bound across document
|
||||
* reloads. [Gitea #60] Listeners registered on a contentWindow are discarded
|
||||
* when that window navigates to a new document (crash recovery, in-widget
|
||||
* navigation), so re-run `bind` from the iframe's `load` event and detach the
|
||||
* previous set. Returns a cleanup that detaches everything.
|
||||
*/
|
||||
const bindIframeWindow = (
|
||||
iframe: HTMLIFrameElement,
|
||||
bind: (win: Window) => () => void,
|
||||
): (() => void) => {
|
||||
let unbind: (() => void) | undefined;
|
||||
const attach = () => {
|
||||
unbind?.();
|
||||
const win = iframe.contentWindow;
|
||||
unbind = win ? bind(win) : undefined;
|
||||
};
|
||||
attach();
|
||||
iframe.addEventListener('load', attach);
|
||||
return () => {
|
||||
iframe.removeEventListener('load', attach);
|
||||
unbind?.();
|
||||
unbind = undefined;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Push-to-talk and deafen hotkeys for the active call.
|
||||
*
|
||||
@@ -150,20 +124,6 @@ export function useCallHotkeys(callEmbed: CallEmbed | undefined, joined: boolean
|
||||
const { microphone } = useCallControlState(embed?.control);
|
||||
const setPttActive = useSetAtom(pttActiveAtom);
|
||||
|
||||
// [Gitea #43] Once the fork reports it handles hotkeys, keys pressed inside
|
||||
// the call frame arrive as io.lotus.hotkey and we stop adding listeners to
|
||||
// the frame's window (which needs same-origin access).
|
||||
const [forkHotkeys, setForkHotkeys] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!embed) {
|
||||
setForkHotkeys(false);
|
||||
return undefined;
|
||||
}
|
||||
const sync = () => setForkHotkeys(embed.control.forkHandlesHotkeys);
|
||||
sync();
|
||||
return embed.control.onForkHotkeysChange(sync);
|
||||
}, [embed]);
|
||||
|
||||
// Track microphone via ref so the PTT effect doesn't need it as a dep (avoids listener churn)
|
||||
const microphoneRef = useRef(microphone);
|
||||
useEffect(() => {
|
||||
@@ -268,25 +228,23 @@ export function useCallHotkeys(callEmbed: CallEmbed | undefined, joined: boolean
|
||||
};
|
||||
};
|
||||
const unbindHost = bind(window);
|
||||
// BUG-9: PTT also works with focus in the call frame — reported by the
|
||||
// fork, or (older fork) via listeners on the frame's window.
|
||||
// BUG-9: PTT also works with focus in the call frame, reported by the
|
||||
// fork (io.lotus.hotkey; the host no longer listens on the frame's window).
|
||||
embed.control.setHotkeyCodes('ptt', [pttKey]);
|
||||
const unbindFork = embed.control.onHotkey((r) => {
|
||||
if (r.type === 'focus') onFocusChange();
|
||||
else if (r.type === 'keydown') pttDown(r, r.editable, r.interactive);
|
||||
else if (r.code === pttKey) release();
|
||||
});
|
||||
const unbindIframe = forkHotkeys ? () => undefined : bindIframeWindow(embed.iframe, bind);
|
||||
return () => {
|
||||
unbindHost();
|
||||
unbindFork();
|
||||
unbindIframe();
|
||||
embed.control.setHotkeyCodes('ptt', []);
|
||||
// BUG-8: if the embed changes while PTT is active, release mic on cleanup
|
||||
if (pttActiveRef.current) release();
|
||||
};
|
||||
// microphone intentionally read via microphoneRef — excluded from deps to avoid listener churn
|
||||
}, [pttMode, pttKey, embed, setPttActive, forkHotkeys]);
|
||||
}, [pttMode, pttKey, embed, setPttActive]);
|
||||
|
||||
// [cinny-desktop #2] System-wide PTT/deafen while a game has focus. The
|
||||
// desktop polls the configured keys without consuming them and emits one
|
||||
@@ -366,17 +324,10 @@ export function useCallHotkeys(callEmbed: CallEmbed | undefined, joined: boolean
|
||||
embed.control.toggleSound();
|
||||
}
|
||||
});
|
||||
const unbindIframe = forkHotkeys
|
||||
? () => undefined
|
||||
: bindIframeWindow(embed.iframe, (win) => {
|
||||
win.addEventListener('keydown', onKeyDown);
|
||||
return () => win.removeEventListener('keydown', onKeyDown);
|
||||
});
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
unbindFork();
|
||||
unbindIframe();
|
||||
embed.control.setHotkeyCodes('deafen', []);
|
||||
};
|
||||
}, [embed, deafenKey, deafenHotkey, forkHotkeys]);
|
||||
}, [embed, deafenKey, deafenHotkey]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user