feat(call): stop injecting CSS and key listeners into the call frame (#43)
CI / Build & Quality Checks (push) Successful in 3m5s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 9s
CI / Trigger Desktop Build (push) Successful in 8s
CI / Playwright smoke (e2e) (push) Successful in 12m42s
CI / Build & Quality Checks (push) Successful in 3m5s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 9s
CI / Trigger Desktop Build (push) Successful in 8s
CI / Playwright smoke (e2e) (push) Successful in 12m42s
Pins @lotusguild/element-call-embedded 0.25.0-lotus.19. - Styles: the fork now hides its own footer (`lotusHostControls=1`) and sets its root color-scheme from the theme, so the host no longer injects `#lotus-ec-styles` or sets inline styles on EC's DOM. The two other injected rules matched nothing in EC 0.25 (dead). The transparent background was already the fork's (`lotusTransparent`). - Hotkeys: PTT / deafen keys pressed with focus inside the call frame now arrive as `io.lotus.hotkey` (the host sends the codes via `io.lotus.set_hotkeys`), instead of listeners on the frame's window. The window binding stays only for a fork that doesn't report `hotkeys`. - Fixes (with lotus.19): pressing the deafen key M with focus in the call also hit EC's own "M = toggle mic" shortcut, so the first press turned the mic ON instead of deafening — even in push-to-talk mode. 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
024edbf546
commit
502b9dfd83
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { atom, useSetAtom } from 'jotai';
|
||||
import { CallEmbed, useCallControlState } from '../plugins/call';
|
||||
import { useSetting } from '../state/hooks/settings';
|
||||
@@ -150,6 +150,20 @@ 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(() => {
|
||||
@@ -210,11 +224,12 @@ export function useCallHotkeys(callEmbed: CallEmbed | undefined, joined: boolean
|
||||
pttActiveRef.current = false;
|
||||
setPttActive(false);
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
// `prevent` is absent for keys reported by the fork, which already
|
||||
// cancelled the default action inside its frame.
|
||||
const pttDown = (e: KeyLike, editable: boolean, interactive: boolean, prevent?: () => void) => {
|
||||
if (!isPttKeyDown(e, pttKey)) return;
|
||||
const target = e.target as HTMLElement;
|
||||
if (isEditable(target)) return;
|
||||
if (!isInteractive(target)) e.preventDefault();
|
||||
if (editable) return;
|
||||
if (!interactive) prevent?.();
|
||||
// Key auto-repeat re-fires keydown while held; don't restart the clock.
|
||||
if (pttActiveRef.current) return;
|
||||
// C-M5: mark PTT active BEFORE unmuting so the mic echo (onMediaState)
|
||||
@@ -225,6 +240,10 @@ export function useCallHotkeys(callEmbed: CallEmbed | undefined, joined: boolean
|
||||
setPttActive(true);
|
||||
holdWatchdog.current.arm();
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
pttDown(e, isEditable(target), isInteractive(target), () => e.preventDefault());
|
||||
};
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.code !== pttKey) return;
|
||||
release();
|
||||
@@ -249,16 +268,25 @@ export function useCallHotkeys(callEmbed: CallEmbed | undefined, joined: boolean
|
||||
};
|
||||
};
|
||||
const unbindHost = bind(window);
|
||||
// BUG-9: also wire iframe key/blur/focus so PTT works with focus in the iframe
|
||||
const unbindIframe = bindIframeWindow(embed.iframe, bind);
|
||||
// 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.
|
||||
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]);
|
||||
}, [pttMode, pttKey, embed, setPttActive, forkHotkeys]);
|
||||
|
||||
// [cinny-desktop #2] System-wide PTT/deafen while a game has focus. The
|
||||
// desktop polls the configured keys without consuming them and emits one
|
||||
@@ -330,15 +358,25 @@ export function useCallHotkeys(callEmbed: CallEmbed | undefined, joined: boolean
|
||||
embed.control.toggleSound();
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
// C-L4: also bind the EC iframe window so the deafen key works when focus is
|
||||
// inside the iframe (mirrors the PTT binding above).
|
||||
const unbindIframe = bindIframeWindow(embed.iframe, (win) => {
|
||||
win.addEventListener('keydown', onKeyDown);
|
||||
return () => win.removeEventListener('keydown', onKeyDown);
|
||||
// C-L4: the deafen key also works with focus inside the call frame (no
|
||||
// composer there, so only the editable-field check applies).
|
||||
embed.control.setHotkeyCodes('deafen', [deafenKey]);
|
||||
const unbindFork = embed.control.onHotkey((r) => {
|
||||
if (r.type === 'keydown' && isDeafenKeyDown(r, deafenKey) && !r.editable) {
|
||||
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]);
|
||||
}, [embed, deafenKey, deafenHotkey, forkHotkeys]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user