import { useCallback, useEffect, useRef, useState } from "react"; import { LuCornerDownLeft } from "react-icons/lu"; import { ExclamationCircleIcon } from "@heroicons/react/16/solid"; import { useClose } from "@headlessui/react"; import { Button } from "@components/Button"; import { GridCard } from "@components/Card"; import { TextAreaWithLabel } from "@components/TextArea"; import { SettingsPageHeader } from "@components/SettingsPageheader"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/stores"; import { keys, modifiers } from "@/keyboardMappings"; import { KeyStroke } from "@/keyboardLayouts"; import useKeyboardLayout from "@/hooks/useKeyboardLayout"; import notifications from "@/notifications"; const hidKeyboardPayload = (modifier: number, keys: number[]) => { return { modifier, keys }; }; const modifierCode = (shift?: boolean, altRight?: boolean) => { return (shift ? modifiers.ShiftLeft : 0) | (altRight ? modifiers.AltRight : 0) } const noModifier = 0 export default function PasteModal() { const TextAreaRef = useRef(null); const { setPasteModeEnabled } = useHidStore(); const { setDisableVideoFocusTrap } = useUiStore(); const { send } = useJsonRpc(); const { rpcDataChannel } = useRTCStore(); const [invalidChars, setInvalidChars] = useState([]); const close = useClose(); const { setKeyboardLayout } = useSettingsStore(); const { selectedKeyboard } = useKeyboardLayout(); useEffect(() => { send("getKeyboardLayout", {}, resp => { if ("error" in resp) return; setKeyboardLayout(resp.result as string); }); }, [send, setKeyboardLayout]); const onCancelPasteMode = useCallback(() => { setPasteModeEnabled(false); setDisableVideoFocusTrap(false); setInvalidChars([]); }, [setDisableVideoFocusTrap, setPasteModeEnabled]); const onConfirmPaste = useCallback(async () => { setPasteModeEnabled(false); setDisableVideoFocusTrap(false); if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return; if (!selectedKeyboard) return; const text = TextAreaRef.current.value; try { for (const char of text) { const keyprops = selectedKeyboard.chars[char]; if (!keyprops) continue; const { key, shift, altRight, deadKey, accentKey } = keyprops; if (!key) continue; // if this is an accented character, we need to send that accent FIRST if (accentKey) { await sendKeystroke({modifier: modifierCode(accentKey.shift, accentKey.altRight), keys: [ keys[accentKey.key] ] }) } // now send the actual key await sendKeystroke({ modifier: modifierCode(shift, altRight), keys: [ keys[key] ]}); // if what was requested was a dead key, we need to send an unmodified space to emit // just the accent character if (deadKey) { await sendKeystroke({ modifier: noModifier, keys: [ keys["Space"] ] }); } // now send a message with no keys down to "release" the keys await sendKeystroke({ modifier: 0, keys: [] }); } } catch (error) { console.error("Failed to paste text:", error); notifications.error("Failed to paste text"); } async function sendKeystroke(stroke: KeyStroke) { await new Promise((resolve, reject) => { send( "keyboardReport", hidKeyboardPayload(stroke.modifier, stroke.keys), params => { if ("error" in params) return reject(params.error); resolve(); } ); }); } }, [selectedKeyboard, rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteModeEnabled]); useEffect(() => { if (TextAreaRef.current) { TextAreaRef.current.focus(); } }, []); return (
e.stopPropagation()} onKeyDown={e => e.stopPropagation()}> e.stopPropagation()} onKeyDown={e => { e.stopPropagation(); if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); onConfirmPaste(); } else if (e.key === "Escape") { e.preventDefault(); onCancelPasteMode(); } }} onChange={e => { const value = e.target.value; const invalidChars = [ ...new Set( // @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments [...new Intl.Segmenter().segment(value)] .map(x => x.segment) .filter(char => !selectedKeyboard.chars[char]), ), ]; setInvalidChars(invalidChars); }} /> {invalidChars.length > 0 && (
The following characters won't be pasted:{" "} {invalidChars.join(", ")}
)}

Sending text using keyboard layout: {selectedKeyboard.isoCode}-{selectedKeyboard.name}

); }