Fix AltGr handling for Windows clients

This commit is contained in:
Riadh SEBAI 2025-11-20 18:17:17 +01:00
parent d24ce1c76f
commit 42d898d3d4
1 changed files with 55 additions and 3 deletions

View File

@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useResizeObserver } from "usehooks-ts";
import { cx } from "@/cva.config";
import { isWindows } from "@/utils";
import useKeyboard from "@hooks/useKeyboard";
import useMouse from "@hooks/useMouse";
import {
@ -74,6 +75,12 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
[setVideoClientSize, setVideoSize]
);
// AltGr Fix for Windows Clients
const altGrSyntheticThresholdMs = 3;
const isWindowsClient = useMemo(() => isWindows(), []);
const lastKeyDownRef = useRef<{ hidKey: number; time: number } | null>(null);
const altGrLoopRef = useRef(false);
useResizeObserver({
ref: videoElm as React.RefObject<HTMLElement>,
onResize: handleResize,
@ -250,7 +257,6 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
const keyDownHandler = useCallback(
(e: KeyboardEvent) => {
e.preventDefault();
if (e.repeat) return;
const code = getAdjustedKeyCode(e);
const hidKey = keys[code];
@ -259,6 +265,35 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
return;
}
// Detect Windows synthetic AltGr (CtrlLeft then AltRight within ~3ms) and cancel the synthetic Ctrl
if (isWindowsClient) {
// Buffer ControlLeft briefly; if no AltRight follows within the threshold, treat it as a real ControlLeft press.
if (hidKey === keys.ControlLeft) {
const controlLeftDownTime = e.timeStamp;
lastKeyDownRef.current = { hidKey, time: controlLeftDownTime };
setTimeout(() => {
if (
lastKeyDownRef.current?.hidKey === keys.ControlLeft &&
lastKeyDownRef.current.time === controlLeftDownTime
) {
lastKeyDownRef.current = null;
handleKeyPress(keys.ControlLeft, true);
}
}, altGrSyntheticThresholdMs);
return;
}
// If AltRight arrives shortly after ControlLeft, treat the pair as AltGr and cancel the pending ControlLeft.
if (
hidKey === keys.AltRight &&
lastKeyDownRef.current?.hidKey === keys.ControlLeft &&
e.timeStamp - lastKeyDownRef.current.time <= altGrSyntheticThresholdMs
) {
altGrLoopRef.current = true;
lastKeyDownRef.current = null;
}
}
// When pressing the meta key + another key, the key will never trigger a keyup
// event, so we need to clear the keys after a short delay
// https://bugs.chromium.org/p/chromium/issues/detail?id=28089
@ -282,7 +317,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
}, 100);
}
},
[handleKeyPress, isKeyboardLockActive],
[handleKeyPress, isKeyboardLockActive, isWindowsClient],
);
const keyUpHandler = useCallback(
@ -296,10 +331,27 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
return;
}
// On Windows, handle ControlLeft specially to preserve FIFO semantics with AltGr buffering.
if (isWindowsClient && hidKey === keys.ControlLeft) {
// Synthetic AltGr ControlLeft: never sent a down, swallow the release as well.
if (altGrLoopRef.current) {
altGrLoopRef.current = false;
return;
}
// Very fast real Ctrl tap: flush the pending down before the up.
if (lastKeyDownRef.current?.hidKey === keys.ControlLeft) {
handleKeyPress(keys.ControlLeft, true);
}
lastKeyDownRef.current = null;
}
console.debug(`Key up: ${hidKey}`);
handleKeyPress(hidKey, false);
},
[handleKeyPress],
[handleKeyPress, isWindowsClient],
);
const videoKeyUpHandler = useCallback((e: KeyboardEvent) => {