This commit is contained in:
Marc Brooks 2025-06-20 14:19:18 -05:00 committed by GitHub
commit 637fa8d723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 232 additions and 148 deletions

View File

@ -67,19 +67,19 @@ function Terminal({
}) { }) {
const enableTerminal = useUiStore(state => state.terminalType == type); const enableTerminal = useUiStore(state => state.terminalType == type);
const setTerminalType = useUiStore(state => state.setTerminalType); 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 }); const { instance, ref } = useXTerm({ options: TERMINAL_CONFIG });
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
setDisableKeyboardFocusTrap(enableTerminal); setDisableVideoFocusTrap(enableTerminal);
}, 500); }, 500);
return () => { return () => {
setDisableKeyboardFocusTrap(false); setDisableVideoFocusTrap(false);
}; };
}, [ref, instance, enableTerminal, setDisableKeyboardFocusTrap, type]); }, [enableTerminal, setDisableVideoFocusTrap]);
const readyState = dataChannel.readyState; const readyState = dataChannel.readyState;
useEffect(() => { useEffect(() => {
@ -116,7 +116,7 @@ function Terminal({
const { domEvent } = e; const { domEvent } = e;
if (domEvent.key === "Escape") { if (domEvent.key === "Escape") {
setTerminalType("none"); setTerminalType("none");
setDisableKeyboardFocusTrap(false); setDisableVideoFocusTrap(false);
domEvent.preventDefault(); domEvent.preventDefault();
} }
}); });
@ -131,7 +131,7 @@ function Terminal({
onDataHandler.dispose(); onDataHandler.dispose();
onKeyHandler.dispose(); onKeyHandler.dispose();
}; };
}, [instance, dataChannel, readyState, setDisableKeyboardFocusTrap, setTerminalType]); }, [dataChannel, instance, readyState, setDisableVideoFocusTrap, setTerminalType]);
useEffect(() => { useEffect(() => {
if (!instance) return; if (!instance) return;
@ -158,7 +158,7 @@ function Terminal({
return () => { return () => {
window.removeEventListener("resize", handleResize); window.removeEventListener("resize", handleResize);
}; };
}, [ref, instance]); }, [instance]);
return ( return (
<div <div

View File

@ -10,11 +10,11 @@ import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/stores"; import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings"; import { keys, modifiers } from "@/keyboardMappings";
import { layouts, chars } from "@/keyboardLayouts"; import { KeyStroke, KeyboardLayout, selectedKeyboard } from "@/keyboardLayouts";
import notifications from "@/notifications"; import notifications from "@/notifications";
const hidKeyboardPayload = (keys: number[], modifier: number) => { const hidKeyboardPayload = (modifier: number, keys: number[]) => {
return { keys, modifier }; return { modifier, keys };
}; };
const modifierCode = (shift?: boolean, altRight?: boolean) => { const modifierCode = (shift?: boolean, altRight?: boolean) => {
@ -62,49 +62,56 @@ export default function PasteModal() {
const onConfirmPaste = useCallback(async () => { const onConfirmPaste = useCallback(async () => {
setPasteMode(false); setPasteMode(false);
setDisableVideoFocusTrap(false); setDisableVideoFocusTrap(false);
if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return; if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return;
if (!safeKeyboardLayout) return; const keyboard: KeyboardLayout = selectedKeyboard(safeKeyboardLayout);
if (!chars[safeKeyboardLayout]) return; if (!keyboard) return;
const text = TextAreaRef.current.value; const text = TextAreaRef.current.value;
try { try {
for (const char of text) { 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; if (!key) continue;
const keyz = [ keys[key] ]; // if this is an accented character, we need to send that accent FIRST
const modz = [ modifierCode(shift, altRight) ];
if (deadKey) {
keyz.push(keys["Space"]);
modz.push(noModifier);
}
if (accentKey) { if (accentKey) {
keyz.unshift(keys[accentKey.key]) await sendKeystroke({modifier: modifierCode(accentKey.shift, accentKey.altRight), keys: [ keys[accentKey.key] ] })
modz.unshift(modifierCode(accentKey.shift, accentKey.altRight))
} }
for (const [index, kei] of keyz.entries()) { // now send the actual key
await new Promise<void>((resolve, reject) => { await sendKeystroke({ modifier: modifierCode(shift, altRight), keys: [ keys[key] ]});
send(
"keyboardReport", // if what was requested was a dead key, we need to send an unmodified space to emit
hidKeyboardPayload([kei], modz[index]), // just the accent character
params => { if (deadKey) {
if ("error" in params) return reject(params.error); await sendKeystroke({ modifier: noModifier, keys: [ keys["Space"] ] });
send("keyboardReport", hidKeyboardPayload([], 0), params => {
if ("error" in params) return reject(params.error);
resolve();
});
},
);
});
} }
// now send a message with no keys down to "release" the keys
await sendKeystroke({ modifier: 0, keys: [] });
} }
} catch (error) { } catch (error) {
console.error(error); console.error("Failed to paste text:", error);
notifications.error("Failed to paste text"); notifications.error("Failed to paste text");
} }
}, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, safeKeyboardLayout]);
async function sendKeystroke(stroke: KeyStroke) {
await new Promise<void>((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(() => { useEffect(() => {
if (TextAreaRef.current) { if (TextAreaRef.current) {
@ -154,7 +161,7 @@ export default function PasteModal() {
// @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments // @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments
[...new Intl.Segmenter().segment(value)] [...new Intl.Segmenter().segment(value)]
.map(x => x.segment) .map(x => x.segment)
.filter(char => !chars[safeKeyboardLayout][char]), .filter(char => !selectedKeyboard(safeKeyboardLayout).chars[char]),
), ),
]; ];
@ -175,7 +182,7 @@ export default function PasteModal() {
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<p className="text-xs text-slate-600 dark:text-slate-400"> <p className="text-xs text-slate-600 dark:text-slate-400">
Sending text using keyboard layout: {layouts[safeKeyboardLayout]} Sending text using keyboard layout: {selectedKeyboard(safeKeyboardLayout).name}
</p> </p>
</div> </div>
</div> </div>

View File

@ -14,7 +14,7 @@ import AddDeviceForm from "./AddDeviceForm";
export default function WakeOnLanModal() { export default function WakeOnLanModal() {
const [storedDevices, setStoredDevices] = useState<StoredDevice[]>([]); const [storedDevices, setStoredDevices] = useState<StoredDevice[]>([]);
const [showAddForm, setShowAddForm] = useState(false); const [showAddForm, setShowAddForm] = useState(false);
const setDisableFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap);
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
@ -24,9 +24,9 @@ export default function WakeOnLanModal() {
const [addDeviceErrorMessage, setAddDeviceErrorMessage] = useState<string | null>(null); const [addDeviceErrorMessage, setAddDeviceErrorMessage] = useState<string | null>(null);
const onCancelWakeOnLanModal = useCallback(() => { const onCancelWakeOnLanModal = useCallback(() => {
setDisableVideoFocusTrap(false);
close(); close();
setDisableFocusTrap(false); }, [close, setDisableVideoFocusTrap]);
}, [close, setDisableFocusTrap]);
const onSendMagicPacket = useCallback( const onSendMagicPacket = useCallback(
(macAddress: string) => { (macAddress: string) => {
@ -43,12 +43,12 @@ export default function WakeOnLanModal() {
} }
} else { } else {
notifications.success("Magic Packet sent successfully"); notifications.success("Magic Packet sent successfully");
setDisableFocusTrap(false); setDisableVideoFocusTrap(false);
close(); close();
} }
}); });
}, },
[close, rpcDataChannel?.readyState, send, setDisableFocusTrap], [close, rpcDataChannel?.readyState, send, setDisableVideoFocusTrap],
); );
const syncStoredDevices = useCallback(() => { const syncStoredDevices = useCallback(() => {
@ -78,7 +78,7 @@ export default function WakeOnLanModal() {
} }
}); });
}, },
[storedDevices, send, syncStoredDevices], [send, storedDevices, syncStoredDevices],
); );
const onAddDevice = useCallback( const onAddDevice = useCallback(

View File

@ -941,5 +941,5 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
} finally { } finally {
set({ loading: false }); set({ loading: false });
} }
}, }
})); }));

