mirror of https://github.com/jetkvm/kvm.git
Enable multiple keyboard layouts for paste text from host
This commit is contained in:
parent
d79f359c43
commit
c3087abe02
|
@ -87,6 +87,7 @@ type Config struct {
|
|||
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
||||
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
||||
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
|
||||
KeyboardLayout string `json:"keyboard_layout"`
|
||||
EdidString string `json:"hdmi_edid_string"`
|
||||
ActiveExtension string `json:"active_extension"`
|
||||
DisplayMaxBrightness int `json:"display_max_brightness"`
|
||||
|
@ -107,6 +108,7 @@ var defaultConfig = &Config{
|
|||
AutoUpdateEnabled: true, // Set a default value
|
||||
ActiveExtension: "",
|
||||
KeyboardMacros: []KeyboardMacro{},
|
||||
KeyboardLayout: "en-US",
|
||||
DisplayMaxBrightness: 64,
|
||||
DisplayDimAfterSec: 120, // 2 minutes
|
||||
DisplayOffAfterSec: 1800, // 30 minutes
|
||||
|
|
14
jsonrpc.go
14
jsonrpc.go
|
@ -863,6 +863,18 @@ func rpcSetScrollSensitivity(sensitivity string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func rpcGetKeyboardLayout() (string, error) {
|
||||
return config.KeyboardLayout, nil
|
||||
}
|
||||
|
||||
func rpcSetKeyboardLayout(layout string) error {
|
||||
config.KeyboardLayout = layout
|
||||
if err := SaveConfig(); err != nil {
|
||||
return fmt.Errorf("failed to save config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKeyboardMacros() (interface{}, error) {
|
||||
macros := make([]KeyboardMacro, len(config.KeyboardMacros))
|
||||
copy(macros, config.KeyboardMacros)
|
||||
|
@ -1028,6 +1040,8 @@ var rpcHandlers = map[string]RPCHandler{
|
|||
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
|
||||
"getScrollSensitivity": {Func: rpcGetScrollSensitivity},
|
||||
"setScrollSensitivity": {Func: rpcSetScrollSensitivity, Params: []string{"sensitivity"}},
|
||||
"getKeyboardLayout": {Func: rpcGetKeyboardLayout},
|
||||
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"layout"}},
|
||||
"getKeyboardMacros": {Func: getKeyboardMacros},
|
||||
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ import { GridCard } from "@components/Card";
|
|||
import { TextAreaWithLabel } from "@components/TextArea";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { useHidStore, useRTCStore, useUiStore } from "@/hooks/stores";
|
||||
import { chars, keys, modifiers } from "@/keyboardMappings";
|
||||
import { useHidStore, useRTCStore, useUiStore, useDeviceSettingsStore } from "@/hooks/stores";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
import { layouts, chars } from "@/keyboardLayouts";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
const hidKeyboardPayload = (keys: number[], modifier: number) => {
|
||||
|
@ -27,6 +28,11 @@ export default function PasteModal() {
|
|||
const [invalidChars, setInvalidChars] = useState<string[]>([]);
|
||||
const close = useClose();
|
||||
|
||||
const keyboardLayout = useDeviceSettingsStore(state => state.keyboardLayout);
|
||||
const setKeyboardLayout = useDeviceSettingsStore(
|
||||
state => state.setKeyboardLayout,
|
||||
);
|
||||
|
||||
const onCancelPasteMode = useCallback(() => {
|
||||
setPasteMode(false);
|
||||
setDisableVideoFocusTrap(false);
|
||||
|
@ -42,13 +48,25 @@ export default function PasteModal() {
|
|||
|
||||
try {
|
||||
for (const char of text) {
|
||||
const { key, shift } = chars[char] ?? {};
|
||||
const { key, shift, altRight, space, capsLock } = chars[keyboardLayout][char] ?? {};
|
||||
if (!key) continue;
|
||||
|
||||
const keyz = [keys[key]];
|
||||
if (space) {
|
||||
keyz.push(keys["Space"]);
|
||||
}
|
||||
if (capsLock) {
|
||||
keyz.unshift(keys["CapsLock"]);
|
||||
keyz.push(keys["CapsLock"]);
|
||||
}
|
||||
|
||||
const modz = shift ? modifiers["ShiftLeft"] : 0
|
||||
| (altRight ? modifiers["AltRight"] : 0);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
send(
|
||||
"keyboardReport",
|
||||
hidKeyboardPayload([keys[key]], shift ? modifiers["ShiftLeft"] : 0),
|
||||
hidKeyboardPayload(keyz, modz),
|
||||
params => {
|
||||
if ("error" in params) return reject(params.error);
|
||||
send("keyboardReport", hidKeyboardPayload([], 0), params => {
|
||||
|
@ -69,6 +87,11 @@ export default function PasteModal() {
|
|||
if (TextAreaRef.current) {
|
||||
TextAreaRef.current.focus();
|
||||
}
|
||||
|
||||
send("getKeyboardLayout", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setKeyboardLayout(resp.result as string);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -113,7 +136,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[char]),
|
||||
.filter(char => !chars[keyboardLayout][char]),
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -132,6 +155,11 @@ export default function PasteModal() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||
Sending key codes for keyboard layout {layouts[keyboardLayout]}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -336,6 +336,8 @@ export interface DeviceSettingsState {
|
|||
trackpadThreshold: number;
|
||||
scrollSensitivity: "low" | "default" | "high";
|
||||
setScrollSensitivity: (sensitivity: DeviceSettingsState["scrollSensitivity"]) => void;
|
||||
keyboardLayout: string;
|
||||
setKeyboardLayout: (layout: string) => void;
|
||||
}
|
||||
|
||||
export const useDeviceSettingsStore = create<DeviceSettingsState>(set => ({
|
||||
|
@ -397,6 +399,9 @@ export const useDeviceSettingsStore = create<DeviceSettingsState>(set => ({
|
|||
scrollSensitivity: sensitivity,
|
||||
});
|
||||
},
|
||||
|
||||
keyboardLayout: "en-US",
|
||||
setKeyboardLayout: layout => set({ keyboardLayout: layout }),
|
||||
}));
|
||||
|
||||
export interface RemoteVirtualMediaState {
|
||||
|
@ -893,4 +898,4 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
|
|||
set({ loading: false });
|
||||
}
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { chars as chars_en_US } from "@/keyboardLayouts/en_US"
|
||||
import { chars as chars_de_CH } from "@/keyboardLayouts/de_CH"
|
||||
|
||||
export const layouts = {
|
||||
"en_US": "English (US)",
|
||||
"de_CH": "Swiss German"
|
||||
} as Record<string, string>;
|
||||
|
||||
export const chars = {
|
||||
"en_US": chars_en_US,
|
||||
"de_CH": chars_de_CH,
|
||||
} as Record<string, Record<string, { key: string | number; shift?: boolean, altRight?: boolean, space?: boolean, capsLock?: boolean }>>;
|
|
@ -0,0 +1,140 @@
|
|||
export const chars = {
|
||||
A: { key: "KeyA", shift: true },
|
||||
B: { key: "KeyB", shift: true },
|
||||
C: { key: "KeyC", shift: true },
|
||||
D: { key: "KeyD", shift: true },
|
||||
E: { key: "KeyE", shift: true },
|
||||
F: { key: "KeyF", shift: true },
|
||||
G: { key: "KeyG", shift: true },
|
||||
H: { key: "KeyH", shift: true },
|
||||
I: { key: "KeyI", shift: true },
|
||||
J: { key: "KeyJ", shift: true },
|
||||
K: { key: "KeyK", shift: true },
|
||||
L: { key: "KeyL", shift: true },
|
||||
M: { key: "KeyM", shift: true },
|
||||
N: { key: "KeyN", shift: true },
|
||||
O: { key: "KeyO", shift: true },
|
||||
P: { key: "KeyP", shift: true },
|
||||
Q: { key: "KeyQ", shift: true },
|
||||
R: { key: "KeyR", shift: true },
|
||||
S: { key: "KeyS", shift: true },
|
||||
T: { key: "KeyT", shift: true },
|
||||
U: { key: "KeyU", shift: true },
|
||||
V: { key: "KeyV", shift: true },
|
||||
W: { key: "KeyW", shift: true },
|
||||
X: { key: "KeyX", shift: true },
|
||||
Y: { key: "KeyZ", shift: true },
|
||||
Z: { key: "KeyY", shift: true },
|
||||
a: { key: "KeyA" },
|
||||
"æ": { key: "KeyA", altRight: true },
|
||||
b: { key: "KeyB" },
|
||||
c: { key: "KeyC" },
|
||||
d: { key: "KeyD" },
|
||||
"ð": { key: "KeyD", altRight: true },
|
||||
e: { key: "KeyE" },
|
||||
f: { key: "KeyF" },
|
||||
"đ": { key: "KeyF", altRight: true },
|
||||
g: { key: "KeyG" },
|
||||
"ŋ": { key: "KeyG", altRight: true },
|
||||
h: { key: "KeyH" },
|
||||
"ħ": { key: "KeyH", altRight: true },
|
||||
i: { key: "KeyI" },
|
||||
"→": { key: "KeyI", altRight: true },
|
||||
j: { key: "KeyJ" },
|
||||
k: { key: "KeyK" },
|
||||
"ĸ": { key: "KeyK", altRight: true },
|
||||
l: { key: "KeyL" },
|
||||
"ł": { key: "KeyL", altRight: true },
|
||||
m: { key: "KeyM" },
|
||||
"µ": { key: "KeyM", altRight: true },
|
||||
n: { key: "KeyN" },
|
||||
o: { key: "KeyO" },
|
||||
"œ": { key: "KeyO", altRight: true },
|
||||
p: { key: "KeyP" },
|
||||
"þ": { key: "KeyP", altRight: true },
|
||||
q: { key: "KeyQ" },
|
||||
r: { key: "KeyR" },
|
||||
"¶": { key: "KeyR", altRight: true },
|
||||
s: { key: "KeyS" },
|
||||
"ß": { key: "KeyS", altRight: true },
|
||||
t: { key: "KeyT" },
|
||||
"ŧ": { key: "KeyT", altRight: true },
|
||||
u: { key: "KeyU" },
|
||||
"↓": { key: "KeyU", altRight: true },
|
||||
v: { key: "KeyV" },
|
||||
"„": { key: "KeyV", altRight: true },
|
||||
w: { key: "KeyW" },
|
||||
"ſ": { key: "KeyW", altRight: true },
|
||||
x: { key: "KeyX" },
|
||||
"»": { key: "KeyX", altRight: true },
|
||||
y: { key: "KeyZ" },
|
||||
"←": { key: "KeyZ", altRight: true },
|
||||
z: { key: "KeyY" },
|
||||
"«": { key: "KeyY", altRight: true },
|
||||
"§": { key: "Backquote" },
|
||||
"°": { key: "Backquote", shift: true },
|
||||
1: { key: "Digit1" },
|
||||
"+": { key: "Digit1", shift: true },
|
||||
"|": { key: "Digit1", altRight: true },
|
||||
2: { key: "Digit2" },
|
||||
"\"": { key: "Digit2", shift: true },
|
||||
"@": { key: "Digit2", altRight: true },
|
||||
3: { key: "Digit3" },
|
||||
"*": { key: "Digit3", shift: true },
|
||||
"#": { key: "Digit3", altRight: true },
|
||||
4: { key: "Digit4" },
|
||||
"ç": { key: "Digit4", shift: true },
|
||||
"¼": { key: "Digit4", altRight: true },
|
||||
5: { key: "Digit5" },
|
||||
"%": { key: "Digit5", shift: true },
|
||||
"½": { key: "Digit5", altRight: true },
|
||||
6: { key: "Digit6" },
|
||||
"&": { key: "Digit6", shift: true },
|
||||
"¬": { key: "Digit6", altRight: true },
|
||||
7: { key: "Digit7" },
|
||||
"/": { key: "Digit7", shift: true },
|
||||
8: { key: "Digit8" },
|
||||
"(": { key: "Digit8", shift: true },
|
||||
"¢": { key: "Digit8", altRight: true },
|
||||
9: { key: "Digit9" },
|
||||
")": { key: "Digit9", shift: true },
|
||||
0: { key: "Digit0" },
|
||||
"=": { key: "Digit0", shift: true },
|
||||
"'": { key: "Minus" },
|
||||
"?": { key: "Minus", shift: true },
|
||||
"^": { key: "Equal", space: true }, // dead key
|
||||
"`": { key: "Equal", shift: true },
|
||||
"~": { key: "Equal", altRight: true, space: true }, // dead key
|
||||
"ü": { key: "BracketLeft" },
|
||||
"è": { key: "BracketLeft", shift: true },
|
||||
"[": { key: "BracketLeft", altRight: true },
|
||||
"Ü": { key: "BracketLeft", capsLock: true },
|
||||
"!": { key: "BracketRight", shift: true },
|
||||
"]": { key: "BracketRight", altRight: true },
|
||||
"ö": { key: "Semicolon" },
|
||||
"é": { key: "Semicolon", shift: true },
|
||||
"Ö": { key: "Semicolon", capsLock: true },
|
||||
"ä": { key: "Quote" },
|
||||
"à": { key: "Quote", shift: true },
|
||||
"{": { key: "Quote", altRight: true },
|
||||
"Ä": { key: "Quote", capsLock: true },
|
||||
"$": { key: "Backslash" },
|
||||
"£": { key: "Backslash", shift: true },
|
||||
"}": { key: "Backslash", altRight: true },
|
||||
",": { key: "Comma" },
|
||||
";": { key: "Comma", shift: true },
|
||||
"•": { key: "Comma", altRight: true },
|
||||
".": { key: "Period" },
|
||||
":": { key: "Period", shift: true },
|
||||
"·": { key: "Period", altRight: true },
|
||||
"-": { key: "Slash" },
|
||||
"_": { key: "Slash", shift: true },
|
||||
"<": { key: "IntlBackslash" },
|
||||
">": { key: "IntlBackslash", shift: true },
|
||||
"\\": { key: "IntlBackslash", altRight: true },
|
||||
"€": { key: "KeyE", altRight: true },
|
||||
" ": { key: "Space" },
|
||||
"\n": { key: "Enter" },
|
||||
Enter: { key: "Enter" },
|
||||
Tab: { key: "Tab" },
|
||||
} as Record<string, { key: string | number; shift?: boolean, altRight?: boolean, space?: boolean, capsLock?: boolean }>
|
|
@ -0,0 +1,102 @@
|
|||
export const chars = {
|
||||
A: { key: "KeyA", shift: true },
|
||||
B: { key: "KeyB", shift: true },
|
||||
C: { key: "KeyC", shift: true },
|
||||
D: { key: "KeyD", shift: true },
|
||||
E: { key: "KeyE", shift: true },
|
||||
F: { key: "KeyF", shift: true },
|
||||
G: { key: "KeyG", shift: true },
|
||||
H: { key: "KeyH", shift: true },
|
||||
I: { key: "KeyI", shift: true },
|
||||
J: { key: "KeyJ", shift: true },
|
||||
K: { key: "KeyK", shift: true },
|
||||
L: { key: "KeyL", shift: true },
|
||||
M: { key: "KeyM", shift: true },
|
||||
N: { key: "KeyN", shift: true },
|
||||
O: { key: "KeyO", shift: true },
|
||||
P: { key: "KeyP", shift: true },
|
||||
Q: { key: "KeyQ", shift: true },
|
||||
R: { key: "KeyR", shift: true },
|
||||
S: { key: "KeyS", shift: true },
|
||||
T: { key: "KeyT", shift: true },
|
||||
U: { key: "KeyU", shift: true },
|
||||
V: { key: "KeyV", shift: true },
|
||||
W: { key: "KeyW", shift: true },
|
||||
X: { key: "KeyX", shift: true },
|
||||
Y: { key: "KeyY", shift: true },
|
||||
Z: { key: "KeyZ", shift: true },
|
||||
a: { key: "KeyA", shift: false },
|
||||
b: { key: "KeyB", shift: false },
|
||||
c: { key: "KeyC", shift: false },
|
||||
d: { key: "KeyD", shift: false },
|
||||
e: { key: "KeyE", shift: false },
|
||||
f: { key: "KeyF", shift: false },
|
||||
g: { key: "KeyG", shift: false },
|
||||
h: { key: "KeyH", shift: false },
|
||||
i: { key: "KeyI", shift: false },
|
||||
j: { key: "KeyJ", shift: false },
|
||||
k: { key: "KeyK", shift: false },
|
||||
l: { key: "KeyL", shift: false },
|
||||
m: { key: "KeyM", shift: false },
|
||||
n: { key: "KeyN", shift: false },
|
||||
o: { key: "KeyO", shift: false },
|
||||
p: { key: "KeyP", shift: false },
|
||||
q: { key: "KeyQ", shift: false },
|
||||
r: { key: "KeyR", shift: false },
|
||||
s: { key: "KeyS", shift: false },
|
||||
t: { key: "KeyT", shift: false },
|
||||
u: { key: "KeyU", shift: false },
|
||||
v: { key: "KeyV", shift: false },
|
||||
w: { key: "KeyW", shift: false },
|
||||
x: { key: "KeyX", shift: false },
|
||||
y: { key: "KeyY", shift: false },
|
||||
z: { key: "KeyZ", shift: false },
|
||||
1: { key: "Digit1", shift: false },
|
||||
"!": { key: "Digit1", shift: true },
|
||||
2: { key: "Digit2", shift: false },
|
||||
"@": { key: "Digit2", shift: true },
|
||||
3: { key: "Digit3", shift: false },
|
||||
"#": { key: "Digit3", shift: true },
|
||||
4: { key: "Digit4", shift: false },
|
||||
$: { key: "Digit4", shift: true },
|
||||
"%": { key: "Digit5", shift: true },
|
||||
5: { key: "Digit5", shift: false },
|
||||
"^": { key: "Digit6", shift: true },
|
||||
6: { key: "Digit6", shift: false },
|
||||
"&": { key: "Digit7", shift: true },
|
||||
7: { key: "Digit7", shift: false },
|
||||
"*": { key: "Digit8", shift: true },
|
||||
8: { key: "Digit8", shift: false },
|
||||
"(": { key: "Digit9", shift: true },
|
||||
9: { key: "Digit9", shift: false },
|
||||
")": { key: "Digit0", shift: true },
|
||||
0: { key: "Digit0", shift: false },
|
||||
"-": { key: "Minus", shift: false },
|
||||
_: { key: "Minus", shift: true },
|
||||
"=": { key: "Equal", shift: false },
|
||||
"+": { key: "Equal", shift: true },
|
||||
"'": { key: "Quote", shift: false },
|
||||
'"': { key: "Quote", shift: true },
|
||||
",": { key: "Comma", shift: false },
|
||||
"<": { key: "Comma", shift: true },
|
||||
"/": { key: "Slash", shift: false },
|
||||
"?": { key: "Slash", shift: true },
|
||||
".": { key: "Period", shift: false },
|
||||
">": { key: "Period", shift: true },
|
||||
";": { key: "Semicolon", shift: false },
|
||||
":": { key: "Semicolon", shift: true },
|
||||
"[": { key: "BracketLeft", shift: false },
|
||||
"{": { key: "BracketLeft", shift: true },
|
||||
"]": { key: "BracketRight", shift: false },
|
||||
"}": { key: "BracketRight", shift: true },
|
||||
"\\": { key: "Backslash", shift: false },
|
||||
"|": { key: "Backslash", shift: true },
|
||||
"`": { key: "Backquote", shift: false },
|
||||
"~": { key: "Backquote", shift: true },
|
||||
"§": { key: "IntlBackslash", shift: false },
|
||||
"±": { key: "IntlBackslash", shift: true },
|
||||
" ": { key: "Space", shift: false },
|
||||
"\n": { key: "Enter", shift: false },
|
||||
Enter: { key: "Enter", shift: false },
|
||||
Tab: { key: "Tab", shift: false },
|
||||
} as Record<string, { key: string | number; shift: boolean }>
|
|
@ -43,7 +43,7 @@ export const keys = {
|
|||
F13: 0x68,
|
||||
Home: 0x4a,
|
||||
Insert: 0x49,
|
||||
IntlBackslash: 0x31,
|
||||
IntlBackslash: 0x64,
|
||||
KeyA: 0x04,
|
||||
KeyB: 0x05,
|
||||
KeyC: 0x06,
|
||||
|
@ -99,109 +99,6 @@ export const keys = {
|
|||
Tab: 0x2b,
|
||||
} as Record<string, number>;
|
||||
|
||||
export const chars = {
|
||||
A: { key: "KeyA", shift: true },
|
||||
B: { key: "KeyB", shift: true },
|
||||
C: { key: "KeyC", shift: true },
|
||||
D: { key: "KeyD", shift: true },
|
||||
E: { key: "KeyE", shift: true },
|
||||
F: { key: "KeyF", shift: true },
|
||||
G: { key: "KeyG", shift: true },
|
||||
H: { key: "KeyH", shift: true },
|
||||
I: { key: "KeyI", shift: true },
|
||||
J: { key: "KeyJ", shift: true },
|
||||
K: { key: "KeyK", shift: true },
|
||||
L: { key: "KeyL", shift: true },
|
||||
M: { key: "KeyM", shift: true },
|
||||
N: { key: "KeyN", shift: true },
|
||||
O: { key: "KeyO", shift: true },
|
||||
P: { key: "KeyP", shift: true },
|
||||
Q: { key: "KeyQ", shift: true },
|
||||
R: { key: "KeyR", shift: true },
|
||||
S: { key: "KeyS", shift: true },
|
||||
T: { key: "KeyT", shift: true },
|
||||
U: { key: "KeyU", shift: true },
|
||||
V: { key: "KeyV", shift: true },
|
||||
W: { key: "KeyW", shift: true },
|
||||
X: { key: "KeyX", shift: true },
|
||||
Y: { key: "KeyY", shift: true },
|
||||
Z: { key: "KeyZ", shift: true },
|
||||
a: { key: "KeyA", shift: false },
|
||||
b: { key: "KeyB", shift: false },
|
||||
c: { key: "KeyC", shift: false },
|
||||
d: { key: "KeyD", shift: false },
|
||||
e: { key: "KeyE", shift: false },
|
||||
f: { key: "KeyF", shift: false },
|
||||
g: { key: "KeyG", shift: false },
|
||||
h: { key: "KeyH", shift: false },
|
||||
i: { key: "KeyI", shift: false },
|
||||
j: { key: "KeyJ", shift: false },
|
||||
k: { key: "KeyK", shift: false },
|
||||
l: { key: "KeyL", shift: false },
|
||||
m: { key: "KeyM", shift: false },
|
||||
n: { key: "KeyN", shift: false },
|
||||
o: { key: "KeyO", shift: false },
|
||||
p: { key: "KeyP", shift: false },
|
||||
q: { key: "KeyQ", shift: false },
|
||||
r: { key: "KeyR", shift: false },
|
||||
s: { key: "KeyS", shift: false },
|
||||
t: { key: "KeyT", shift: false },
|
||||
u: { key: "KeyU", shift: false },
|
||||
v: { key: "KeyV", shift: false },
|
||||
w: { key: "KeyW", shift: false },
|
||||
x: { key: "KeyX", shift: false },
|
||||
y: { key: "KeyY", shift: false },
|
||||
z: { key: "KeyZ", shift: false },
|
||||
1: { key: "Digit1", shift: false },
|
||||
"!": { key: "Digit1", shift: true },
|
||||
2: { key: "Digit2", shift: false },
|
||||
"@": { key: "Digit2", shift: true },
|
||||
3: { key: "Digit3", shift: false },
|
||||
"#": { key: "Digit3", shift: true },
|
||||
4: { key: "Digit4", shift: false },
|
||||
$: { key: "Digit4", shift: true },
|
||||
"%": { key: "Digit5", shift: true },
|
||||
5: { key: "Digit5", shift: false },
|
||||
"^": { key: "Digit6", shift: true },
|
||||
6: { key: "Digit6", shift: false },
|
||||
"&": { key: "Digit7", shift: true },
|
||||
7: { key: "Digit7", shift: false },
|
||||
"*": { key: "Digit8", shift: true },
|
||||
8: { key: "Digit8", shift: false },
|
||||
"(": { key: "Digit9", shift: true },
|
||||
9: { key: "Digit9", shift: false },
|
||||
")": { key: "Digit0", shift: true },
|
||||
0: { key: "Digit0", shift: false },
|
||||
"-": { key: "Minus", shift: false },
|
||||
_: { key: "Minus", shift: true },
|
||||
"=": { key: "Equal", shift: false },
|
||||
"+": { key: "Equal", shift: true },
|
||||
"'": { key: "Quote", shift: false },
|
||||
'"': { key: "Quote", shift: true },
|
||||
",": { key: "Comma", shift: false },
|
||||
"<": { key: "Comma", shift: true },
|
||||
"/": { key: "Slash", shift: false },
|
||||
"?": { key: "Slash", shift: true },
|
||||
".": { key: "Period", shift: false },
|
||||
">": { key: "Period", shift: true },
|
||||
";": { key: "Semicolon", shift: false },
|
||||
":": { key: "Semicolon", shift: true },
|
||||
"[": { key: "BracketLeft", shift: false },
|
||||
"{": { key: "BracketLeft", shift: true },
|
||||
"]": { key: "BracketRight", shift: false },
|
||||
"}": { key: "BracketRight", shift: true },
|
||||
"\\": { key: "Backslash", shift: false },
|
||||
"|": { key: "Backslash", shift: true },
|
||||
"`": { key: "Backquote", shift: false },
|
||||
"~": { key: "Backquote", shift: true },
|
||||
"§": { key: "IntlBackslash", shift: false },
|
||||
"±": { key: "IntlBackslash", shift: true },
|
||||
" ": { key: "Space", shift: false },
|
||||
"\n": { key: "Enter", shift: false },
|
||||
Enter: { key: "Enter", shift: false },
|
||||
Tab: { key: "Tab", shift: false },
|
||||
} as Record<string, { key: string | number; shift: boolean }>;
|
||||
|
||||
export const modifiers = {
|
||||
ControlLeft: 0x01,
|
||||
ControlRight: 0x10,
|
||||
|
|
|
@ -32,7 +32,8 @@ import { CLOUD_API, DEVICE_API } from "./ui.config";
|
|||
import OtherSessionRoute from "./routes/devices.$id.other-session";
|
||||
import MountRoute from "./routes/devices.$id.mount";
|
||||
import * as SettingsRoute from "./routes/devices.$id.settings";
|
||||
import SettingsKeyboardMouseRoute from "./routes/devices.$id.settings.mouse";
|
||||
import SettingsMouseRoute from "./routes/devices.$id.settings.mouse";
|
||||
import SettingsKeyboardRoute from "./routes/devices.$id.settings.keyboard";
|
||||
import api from "./api";
|
||||
import * as SettingsIndexRoute from "./routes/devices.$id.settings._index";
|
||||
import SettingsAdvancedRoute from "./routes/devices.$id.settings.advanced";
|
||||
|
@ -147,7 +148,11 @@ if (isOnDevice) {
|
|||
},
|
||||
{
|
||||
path: "mouse",
|
||||
element: <SettingsKeyboardMouseRoute />,
|
||||
element: <SettingsMouseRoute />,
|
||||
},
|
||||
{
|
||||
path: "keyboard",
|
||||
element: <SettingsKeyboardRoute />,
|
||||
},
|
||||
{
|
||||
path: "advanced",
|
||||
|
@ -276,7 +281,11 @@ if (isOnDevice) {
|
|||
},
|
||||
{
|
||||
path: "mouse",
|
||||
element: <SettingsKeyboardMouseRoute />,
|
||||
element: <SettingsMouseRoute />,
|
||||
},
|
||||
{
|
||||
path: "keyboard",
|
||||
element: <SettingsKeyboardRoute />,
|
||||
},
|
||||
{
|
||||
path: "advanced",
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import { useCallback, useEffect } from "react";
|
||||
|
||||
import { useDeviceSettingsStore } from "@/hooks/stores";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import notifications from "@/notifications";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { layouts } from "@/keyboardLayouts";
|
||||
|
||||
import { FeatureFlag } from "../components/FeatureFlag";
|
||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export default function SettingsKeyboardRoute() {
|
||||
const keyboardLayout = useDeviceSettingsStore(state => state.keyboardLayout);
|
||||
const setKeyboardLayout = useDeviceSettingsStore(
|
||||
state => state.setKeyboardLayout,
|
||||
);
|
||||
|
||||
const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } })
|
||||
|
||||
const [send] = useJsonRpc();
|
||||
|
||||
useEffect(() => {
|
||||
send("getKeyboardLayout", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setKeyboardLayout(resp.result as string);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onKeyboardLayoutChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const layout = e.target.value;
|
||||
send("setKeyboardLayout", { layout }, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to set keyboard layout: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
notifications.success("Keyboard layout set successfully");
|
||||
setKeyboardLayout(layout);
|
||||
});
|
||||
},
|
||||
[send, setKeyboardLayout],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<SettingsPageHeader
|
||||
title="Keyboard"
|
||||
description="Configure keyboard layout settings for your device"
|
||||
/>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FeatureFlag minAppVersion="0.4.0" name="Paste text">
|
||||
{ /* this menu item could be renamed to plain "Keyboard layout" in the future, when also the virtual keyboard layout mappings are being implemented */ }
|
||||
<SettingsItem
|
||||
title="Paste text"
|
||||
description="Keyboard layout of target operating system"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
label=""
|
||||
fullWidth
|
||||
value={keyboardLayout}
|
||||
onChange={onKeyboardLayoutChange}
|
||||
options={layoutOptions}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||
Pasting text sends individual key strokes to the target device. The keyboard layout determines which key codes are being sent. Ensure that the keyboard layout in JetKVM matches the settings in the operating system.
|
||||
</p>
|
||||
</FeatureFlag>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -18,7 +18,7 @@ import { SettingsItem } from "./devices.$id.settings";
|
|||
|
||||
type ScrollSensitivity = "low" | "default" | "high";
|
||||
|
||||
export default function SettingsKeyboardMouseRoute() {
|
||||
export default function SettingsMouseRoute() {
|
||||
const hideCursor = useSettingsStore(state => state.isCursorHidden);
|
||||
const setHideCursor = useSettingsStore(state => state.setCursorVisibility);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { NavLink, Outlet, useLocation } from "react-router-dom";
|
||||
import {
|
||||
LuSettings,
|
||||
LuMouse,
|
||||
LuKeyboard,
|
||||
LuVideo,
|
||||
LuCpu,
|
||||
|
@ -148,11 +149,22 @@ export default function SettingsRoute() {
|
|||
className={({ isActive }) => (isActive ? "active" : "")}
|
||||
>
|
||||
<div className="flex items-center gap-x-2 rounded-md px-2.5 py-2.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700 [.active_&]:bg-blue-50 [.active_&]:!text-blue-700 md:[.active_&]:bg-transparent dark:[.active_&]:bg-blue-900 dark:[.active_&]:!text-blue-200 dark:md:[.active_&]:bg-transparent">
|
||||
<LuKeyboard className="h-4 w-4 shrink-0" />
|
||||
<LuMouse className="h-4 w-4 shrink-0" />
|
||||
<h1>Mouse</h1>
|
||||
</div>
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<NavLink
|
||||
to="keyboard"
|
||||
className={({ isActive }) => (isActive ? "active" : "")}
|
||||
>
|
||||
<div className="flex items-center gap-x-2 rounded-md px-2.5 py-2.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700 [.active_&]:bg-blue-50 [.active_&]:!text-blue-700 md:[.active_&]:bg-transparent dark:[.active_&]:bg-blue-900 dark:[.active_&]:!text-blue-200 dark:md:[.active_&]:bg-transparent">
|
||||
<LuKeyboard className="h-4 w-4 shrink-0" />
|
||||
<h1>Keyboard</h1>
|
||||
</div>
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<NavLink
|
||||
to="video"
|
||||
|
|
Loading…
Reference in New Issue