diff --git a/ui/src/components/Terminal.tsx b/ui/src/components/Terminal.tsx index 5451afe..f5d662d 100644 --- a/ui/src/components/Terminal.tsx +++ b/ui/src/components/Terminal.tsx @@ -67,19 +67,19 @@ function Terminal({ }) { const enableTerminal = useUiStore(state => state.terminalType == type); const setTerminalType = useUiStore(state => state.setTerminalType); - const setDisableKeyboardFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); + const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const { instance, ref } = useXTerm({ options: TERMINAL_CONFIG }); useEffect(() => { setTimeout(() => { - setDisableKeyboardFocusTrap(enableTerminal); + setDisableVideoFocusTrap(enableTerminal); }, 500); return () => { - setDisableKeyboardFocusTrap(false); + setDisableVideoFocusTrap(false); }; - }, [ref, instance, enableTerminal, setDisableKeyboardFocusTrap, type]); + }, [enableTerminal, setDisableVideoFocusTrap]); const readyState = dataChannel.readyState; useEffect(() => { @@ -116,7 +116,7 @@ function Terminal({ const { domEvent } = e; if (domEvent.key === "Escape") { setTerminalType("none"); - setDisableKeyboardFocusTrap(false); + setDisableVideoFocusTrap(false); domEvent.preventDefault(); } }); @@ -131,7 +131,7 @@ function Terminal({ onDataHandler.dispose(); onKeyHandler.dispose(); }; - }, [instance, dataChannel, readyState, setDisableKeyboardFocusTrap, setTerminalType]); + }, [dataChannel, instance, readyState, setDisableVideoFocusTrap, setTerminalType]); useEffect(() => { if (!instance) return; @@ -158,7 +158,7 @@ function Terminal({ return () => { window.removeEventListener("resize", handleResize); }; - }, [ref, instance]); + }, [instance]); return (
{ - return { keys, modifier }; +const hidKeyboardPayload = (modifier: number, keys: number[]) => { + return { modifier, keys }; }; const modifierCode = (shift?: boolean, altRight?: boolean) => { @@ -62,49 +62,56 @@ export default function PasteModal() { const onConfirmPaste = useCallback(async () => { setPasteMode(false); setDisableVideoFocusTrap(false); + if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return; - if (!safeKeyboardLayout) return; - if (!chars[safeKeyboardLayout]) return; + const keyboard: KeyboardLayout = selectedKeyboard(safeKeyboardLayout); + if (!keyboard) return; + const text = TextAreaRef.current.value; try { for (const char of text) { - const { key, shift, altRight, deadKey, accentKey } = chars[safeKeyboardLayout][char] + const keyprops = keyboard.chars[char]; + if (!keyprops) continue; + + const { key, shift, altRight, deadKey, accentKey } = keyprops; if (!key) continue; - const keyz = [ keys[key] ]; - const modz = [ modifierCode(shift, altRight) ]; - - if (deadKey) { - keyz.push(keys["Space"]); - modz.push(noModifier); - } + // if this is an accented character, we need to send that accent FIRST if (accentKey) { - keyz.unshift(keys[accentKey.key]) - modz.unshift(modifierCode(accentKey.shift, accentKey.altRight)) + await sendKeystroke({modifier: modifierCode(accentKey.shift, accentKey.altRight), keys: [ keys[accentKey.key] ] }) } - for (const [index, kei] of keyz.entries()) { - await new Promise((resolve, reject) => { - send( - "keyboardReport", - hidKeyboardPayload([kei], modz[index]), - params => { - if ("error" in params) return reject(params.error); - send("keyboardReport", hidKeyboardPayload([], 0), params => { - if ("error" in params) return reject(params.error); - resolve(); - }); - }, - ); - }); + // 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(error); + console.error("Failed to paste text:", error); notifications.error("Failed to paste text"); } - }, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, safeKeyboardLayout]); + + 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(); + } + ); + }); + } + }, [rpcDataChannel?.readyState, safeKeyboardLayout, send, setDisableVideoFocusTrap, setPasteMode]); useEffect(() => { if (TextAreaRef.current) { @@ -154,7 +161,7 @@ export default function PasteModal() { // @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments [...new Intl.Segmenter().segment(value)] .map(x => x.segment) - .filter(char => !chars[safeKeyboardLayout][char]), + .filter(char => !selectedKeyboard(safeKeyboardLayout).chars[char]), ), ]; @@ -175,7 +182,7 @@ export default function PasteModal() {

- Sending text using keyboard layout: {layouts[safeKeyboardLayout]} + Sending text using keyboard layout: {selectedKeyboard(safeKeyboardLayout).name}

diff --git a/ui/src/components/popovers/WakeOnLan/Index.tsx b/ui/src/components/popovers/WakeOnLan/Index.tsx index f4f4951..1cf7f18 100644 --- a/ui/src/components/popovers/WakeOnLan/Index.tsx +++ b/ui/src/components/popovers/WakeOnLan/Index.tsx @@ -14,7 +14,7 @@ import AddDeviceForm from "./AddDeviceForm"; export default function WakeOnLanModal() { const [storedDevices, setStoredDevices] = useState([]); const [showAddForm, setShowAddForm] = useState(false); - const setDisableFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); + const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); @@ -24,9 +24,9 @@ export default function WakeOnLanModal() { const [addDeviceErrorMessage, setAddDeviceErrorMessage] = useState(null); const onCancelWakeOnLanModal = useCallback(() => { + setDisableVideoFocusTrap(false); close(); - setDisableFocusTrap(false); - }, [close, setDisableFocusTrap]); + }, [close, setDisableVideoFocusTrap]); const onSendMagicPacket = useCallback( (macAddress: string) => { @@ -43,12 +43,12 @@ export default function WakeOnLanModal() { } } else { notifications.success("Magic Packet sent successfully"); - setDisableFocusTrap(false); + setDisableVideoFocusTrap(false); close(); } }); }, - [close, rpcDataChannel?.readyState, send, setDisableFocusTrap], + [close, rpcDataChannel?.readyState, send, setDisableVideoFocusTrap], ); const syncStoredDevices = useCallback(() => { @@ -78,7 +78,7 @@ export default function WakeOnLanModal() { } }); }, - [storedDevices, send, syncStoredDevices], + [send, storedDevices, syncStoredDevices], ); const onAddDevice = useCallback( diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts index 6bc7e17..b7ad53e 100644 --- a/ui/src/hooks/stores.ts +++ b/ui/src/hooks/stores.ts @@ -941,5 +941,5 @@ export const useMacrosStore = create((set, get) => ({ } finally { set({ loading: false }); } - }, + } })); diff --git a/ui/src/keyboardLayouts.ts b/ui/src/keyboardLayouts.ts index 3b835b1..4ae3ad9 100644 --- a/ui/src/keyboardLayouts.ts +++ b/ui/src/keyboardLayouts.ts @@ -1,45 +1,32 @@ -import { chars as chars_fr_BE, name as name_fr_BE } from "@/keyboardLayouts/fr_BE" -import { chars as chars_cs_CZ, name as name_cs_CZ } from "@/keyboardLayouts/cs_CZ" -import { chars as chars_en_UK, name as name_en_UK } from "@/keyboardLayouts/en_UK" -import { chars as chars_en_US, name as name_en_US } from "@/keyboardLayouts/en_US" -import { chars as chars_fr_FR, name as name_fr_FR } from "@/keyboardLayouts/fr_FR" -import { chars as chars_de_DE, name as name_de_DE } from "@/keyboardLayouts/de_DE" -import { chars as chars_it_IT, name as name_it_IT } from "@/keyboardLayouts/it_IT" -import { chars as chars_nb_NO, name as name_nb_NO } from "@/keyboardLayouts/nb_NO" -import { chars as chars_es_ES, name as name_es_ES } from "@/keyboardLayouts/es_ES" -import { chars as chars_sv_SE, name as name_sv_SE } from "@/keyboardLayouts/sv_SE" -import { chars as chars_fr_CH, name as name_fr_CH } from "@/keyboardLayouts/fr_CH" -import { chars as chars_de_CH, name as name_de_CH } from "@/keyboardLayouts/de_CH" +export interface KeyStroke { modifier: number; keys: number[]; } +export interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean } +export interface KeyCombo extends KeyInfo { deadKey?: boolean, accentKey?: KeyInfo } +export interface KeyboardLayout { isoCode: string, name: string, chars: Record } -interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean } -export type KeyCombo = KeyInfo & { deadKey?: boolean, accentKey?: KeyInfo } +// to add a new layout, create a file like the above and add it to the list +import { cs_CZ } from "@/keyboardLayouts/cs_CZ" +import { de_CH } from "@/keyboardLayouts/de_CH" +import { de_DE } from "@/keyboardLayouts/de_DE" +import { en_US } from "@/keyboardLayouts/en_US" +import { en_UK } from "@/keyboardLayouts/en_UK" +import { es_ES } from "@/keyboardLayouts/es_ES" +import { fr_BE } from "@/keyboardLayouts/fr_BE" +import { fr_CH } from "@/keyboardLayouts/fr_CH" +import { fr_FR } from "@/keyboardLayouts/fr_FR" +import { it_IT } from "@/keyboardLayouts/it_IT" +import { nb_NO } from "@/keyboardLayouts/nb_NO" +import { sv_SE } from "@/keyboardLayouts/sv_SE" -export const layouts: Record = { - be_FR: name_fr_BE, - cs_CZ: name_cs_CZ, - en_UK: name_en_UK, - en_US: name_en_US, - fr_FR: name_fr_FR, - de_DE: name_de_DE, - it_IT: name_it_IT, - nb_NO: name_nb_NO, - es_ES: name_es_ES, - sv_SE: name_sv_SE, - fr_CH: name_fr_CH, - de_CH: name_de_CH, -} +export const keyboards: KeyboardLayout[] = [ cs_CZ, de_CH, de_DE, en_UK, en_US, es_ES, fr_BE, fr_CH, fr_FR, it_IT, nb_NO, sv_SE ]; -export const chars: Record> = { - be_FR: chars_fr_BE, - cs_CZ: chars_cs_CZ, - en_UK: chars_en_UK, - en_US: chars_en_US, - fr_FR: chars_fr_FR, - de_DE: chars_de_DE, - it_IT: chars_it_IT, - nb_NO: chars_nb_NO, - es_ES: chars_es_ES, - sv_SE: chars_sv_SE, - fr_CH: chars_fr_CH, - de_CH: chars_de_CH, +export const selectedKeyboard = (isoCode: string): KeyboardLayout => { + // fallback to original behaviour of en-US if no isoCode given + return keyboards.find(keyboard => keyboard.isoCode == isoCode) + ?? keyboards.find(keyboard => keyboard.isoCode == "en-US")!; }; + +export const keyboardOptions = () => { + return keyboards.map((keyboard) => { + return { label: keyboard.name, value: keyboard.isoCode } + }); +} diff --git a/ui/src/keyboardLayouts/cs_CZ.ts b/ui/src/keyboardLayouts/cs_CZ.ts index a289d75..e4f8822 100644 --- a/ui/src/keyboardLayouts/cs_CZ.ts +++ b/ui/src/keyboardLayouts/cs_CZ.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Čeština"; +const name = "Čeština"; const keyTrema = { key: "Backslash" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter @@ -13,7 +13,7 @@ const keyOverdot = { key: "Digit8", shift: true, altRight: true } // overdot (do const keyHook = { key: "Digit6", shift: true, altRight: true } // ogonoek (little hook), mark ˛ placed beneath a letter const keyCedille = { key: "Equal", shift: true, altRight: true } // accent cedille (cedilla), mark ¸ placed beneath a letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -242,3 +242,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const cs_CZ: KeyboardLayout = { + isoCode: "cs-CZ", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/de_CH.ts b/ui/src/keyboardLayouts/de_CH.ts index 06c0619..4743bcf 100644 --- a/ui/src/keyboardLayouts/de_CH.ts +++ b/ui/src/keyboardLayouts/de_CH.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Schwiizerdütsch"; +const name = "Schwiizerdütsch"; const keyTrema = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Minus", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "Equal" } // accent circonflexe (accent hat), mark ^ place const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "Equal", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -163,3 +163,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const de_CH: KeyboardLayout = { + isoCode: "de-CH", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/de_DE.ts b/ui/src/keyboardLayouts/de_DE.ts index 87a8d2e..89b7eed 100644 --- a/ui/src/keyboardLayouts/de_DE.ts +++ b/ui/src/keyboardLayouts/de_DE.ts @@ -1,12 +1,12 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Deutsch"; +const name = "Deutsch"; const keyAcute = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter const keyHat = { key: "Backquote" } // accent circonflexe (accent hat), mark ^ placed above the letter const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Â": { key: "KeyA", shift: true, accentKey: keyHat }, @@ -150,3 +150,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const de_DE: KeyboardLayout = { + isoCode: "de-DE", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/en_UK.ts b/ui/src/keyboardLayouts/en_UK.ts index ed0c8dd..a5ef779 100644 --- a/ui/src/keyboardLayouts/en_UK.ts +++ b/ui/src/keyboardLayouts/en_UK.ts @@ -1,8 +1,8 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "English (UK)"; +const name = "English (UK)"; -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, B: { key: "KeyB", shift: true }, C: { key: "KeyC", shift: true }, @@ -105,3 +105,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record + +export const en_UK: KeyboardLayout = { + isoCode: "en-UK", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/en_US.ts b/ui/src/keyboardLayouts/en_US.ts index 592bf27..cd7aaf6 100644 --- a/ui/src/keyboardLayouts/en_US.ts +++ b/ui/src/keyboardLayouts/en_US.ts @@ -1,8 +1,8 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "English (US)"; +const name = "English (US)"; -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, B: { key: "KeyB", shift: true }, C: { key: "KeyC", shift: true }, @@ -111,3 +111,9 @@ export const chars = { Insert: { key: "Insert", shift: false }, Delete: { key: "Delete", shift: false }, } as Record + +export const en_US: KeyboardLayout = { + isoCode: "en-US", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/es_ES.ts b/ui/src/keyboardLayouts/es_ES.ts index 47fc230..9eb1d6a 100644 --- a/ui/src/keyboardLayouts/es_ES.ts +++ b/ui/src/keyboardLayouts/es_ES.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Español"; +const name = "Español"; const keyTrema = { key: "Quote", shift: true } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Quote" } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accen const keyGrave = { key: "BracketRight" } // accent grave, mark ` placed above the letter const keyTilde = { key: "Key4", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -166,3 +166,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const es_ES: KeyboardLayout = { + isoCode: "es-ES", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/fr_BE.ts b/ui/src/keyboardLayouts/fr_BE.ts index 2b8b34c..bd417e0 100644 --- a/ui/src/keyboardLayouts/fr_BE.ts +++ b/ui/src/keyboardLayouts/fr_BE.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Belgisch Nederlands"; +const name = "Belgisch Nederlands"; const keyTrema = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel const keyHat = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter @@ -8,7 +8,7 @@ const keyAcute = { key: "Semicolon", altRight: true } // accent aigu (acute acce const keyGrave = { key: "Quote", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "Slash", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyQ", shift: true }, "Ä": { key: "KeyQ", shift: true, accentKey: keyTrema }, "Â": { key: "KeyQ", shift: true, accentKey: keyHat }, @@ -165,3 +165,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const fr_BE: KeyboardLayout = { + isoCode: "fr-BE", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/fr_CH.ts b/ui/src/keyboardLayouts/fr_CH.ts index cf1d3df..0ba8cb4 100644 --- a/ui/src/keyboardLayouts/fr_CH.ts +++ b/ui/src/keyboardLayouts/fr_CH.ts @@ -1,11 +1,11 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -import { chars as chars_de_CH } from "./de_CH" +import { de_CH } from "./de_CH" -export const name = "Français de Suisse"; +const name = "Français de Suisse"; -export const chars = { - ...chars_de_CH, +const chars = { + ...de_CH.chars, "è": { key: "BracketLeft" }, "ü": { key: "BracketLeft", shift: true }, "é": { key: "Semicolon" }, @@ -13,3 +13,9 @@ export const chars = { "à": { key: "Quote" }, "ä": { key: "Quote", shift: true }, } as Record; + +export const fr_CH: KeyboardLayout = { + isoCode: "fr-CH", + name: name, + chars: chars +}; diff --git a/ui/src/keyboardLayouts/fr_FR.ts b/ui/src/keyboardLayouts/fr_FR.ts index 27a03fd..29d5104 100644 --- a/ui/src/keyboardLayouts/fr_FR.ts +++ b/ui/src/keyboardLayouts/fr_FR.ts @@ -1,11 +1,11 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Français"; +const name = "Français"; const keyTrema = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel const keyHat = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter -export const chars = { +const chars = { A: { key: "KeyQ", shift: true }, "Ä": { key: "KeyQ", shift: true, accentKey: keyTrema }, "Â": { key: "KeyQ", shift: true, accentKey: keyHat }, @@ -137,3 +137,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const fr_FR: KeyboardLayout = { + isoCode: "fr-FR", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/it_IT.ts b/ui/src/keyboardLayouts/it_IT.ts index 9de61c5..0ff6e24 100644 --- a/ui/src/keyboardLayouts/it_IT.ts +++ b/ui/src/keyboardLayouts/it_IT.ts @@ -1,8 +1,8 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Italiano"; +const name = "Italiano"; -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, B: { key: "KeyB", shift: true }, C: { key: "KeyC", shift: true }, @@ -111,3 +111,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const it_IT: KeyboardLayout = { + isoCode: "it-IT", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/nb_NO.ts b/ui/src/keyboardLayouts/nb_NO.ts index 83918b2..4dae9c8 100644 --- a/ui/src/keyboardLayouts/nb_NO.ts +++ b/ui/src/keyboardLayouts/nb_NO.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Norsk bokmål"; +const name = "Norsk bokmål"; const keyTrema = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Equal", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accen const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -165,3 +165,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const nb_NO: KeyboardLayout = { + isoCode: "nb-NO", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/sv_SE.ts b/ui/src/keyboardLayouts/sv_SE.ts index 75197cb..fbde3d0 100644 --- a/ui/src/keyboardLayouts/sv_SE.ts +++ b/ui/src/keyboardLayouts/sv_SE.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Svenska"; +const name = "Svenska"; const keyTrema = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accen const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Â": { key: "KeyA", shift: true, accentKey: keyHat }, @@ -162,3 +162,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const sv_SE: KeyboardLayout = { + isoCode: "sv-SE", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardMappings.ts b/ui/src/keyboardMappings.ts index 891b96e..bb24fbb 100644 --- a/ui/src/keyboardMappings.ts +++ b/ui/src/keyboardMappings.ts @@ -1,17 +1,19 @@ // Key codes and modifiers correspond to definitions in the // [Linux USB HID gadget driver](https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt) +// [Section 10. Keyboard/Keypad Page 0x07](https://usb.org/sites/default/files/hut1_21.pdf) export const keys = { ArrowDown: 0x51, ArrowLeft: 0x50, ArrowRight: 0x4f, ArrowUp: 0x52, - Backquote: 0x35, + Backquote: 0x35, // aka Grave Backslash: 0x31, Backspace: 0x2a, - BracketLeft: 0x2f, - BracketRight: 0x30, + BracketLeft: 0x2f, // aka LeftBrace + BracketRight: 0x30, // aka RightBrace CapsLock: 0x39, Comma: 0x36, + Compose: 0x65, ContextMenu: 0, Delete: 0x4c, Digit0: 0x27, @@ -40,10 +42,21 @@ export const keys = { F10: 0x43, F11: 0x44, F12: 0x45, - F13: 0x68, + F14: 0x69, + F15: 0x6a, + F16: 0x6b, + F17: 0x6c, + F18: 0x6d, + F19: 0x6e, + F20: 0x6f, + F21: 0x70, + F22: 0x71, + F23: 0x72, + F24: 0x73, Home: 0x4a, + HashTilde: 0x32, // non-US # and ~ Insert: 0x49, - IntlBackslash: 0x64, + IntlBackslash: 0x64, // non-US \ and | KeyA: 0x04, KeyB: 0x05, KeyC: 0x06, @@ -72,30 +85,35 @@ export const keys = { KeyZ: 0x1d, KeypadExclamation: 0xcf, Minus: 0x2d, - NumLock: 0x53, - Numpad0: 0x62, - Numpad1: 0x59, - Numpad2: 0x5a, - Numpad3: 0x5b, - Numpad4: 0x5c, + None: 0x00, + NumLock: 0x53, // and Clear + Numpad0: 0x62, // and Insert + Numpad1: 0x59, // and End + Numpad2: 0x5a, // and Down Arrow + Numpad3: 0x5b, // and Page Down + Numpad4: 0x5c, // and Left Arrow Numpad5: 0x5d, - Numpad6: 0x5e, - Numpad7: 0x5f, - Numpad8: 0x60, - Numpad9: 0x61, + Numpad6: 0x5e, // and Right Arrow + Numpad7: 0x5f, // and Home + Numpad8: 0x60, // and Up Arrow + Numpad9: 0x61, // and Page Up NumpadAdd: 0x57, + NumpadComma: 0x85, + NumpadDecimal: 0x63, NumpadDivide: 0x54, NumpadEnter: 0x58, NumpadEqual: 0x67, + NumpadLeftParen: 0xb6, NumpadMultiply: 0x55, + NumpadRightParen: 0xb7, NumpadSubtract: 0x56, - NumpadDecimal: 0x63, PageDown: 0x4e, PageUp: 0x4b, Period: 0x37, PrintScreen: 0x46, Pause: 0x48, - Quote: 0x34, + Power: 0x66, + Quote: 0x34, // aka Single Quote or Apostrophe ScrollLock: 0x47, Semicolon: 0x33, Slash: 0x38, diff --git a/ui/src/routes/devices.$id.settings.keyboard.tsx b/ui/src/routes/devices.$id.settings.keyboard.tsx index 6e68f81..e797ce7 100644 --- a/ui/src/routes/devices.$id.settings.keyboard.tsx +++ b/ui/src/routes/devices.$id.settings.keyboard.tsx @@ -4,7 +4,7 @@ import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import notifications from "@/notifications"; import { SettingsPageHeader } from "@components/SettingsPageheader"; -import { layouts } from "@/keyboardLayouts"; +import { keyboardOptions } from "@/keyboardLayouts"; import { Checkbox } from "@/components/Checkbox"; import { SelectMenuBasic } from "../components/SelectMenuBasic"; @@ -32,7 +32,7 @@ export default function SettingsKeyboardRoute() { return "en_US"; }, [keyboardLayout]); - const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } }) + const layoutOptions = keyboardOptions(); const ledSyncOptions = [ { value: "auto", label: "Automatic" }, { value: "browser", label: "Browser Only" }, diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx index 1e888f6..b729bf0 100644 --- a/ui/src/routes/devices.$id.settings.tsx +++ b/ui/src/routes/devices.$id.settings.tsx @@ -79,7 +79,7 @@ export default function SettingsRoute() { return () => { setDisableVideoFocusTrap(false); }; - }, [setDisableVideoFocusTrap, sendKeyboardEvent]); + }, [sendKeyboardEvent, setDisableVideoFocusTrap]); return (
diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx index 8cdb5b3..1785bcd 100644 --- a/ui/src/routes/devices.$id.tsx +++ b/ui/src/routes/devices.$id.tsx @@ -707,7 +707,7 @@ export default function KvmIdRoute() { }, [diskChannel, file]); // System update - const disableKeyboardFocusTrap = useUiStore(state => state.disableVideoFocusTrap); + const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap); const [kvmTerminal, setKvmTerminal] = useState(null); const [serialConsole, setSerialConsole] = useState(null); @@ -805,7 +805,7 @@ export default function KvmIdRoute() { )}