diff --git a/ui/e2e/infobar-lock-toggles.spec.ts b/ui/e2e/infobar-lock-toggles.spec.ts new file mode 100644 index 000000000..950e3a62b --- /dev/null +++ b/ui/e2e/infobar-lock-toggles.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from "@playwright/test"; + +import { ensureLocalAuthMode, waitForWebRTCReady, getLedState, waitForLedState } from "./helpers"; + +test.describe("InfoBar lock toggle buttons", () => { + test.setTimeout(60_000); + + test.beforeEach(async ({ page }) => { + await page.goto("/"); + await page.waitForLoadState("networkidle"); + await ensureLocalAuthMode(page, { mode: "noPassword" }); + await waitForWebRTCReady(page); + }); + + test("Caps Lock toggle button sends scancode and reflects LED state", async ({ page }) => { + const initial = await getLedState(page); + expect(initial, "LED state should be available").not.toBeNull(); + const initialCapsLock = initial!.caps_lock; + + const capsBtn = page.getByTestId("caps-lock-toggle"); + await expect(capsBtn).toBeVisible(); + + await capsBtn.click(); + await waitForLedState(page, "caps_lock", !initialCapsLock); + + await capsBtn.click(); + await waitForLedState(page, "caps_lock", initialCapsLock); + }); + + test("Num Lock toggle button sends scancode and reflects LED state", async ({ page }) => { + const initial = await getLedState(page); + expect(initial, "LED state should be available").not.toBeNull(); + const initialNumLock = initial!.num_lock; + + const numBtn = page.getByTestId("num-lock-toggle"); + await expect(numBtn).toBeVisible(); + + await numBtn.click(); + await waitForLedState(page, "num_lock", !initialNumLock); + + await numBtn.click(); + await waitForLedState(page, "num_lock", initialNumLock); + }); +}); diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx index 12e45a9d9..4e1f6e484 100644 --- a/ui/src/components/InfoBar.tsx +++ b/ui/src/components/InfoBar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useHidStore, @@ -9,9 +9,11 @@ import { VideoState, } from "@hooks/stores"; import { useHidRpc } from "@hooks/useHidRpc"; +import { useJsonRpc } from "@/hooks/useJsonRpc"; import { keys, modifiers } from "@/keyboardMappings"; import { cx } from "@/cva.config"; import { m } from "@localizations/messages.js"; +import { Button } from "@components/Button"; export default function InfoBar() { const { keysDownState } = useHidStore(); @@ -32,6 +34,18 @@ export default function InfoBar() { const { isTurnServerInUse, peerConnection, peerConnectionState } = useRTCStore(); const { hdmiState } = useVideoStore(); + const { send } = useJsonRpc(); + + const sendLockKey = useCallback( + (keyCode: number) => { + send("keypressReport", { key: keyCode, press: true }); + setTimeout(() => { + send("keypressReport", { key: keyCode, press: false }); + }, 20); + }, + [send], + ); + const [videoCodec, setVideoCodec] = useState(null); const [videoBitrate, setVideoBitrate] = useState(null); @@ -211,27 +225,21 @@ export default function InfoBar() { )} -
- {m.info_caps_lock()} -
- -
- {m.info_num_lock()} -
+