Skip to content

Commit 6a7577d

Browse files
committed
refactor(ui): Refactor the keyboardLayouts
Add missing keyboard mappings for most layouts Change pasteModel.tsx to use the new structure and vastly clarified the way that keys are emitted. Make each layout export just the KeyboardLayout object (which is a package of isoCode, name, and chars) Made keyboardLayouts.ts export a function to select keyboard by `isoCode`, export the keyboards as label . value pairs (for a select list) and the list of keyboards. Changed devices.$id.settings.keyboard.tsx use the exported keyboard option list.
1 parent 0cee284 commit 6a7577d

File tree

17 files changed

+218
-136
lines changed

17 files changed

+218
-136
lines changed

ui/src/components/popovers/PasteModal.tsx

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import { SettingsPageHeader } from "@components/SettingsPageheader";
1010
import { useJsonRpc } from "@/hooks/useJsonRpc";
1111
import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/stores";
1212
import { keys, modifiers } from "@/keyboardMappings";
13-
import { layouts, chars } from "@/keyboardLayouts";
13+
import { KeyStroke, KeyboardLayout, selectedKeyboard } from "@/keyboardLayouts";
1414
import notifications from "@/notifications";
1515

16-
const hidKeyboardPayload = (keys: number[], modifier: number) => {
17-
return { keys, modifier };
16+
const hidKeyboardPayload = (modifier: number, keys: number[]) => {
17+
return { modifier, keys };
1818
};
1919

2020
const modifierCode = (shift?: boolean, altRight?: boolean) => {
@@ -57,47 +57,53 @@ export default function PasteModal() {
5757
setDisableVideoFocusTrap(false);
5858
if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return;
5959
if (!keyboardLayout) return;
60-
if (!chars[keyboardLayout]) return;
60+
const keyboard: KeyboardLayout = selectedKeyboard(keyboardLayout);
61+
if (!keyboard) return;
6162

6263
const text = TextAreaRef.current.value;
6364

6465
try {
6566
for (const char of text) {
66-
const { key, shift, altRight, deadKey, accentKey } = chars[keyboardLayout][char]
67+
const keyprops = keyboard.chars[char];
68+
if (!keyprops) continue;
69+
70+
const { key, shift, altRight, deadKey, accentKey } = keyprops;
6771
if (!key) continue;
6872

69-
const keyz = [ keys[key] ];
70-
const modz = [ modifierCode(shift, altRight) ];
71-
72-
if (deadKey) {
73-
keyz.push(keys["Space"]);
74-
modz.push(noModifier);
75-
}
76-
if (accentKey) {
77-
keyz.unshift(keys[accentKey.key])
78-
modz.unshift(modifierCode(accentKey.shift, accentKey.altRight))
79-
}
80-
81-
for (const [index, kei] of keyz.entries()) {
82-
await new Promise<void>((resolve, reject) => {
83-
send(
84-
"keyboardReport",
85-
hidKeyboardPayload([kei], modz[index]),
86-
params => {
87-
if ("error" in params) return reject(params.error);
88-
send("keyboardReport", hidKeyboardPayload([], 0), params => {
89-
if ("error" in params) return reject(params.error);
90-
resolve();
91-
});
92-
},
93-
);
94-
});
95-
}
73+
// if this is an accented character, we need to send that accent FIRST
74+
if (accentKey) {
75+
await sendKeystroke({modifier: modifierCode(accentKey.shift, accentKey.altRight), keys: [ keys[accentKey.key] ] })
76+
}
77+
78+
// now send the actual key
79+
await sendKeystroke({ modifier: modifierCode(shift, altRight), keys: [ keys[key] ]});
80+
81+
// if what was requested was a dead key, we need to send an unmodified space to emit
82+
// just the accent character
83+
if (deadKey) {
84+
await sendKeystroke({ modifier: noModifier, keys: [ keys["Space"] ] });
85+
}
86+
87+
// now send a message with no keys down to "release" the keys
88+
await sendKeystroke({ modifier: 0, keys: [] });
9689
}
9790
} catch (error) {
98-
console.error(error);
91+
console.error("Failed to paste text:", error);
9992
notifications.error("Failed to paste text");
10093
}
94+
95+
async function sendKeystroke(stroke: KeyStroke) {
96+
await new Promise<void>((resolve, reject) => {
97+
send(
98+
"keyboardReport",
99+
hidKeyboardPayload(stroke.modifier, stroke.keys),
100+
params => {
101+
if ("error" in params) return reject(params.error);
102+
resolve();
103+
}
104+
);
105+
});
106+
}
101107
}, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, keyboardLayout]);
102108