View File

@ -1,45 +1,32 @@
import { chars as chars_fr_BE, name as name_fr_BE } from "@/keyboardLayouts/fr_BE" export interface KeyStroke { modifier: number; keys: number[]; }
import { chars as chars_cs_CZ, name as name_cs_CZ } from "@/keyboardLayouts/cs_CZ" export interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean }
import { chars as chars_en_UK, name as name_en_UK } from "@/keyboardLayouts/en_UK" export interface KeyCombo extends KeyInfo { deadKey?: boolean, accentKey?: KeyInfo }
import { chars as chars_en_US, name as name_en_US } from "@/keyboardLayouts/en_US" export interface KeyboardLayout { isoCode: string, name: string, chars: Record<string, KeyCombo> }
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"
interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean } // to add a new layout, create a file like the above and add it to the list
export type KeyCombo = KeyInfo & { deadKey?: boolean, accentKey?: KeyInfo } 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<string, string> = { 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 ];
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 chars: Record<string, Record<string, KeyCombo>> = { export const selectedKeyboard = (isoCode: string): KeyboardLayout => {
be_FR: chars_fr_BE, // fallback to original behaviour of en-US if no isoCode given
cs_CZ: chars_cs_CZ, return keyboards.find(keyboard => keyboard.isoCode == isoCode)
en_UK: chars_en_UK, ?? keyboards.find(keyboard => keyboard.isoCode == "en-US")!;
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 keyboardOptions = () => {
return keyboards.map((keyboard) => {
return { label: keyboard.name, value: keyboard.isoCode }
});
}

View File

@ -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 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 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 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 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 }, A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute },
@ -242,3 +242,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const cs_CZ: KeyboardLayout = {
isoCode: "cs-CZ",
name: name,
chars: chars
};

