fix(calls): hotkey rebind denylist; ignore modifiers; keep Space on buttons
- useKeyBind refuses Tab/Enter/arrows/Home/End/Page*/Escape and bare modifier codes, and refuses a code equal to the other call key, with an inline message (isBindableCallKey, unit-tested). - PTT and deafen handlers ignore events with Ctrl/Alt/Meta held (deafen also Shift), so Cmd+M / Ctrl+M no longer toggle deafen. - PTT only preventDefault()s when the target is not an interactive control, so Space still activates focused buttons during a call. Fixes #23 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -117,6 +117,7 @@ import { playCallJoinSound } from '../../../utils/callSounds';
|
||||
import { previewRingtone, RINGTONE_OPTIONS } from '../../../utils/ringtones';
|
||||
import { DenoiseTester } from './DenoiseTester';
|
||||
import { SettingsSelect } from '../../../components/settings-select/SettingsSelect';
|
||||
import { isBindableCallKey } from '../../../utils/callKeybind';
|
||||
|
||||
/**
|
||||
* P5-47 — opt-in TDS window chrome toggle (desktop only). Renders nothing in the
|
||||
@@ -1474,8 +1475,12 @@ function Privacy() {
|
||||
);
|
||||
}
|
||||
|
||||
function useKeyBind(setter: (code: string) => void) {
|
||||
// [Gitea #23] Denylist navigation-critical/modifier codes and reject a code that
|
||||
// collides with the other call key (`otherKey`), so a rebind can never trap
|
||||
// keyboard focus in-call or silently double-bind PTT and deafen to the same key.
|
||||
function useKeyBind(setter: (code: string) => void, otherKey?: string) {
|
||||
const [listening, setListening] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const listenerRef = useRef<((e: KeyboardEvent) => void) | null>(null);
|
||||
|
||||
useEffect(
|
||||
@@ -1487,19 +1492,28 @@ function useKeyBind(setter: (code: string) => void) {
|
||||
|
||||
const startListening = useCallback(() => {
|
||||
if (listening) return;
|
||||
setError(null);
|
||||
setListening(true);
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
if (e.code !== 'Escape') setter(e.code);
|
||||
if (e.code === 'Escape') {
|
||||
// Escape always cancels the rebind without changing the key.
|
||||
} else if (!isBindableCallKey(e.code)) {
|
||||
setError('That key can’t be bound — it’s needed for keyboard navigation.');
|
||||
} else if (otherKey && e.code === otherKey) {
|
||||
setError('That key is already bound to the other call shortcut.');
|
||||
} else {
|
||||
setter(e.code);
|
||||
}
|
||||
setListening(false);
|
||||
window.removeEventListener('keydown', onKey, true);
|
||||
listenerRef.current = null;
|
||||
};
|
||||
listenerRef.current = onKey;
|
||||
window.addEventListener('keydown', onKey, true);
|
||||
}, [listening, setter]);
|
||||
}, [listening, setter, otherKey]);
|
||||
|
||||
return { listening, startListening };
|
||||
return { listening, startListening, error };
|
||||
}
|
||||
|
||||
const keyLabel = (code: string) =>
|
||||
@@ -1556,8 +1570,8 @@ function Calls() {
|
||||
previewRingtone(value, Math.max(0, Math.min(1, ringtoneVolume / 100)));
|
||||
};
|
||||
|
||||
const pttBind = useKeyBind(setPttKey);
|
||||
const deafenBind = useKeyBind(setDeafenKey);
|
||||
const pttBind = useKeyBind(setPttKey, deafenKey);
|
||||
const deafenBind = useKeyBind(setDeafenKey, pttKey);
|
||||
|
||||
const mlSupported = isMLDenoiseSupported();
|
||||
const selectedDenoiseModel = DENOISE_MODELS.find((m) => m.id === callDenoiseModel);
|
||||
@@ -1823,7 +1837,7 @@ function Calls() {
|
||||
{pttMode && (
|
||||
<SettingTile
|
||||
title="PTT Key"
|
||||
description="Press a key to bind it as your push-to-talk key."
|
||||
description={pttBind.error ?? 'Press a key to bind it as your push-to-talk key.'}
|
||||
after={
|
||||
<Button
|
||||
size="300"
|
||||
@@ -1841,7 +1855,9 @@ function Calls() {
|
||||
)}
|
||||
<SettingTile
|
||||
title="Push to Deafen"
|
||||
description="Toggle speaker mute during a call. Press Escape to cancel rebind."
|
||||
description={
|
||||
deafenBind.error ?? 'Toggle speaker mute during a call. Press Escape to cancel rebind.'
|
||||
}
|
||||
after={
|
||||
<Button
|
||||
size="300"
|
||||
|
||||
Reference in New Issue
Block a user