103109
useEffect(() => {
@@ -148,7 +154,7 @@ export default function PasteModal() {
148154
// @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments
149155
[...new Intl.Segmenter().segment(value)]
150156
.map(x => x.segment)
151-
.filter(char => !chars[keyboardLayout][char]),
157+
.filter(char => !selectedKeyboard(keyboardLayout).chars[char]),
152158
),
153159
];
154160

@@ -167,11 +173,11 @@ export default function PasteModal() {
167173
)}
168174
</div>
169175
</div>
170-
<div className="space-y-4">
176+
<div className="space-y-4">
171177
<p className="text-xs text-slate-600 dark:text-slate-400">
172-
Sending text using keyboard layout: {layouts[keyboardLayout]}
178+
Sending text using keyboard layout: {selectedKeyboard(keyboardLayout).name}
173179
</p>
174-
</div>
180+
</div>
175181
</div>
176182
</div>
177183
</div>

ui/src/hooks/stores.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,5 +884,5 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
884884
} finally {
885885
set({ loading: false });
886886
}
887-
},
887+
}
888888
}));

ui/src/keyboardLayouts.ts

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,31 @@
1-
import { chars as chars_fr_BE, name as name_fr_BE } from "@/keyboardLayouts/fr_BE"
2-
import { chars as chars_cs_CZ, name as name_cs_CZ } from "@/keyboardLayouts/cs_CZ"
3-
import { chars as chars_en_UK, name as name_en_UK } from "@/keyboardLayouts/en_UK"
4-
import { chars as chars_en_US, name as name_en_US } from "@/keyboardLayouts/en_US"
5-
import { chars as chars_fr_FR, name as name_fr_FR } from "@/keyboardLayouts/fr_FR"
6-
import { chars as chars_de_DE, name as name_de_DE } from "@/keyboardLayouts/de_DE"
7-
import { chars as chars_it_IT, name as name_it_IT } from "@/keyboardLayouts/it_IT"
8-
import { chars as chars_nb_NO, name as name_nb_NO } from "@/keyboardLayouts/nb_NO"
9-
import { chars as chars_es_ES, name as name_es_ES } from "@/keyboardLayouts/es_ES"
10-
import { chars as chars_sv_SE, name as name_sv_SE } from "@/keyboardLayouts/sv_SE"
11-
import { chars as chars_fr_CH, name as name_fr_CH } from "@/keyboardLayouts/fr_CH"
12-
import { chars as chars_de_CH, name as name_de_CH } from "@/keyboardLayouts/de_CH"
1+
export interface KeyStroke { modifier: number; keys: number[]; }
2+
export interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean }
3+
export interface KeyCombo extends KeyInfo { deadKey?: boolean, accentKey?: KeyInfo }
4+
export interface KeyboardLayout { isoCode: string, name: string, chars: Record<string, KeyCombo> }
135

14-
interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean }
15-
export type KeyCombo = KeyInfo & { deadKey?: boolean, accentKey?: KeyInfo }
6+
// to add a new layout, create a file like the above and add it to the list
7+
import { cs_CZ } from "@/keyboardLayouts/cs_CZ"
8+
import { de_CH } from "@/keyboardLayouts/de_CH"
9+
import { de_DE } from "@/keyboardLayouts/de_DE"
10+
import { en_US } from "@/keyboardLayouts/en_US"
11+
import { en_UK } from "@/keyboardLayouts/en_UK"
12+
import { es_ES } from "@/keyboardLayouts/es_ES"
13+
import { fr_BE } from "@/keyboardLayouts/fr_BE"
14+
import { fr_CH } from "@/keyboardLayouts/fr_CH"
15+
import { fr_FR } from "@/keyboardLayouts/fr_FR"
16+
import { it_IT } from "@/keyboardLayouts/it_IT"
17+
import { nb_NO } from "@/keyboardLayouts/nb_NO"
18+
import { sv_SE } from "@/keyboardLayouts/sv_SE"
1619