View File

@ -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 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 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 keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
const keyTilde = { key: "Equal", altRight: true } // tilde, 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 }, A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute },
@ -163,3 +163,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const de_CH: KeyboardLayout = {
isoCode: "de-CH",
name: name,
chars: chars
};

View File

@ -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 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 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 const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
export const chars = { const chars = {
A: { key: "KeyA", shift: true }, A: { key: "KeyA", shift: true },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat }, "Â": { key: "KeyA", shift: true, accentKey: keyHat },
@ -150,3 +150,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const de_DE: KeyboardLayout = {
isoCode: "de-DE",
name: name,
chars: chars
};

View File

@ -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 }, A: { key: "KeyA", shift: true },
B: { key: "KeyB", shift: true }, B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true }, C: { key: "KeyC", shift: true },
@ -105,3 +105,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo> } as Record<string, KeyCombo>
export const en_UK: KeyboardLayout = {
isoCode: "en-UK",
name: name,
chars: chars
};

View File

@ -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 }, A: { key: "KeyA", shift: true },
B: { key: "KeyB", shift: true }, B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true }, C: { key: "KeyC", shift: true },
@ -111,3 +111,9 @@ export const chars = {
Insert: { key: "Insert", shift: false }, Insert: { key: "Insert", shift: false },
Delete: { key: "Delete", shift: false }, Delete: { key: "Delete", shift: false },
} as Record<string, KeyCombo> } as Record<string, KeyCombo>
export const en_US: KeyboardLayout = {
isoCode: "en-US",
name: name,
chars: chars
};

View File

@ -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 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 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 keyGrave = { key: "BracketRight" } // accent grave, mark ` placed above the letter
const keyTilde = { key: "Key4", altRight: true } // tilde, 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 }, A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute },
@ -166,3 +166,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const es_ES: KeyboardLayout = {
isoCode: "es-ES",
name: name,
chars: chars
};

View File

@ -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 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 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 keyGrave = { key: "Quote", shift: true } // accent grave, mark ` placed above the letter
const keyTilde = { key: "Slash", altRight: true } // tilde, 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 }, A: { key: "KeyQ", shift: true },
"Ä": { key: "KeyQ", shift: true, accentKey: keyTrema }, "Ä": { key: "KeyQ", shift: true, accentKey: keyTrema },
"Â": { key: "KeyQ", shift: true, accentKey: keyHat }, "Â": { key: "KeyQ", shift: true, accentKey: keyHat },
@ -165,3 +165,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const fr_BE: KeyboardLayout = {
isoCode: "fr-BE",
name: name,
chars: chars
};

View File

@ -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 = { const chars = {
...chars_de_CH, ...de_CH.chars,
"è": { key: "BracketLeft" }, "è": { key: "BracketLeft" },
"ü": { key: "BracketLeft", shift: true }, "ü": { key: "BracketLeft", shift: true },
"é": { key: "Semicolon" }, "é": { key: "Semicolon" },
@ -13,3 +13,9 @@ export const chars = {
"à": { key: "Quote" }, "à": { key: "Quote" },
"ä": { key: "Quote", shift: true }, "ä": { key: "Quote", shift: true },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const fr_CH: KeyboardLayout = {
isoCode: "fr-CH",
name: name,
chars: chars
};

View File

@ -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 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 const keyHat = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter
export const chars = { const chars = {
A: { key: "KeyQ", shift: true }, A: { key: "KeyQ", shift: true },
"Ä": { key: "KeyQ", shift: true, accentKey: keyTrema }, "Ä": { key: "KeyQ", shift: true, accentKey: keyTrema },
"Â": { key: "KeyQ", shift: true, accentKey: keyHat }, "Â": { key: "KeyQ", shift: true, accentKey: keyHat },
@ -137,3 +137,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const fr_FR: KeyboardLayout = {
isoCode: "fr-FR",
name: name,
chars: chars
};

View File

@ -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 }, A: { key: "KeyA", shift: true },
B: { key: "KeyB", shift: true }, B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true }, C: { key: "KeyC", shift: true },
@ -111,3 +111,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const it_IT: KeyboardLayout = {
isoCode: "it-IT",
name: name,
chars: chars
};

View File

@ -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 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 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 keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
const keyTilde = { key: "BracketRight", altRight: true } // tilde, 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 }, A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute },
@ -165,3 +165,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const nb_NO: KeyboardLayout = {
isoCode: "nb-NO",
name: name,
chars: chars
};

View File

@ -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 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 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 keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
const keyTilde = { key: "BracketRight", altRight: true } // tilde, 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 }, A: { key: "KeyA", shift: true },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat }, "Â": { key: "KeyA", shift: true, accentKey: keyHat },
@ -162,3 +162,9 @@ export const chars = {
Enter: { key: "Enter" }, Enter: { key: "Enter" },
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const sv_SE: KeyboardLayout = {
isoCode: "sv-SE",
name: name,
chars: chars
};

View File

@ -1,17 +1,19 @@
// Key codes and modifiers correspond to definitions in the // Key codes and modifiers correspond to definitions in the
// [Linux USB HID gadget driver](https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt) // [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 = { export const keys = {
ArrowDown: 0x51, ArrowDown: 0x51,
ArrowLeft: 0x50, ArrowLeft: 0x50,
ArrowRight: 0x4f, ArrowRight: 0x4f,
ArrowUp: 0x52, ArrowUp: 0x52,
Backquote: 0x35, Backquote: 0x35, // aka Grave
Backslash: 0x31, Backslash: 0x31,
Backspace: 0x2a, Backspace: 0x2a,
BracketLeft: 0x2f, BracketLeft: 0x2f, // aka LeftBrace
BracketRight: 0x30, BracketRight: 0x30, // aka RightBrace
CapsLock: 0x39, CapsLock: 0x39,
Comma: 0x36, Comma: 0x36,
Compose: 0x65,
ContextMenu: 0, ContextMenu: 0,
Delete: 0x4c, Delete: 0x4c,
Digit0: 0x27, Digit0: 0x27,
@ -40,10 +42,21 @@ export const keys = {
F10: 0x43, F10: 0x43,
F11: 0x44, F11: 0x44,
F12: 0x45, 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, Home: 0x4a,
HashTilde: 0x32, // non-US # and ~
Insert: 0x49, Insert: 0x49,
IntlBackslash: 0x64, IntlBackslash: 0x64, // non-US \ and |
KeyA: 0x04, KeyA: 0x04,
KeyB: 0x05, KeyB: 0x05,
KeyC: 0x06, KeyC: 0x06,
@ -72,30 +85,35 @@ export const keys = {
KeyZ: 0x1d, KeyZ: 0x1d,
KeypadExclamation: 0xcf, KeypadExclamation: 0xcf,
Minus: 0x2d, Minus: 0x2d,
NumLock: 0x53, None: 0x00,
Numpad0: 0x62, NumLock: 0x53, // and Clear
Numpad1: 0x59, Numpad0: 0x62, // and Insert
Numpad2: 0x5a, Numpad1: 0x59, // and End
Numpad3: 0x5b, Numpad2: 0x5a, // and Down Arrow
Numpad4: 0x5c, Numpad3: 0x5b, // and Page Down
Numpad4: 0x5c, // and Left Arrow
Numpad5: 0x5d, Numpad5: 0x5d,
Numpad6: 0x5e, Numpad6: 0x5e, // and Right Arrow
Numpad7: 0x5f, Numpad7: 0x5f, // and Home
Numpad8: 0x60, Numpad8: 0x60, // and Up Arrow
Numpad9: 0x61, Numpad9: 0x61, // and Page Up
NumpadAdd: 0x57, NumpadAdd: 0x57,
NumpadComma: 0x85,
NumpadDecimal: 0x63,
NumpadDivide: 0x54, NumpadDivide: 0x54,
NumpadEnter: 0x58, NumpadEnter: 0x58,
NumpadEqual: 0x67, NumpadEqual: 0x67,
NumpadLeftParen: 0xb6,
NumpadMultiply: 0x55, NumpadMultiply: 0x55,
NumpadRightParen: 0xb7,
NumpadSubtract: 0x56, NumpadSubtract: 0x56,
NumpadDecimal: 0x63,
PageDown: 0x4e, PageDown: 0x4e,
PageUp: 0x4b, PageUp: 0x4b,
Period: 0x37, Period: 0x37,
PrintScreen: 0x46, PrintScreen: 0x46,
Pause: 0x48, Pause: 0x48,
Quote: 0x34, Power: 0x66,
Quote: 0x34, // aka Single Quote or Apostrophe
ScrollLock: 0x47, ScrollLock: 0x47,
Semicolon: 0x33, Semicolon: 0x33,
Slash: 0x38, Slash: 0x38,

View File

@ -4,7 +4,7 @@ import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { layouts } from "@/keyboardLayouts"; import { keyboardOptions } from "@/keyboardLayouts";
import { Checkbox } from "@/components/Checkbox"; import { Checkbox } from "@/components/Checkbox";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SelectMenuBasic } from "../components/SelectMenuBasic";
@ -32,7 +32,7 @@ export default function SettingsKeyboardRoute() {
return "en_US"; return "en_US";
}, [keyboardLayout]); }, [keyboardLayout]);
const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } }) const layoutOptions = keyboardOptions();
const ledSyncOptions = [ const ledSyncOptions = [
{ value: "auto", label: "Automatic" }, { value: "auto", label: "Automatic" },
{ value: "browser", label: "Browser Only" }, { value: "browser", label: "Browser Only" },

View File

@ -79,7 +79,7 @@ export default function SettingsRoute() {
return () => { return () => {
setDisableVideoFocusTrap(false); setDisableVideoFocusTrap(false);
}; };
}, [setDisableVideoFocusTrap, sendKeyboardEvent]); }, [sendKeyboardEvent, setDisableVideoFocusTrap]);
return ( return (
<div className="pointer-events-auto relative mx-auto max-w-4xl translate-x-0 transform text-left dark:text-white"> <div className="pointer-events-auto relative mx-auto max-w-4xl translate-x-0 transform text-left dark:text-white">

View File

@ -707,7 +707,7 @@ export default function KvmIdRoute() {
}, [diskChannel, file]); }, [diskChannel, file]);
// System update // System update
const disableKeyboardFocusTrap = useUiStore(state => state.disableVideoFocusTrap); const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap);
const [kvmTerminal, setKvmTerminal] = useState<RTCDataChannel | null>(null); const [kvmTerminal, setKvmTerminal] = useState<RTCDataChannel | null>(null);
const [serialConsole, setSerialConsole] = useState<RTCDataChannel | null>(null); const [serialConsole, setSerialConsole] = useState<RTCDataChannel | null>(null);
@ -805,7 +805,7 @@ export default function KvmIdRoute() {
)} )}
<div className="relative h-full"> <div className="relative h-full">
<FocusTrap <FocusTrap
paused={disableKeyboardFocusTrap} paused={disableVideoFocusTrap}
focusTrapOptions={{ focusTrapOptions={{
allowOutsideClick: true, allowOutsideClick: true,
escapeDeactivates: false, escapeDeactivates: false,