17-
export const layouts: Record<string, string> = {
18-
be_FR: name_fr_BE,
19-
cs_CZ: name_cs_CZ,
20-
en_UK: name_en_UK,
21-
en_US: name_en_US,
22-
fr_FR: name_fr_FR,
23-
de_DE: name_de_DE,
24-
it_IT: name_it_IT,
25-
nb_NO: name_nb_NO,
26-
es_ES: name_es_ES,
27-
sv_SE: name_sv_SE,
28-
fr_CH: name_fr_CH,
29-
de_CH: name_de_CH,
30-
}
20+
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 ];
3121

32-
export const chars: Record<string, Record<string, KeyCombo>> = {
33-
be_FR: chars_fr_BE,
34-
cs_CZ: chars_cs_CZ,
35-
en_UK: chars_en_UK,
36-
en_US: chars_en_US,
37-
fr_FR: chars_fr_FR,
38-
de_DE: chars_de_DE,
39-
it_IT: chars_it_IT,
40-
nb_NO: chars_nb_NO,
41-
es_ES: chars_es_ES,
42-
sv_SE: chars_sv_SE,
43-
fr_CH: chars_fr_CH,
44-
de_CH: chars_de_CH,
22+
export const selectedKeyboard = (isoCode: string): KeyboardLayout => {
23+
// fallback to original behaviour of en-US if no isoCode given
24+
return keyboards.find(keyboard => keyboard.isoCode == (isoCode ?? "en-US"))!;
4525
};
26+
27+
export const keyboardOptions = () => {
28+
return keyboards.map((keyboard) => {
29+
return { label: keyboard.name, value: keyboard.isoCode }
30+
});
31+
}

ui/src/keyboardLayouts/cs_CZ.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { KeyCombo } from "../keyboardLayouts"
1+
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
22

3-
export const name = "Čeština";
3+
const name = "Čeština";
44

55
const keyTrema = { key: "Backslash" } // tréma (umlaut), two dots placed above a vowel
66
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
1313
const keyHook = { key: "Digit6", shift: true, altRight: true } // ogonoek (little hook), mark ˛ placed beneath a letter
1414
const keyCedille = { key: "Equal", shift: true, altRight: true } // accent cedille (cedilla), mark ¸ placed beneath a letter
1515

16-
export const chars = {
16+
const chars = {
1717
A: { key: "KeyA", shift: true },
1818
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
1919
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
@@ -242,3 +242,9 @@ export const chars = {
242242
Enter: { key: "Enter" },
243243
Tab: { key: "Tab" },
244244
} as Record<string, KeyCombo>;
245+
246+
export const cs_CZ: KeyboardLayout = {
247+
isoCode: "cs-CZ",
248+
name: name,
249+
chars: chars
250+
};

ui/src/keyboardLayouts/de_CH.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { KeyCombo } from "../keyboardLayouts"
1+
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
22

3-
export const name = "Schwiizerdütsch";
3+
const name = "Schwiizerdütsch";
44

55
const keyTrema = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel
66
const keyAcute = { key: "Minus", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter
77
const keyHat = { key: "Equal" } // accent circonflexe (accent hat), mark ^ placed above the letter
88
const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
99
const keyTilde = { key: "Equal", altRight: true } // tilde, mark ~ placed above the letter
1010

11-
export const chars = {
11+
const chars = {
1212
A: { key: "KeyA", shift: true },
1313
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
1414
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
@@ -163,3 +163,9 @@ export const chars = {
163163
Enter: { key: "Enter" },
164164
Tab: { key: "Tab" },
165165
} as Record<string, KeyCombo>;
166+
167+
export const de_CH: KeyboardLayout = {
168+
isoCode: "de-CH",
169+
name: name,
170+
chars: chars
171+
};

ui/src/keyboardLayouts/de_DE.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { KeyCombo } from "../keyboardLayouts"
1+
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
22

3-
export const name = "Deutsch";
3+
const name = "Deutsch";
44

55
const keyAcute = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter
66
const keyHat = { key: "Backquote" } // accent circonflexe (accent hat), mark ^ placed above the letter
77
const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
88

9-
export const chars = {
9+
const chars = {
1010
A: { key: "KeyA", shift: true },
1111
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
1212
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
@@ -150,3 +150,9 @@ export const chars = {
150150
Enter: { key: "Enter" },
151151
Tab: { key: "Tab" },
152152
} as Record<string, KeyCombo>;
153+
154+
export const de_DE: KeyboardLayout = {
155+
isoCode: "de-DE",
156+
name: name,
157+
chars: chars
158+
};

ui/src/keyboardLayouts/en_UK.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { KeyCombo } from "../keyboardLayouts"
1+
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
22

3-
export const name = "English (UK)";
3+
const name = "English (UK)";
44

5-
export const chars = {
5+
const chars = {
66
A: { key: "KeyA", shift: true },
77
B: { key: "KeyB", shift: true },
88
C: { key: "KeyC", shift: true },
@@ -105,3 +105,9 @@ export const chars = {
105105
Enter: { key: "Enter" },
106106
Tab: { key: "Tab" },
107107
} as Record<string, KeyCombo>
108+
109+
export const en_UK: KeyboardLayout = {
110+
isoCode: "en-UK",
111+
name: name,
112+
chars: chars
113+
};

ui/src/keyboardLayouts/en_US.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { KeyCombo } from "../keyboardLayouts"
1+
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
22

3-
export const name = "English (US)";
3+
const name = "English (US)";
44

5-
export const chars = {
5+
const chars = {
66
A: { key: "KeyA", shift: true },
77
B: { key: "KeyB", shift: true },
88
C: { key: "KeyC", shift: true },
@@ -111,3 +111,9 @@ export const chars = {
111111
Insert: { key: "Insert", shift: false },
112112
Delete: { key: "Delete", shift: false },
113113
} as Record<string, KeyCombo>
114+
115+
export const en_US: KeyboardLayout = {
116+
isoCode: "en-US",
117+
name: name,
118+
chars: chars
119+
};

ui/src/keyboardLayouts/es_ES.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { KeyCombo } from "../keyboardLayouts"
1+
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
22

3-
export const name = "Español";
3+
const name = "Español";
44

55
const keyTrema = { key: "Quote", shift: true } // tréma (umlaut), two dots placed above a vowel
66
const keyAcute = { key: "Quote" } // accent aigu (acute accent), mark ´ placed above the letter
77
const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter
88
const keyGrave = { key: "BracketRight" } // accent grave, mark ` placed above the letter
99
const keyTilde = { key: "Key4", altRight: true } // tilde, mark ~ placed above the letter
1010

11-
export const chars = {
11+
const chars = {
1212
A: { key: "KeyA", shift: true },
1313
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
1414
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
@@ -166,3 +166,9 @@ export const chars = {
166166
Enter: { key: "Enter" },
167167
Tab: { key: "Tab" },
168168
} as Record<string, KeyCombo>;
169+
170+
export const es_ES: KeyboardLayout = {
171+
isoCode: "es-ES",
172+
name: name,
173+
chars: chars
174+
};

ui/src/keyboardLayouts/fr_BE.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { KeyCombo } from "../keyboardLayouts"
1+
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
22

3-
export const name = "Belgisch Nederlands";
3+
const name = "Belgisch Nederlands";
44

55
const keyTrema = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel
66
const keyHat = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter
77
const keyAcute = { key: "Semicolon", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter
88
const keyGrave = { key: "Quote", shift: true } // accent grave, mark ` placed above the letter
99
const keyTilde = { key: "Slash", altRight: true } // tilde, mark ~ placed above the letter
1010

11-
export const chars = {
11+
const chars = {
1212
A: { key: "KeyQ", shift: true },
1313
"Ä": { key: "KeyQ", shift: true, accentKey: keyTrema },
1414
"Â": { key: "KeyQ", shift: true, accentKey: keyHat },
@@ -165,3 +165,9 @@ export const chars = {
165165
Enter: { key: "Enter" },
166166
Tab: { key: "Tab" },
167167
} as Record<string, KeyCombo>;
168+
169+
export const fr_BE: KeyboardLayout = {
170+
isoCode: "fr-BE",
171+
name: name,
172+
chars: chars
173+
};

0 commit comments

Comments
 (0)