mirror of https://github.com/jetkvm/kvm.git
Merge f753a7ac08
into 009b0abbe9
This commit is contained in:
commit
fc3a3388bd
64
config.go
64
config.go
|
@ -73,41 +73,45 @@ func (m *KeyboardMacro) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
CloudURL string `json:"cloud_url"`
|
CloudURL string `json:"cloud_url"`
|
||||||
CloudAppURL string `json:"cloud_app_url"`
|
CloudAppURL string `json:"cloud_app_url"`
|
||||||
CloudToken string `json:"cloud_token"`
|
CloudToken string `json:"cloud_token"`
|
||||||
GoogleIdentity string `json:"google_identity"`
|
GoogleIdentity string `json:"google_identity"`
|
||||||
JigglerEnabled bool `json:"jiggler_enabled"`
|
JigglerEnabled bool `json:"jiggler_enabled"`
|
||||||
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
||||||
IncludePreRelease bool `json:"include_pre_release"`
|
KeyboardLayout string `json:"keyboard_layout"`
|
||||||
HashedPassword string `json:"hashed_password"`
|
KeyboardMappingEnabled bool `json:"keyboard_mapping_enabled"`
|
||||||
LocalAuthToken string `json:"local_auth_token"`
|
IncludePreRelease bool `json:"include_pre_release"`
|
||||||
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
HashedPassword string `json:"hashed_password"`
|
||||||
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
LocalAuthToken string `json:"local_auth_token"`
|
||||||
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
|
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
||||||
EdidString string `json:"hdmi_edid_string"`
|
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
||||||
ActiveExtension string `json:"active_extension"`
|
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
|
||||||
DisplayMaxBrightness int `json:"display_max_brightness"`
|
EdidString string `json:"hdmi_edid_string"`
|
||||||
DisplayDimAfterSec int `json:"display_dim_after_sec"`
|
ActiveExtension string `json:"active_extension"`
|
||||||
DisplayOffAfterSec int `json:"display_off_after_sec"`
|
DisplayMaxBrightness int `json:"display_max_brightness"`
|
||||||
TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", ""
|
DisplayDimAfterSec int `json:"display_dim_after_sec"`
|
||||||
UsbConfig *usbgadget.Config `json:"usb_config"`
|
DisplayOffAfterSec int `json:"display_off_after_sec"`
|
||||||
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", ""
|
||||||
DefaultLogLevel string `json:"default_log_level"`
|
UsbConfig *usbgadget.Config `json:"usb_config"`
|
||||||
|
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
||||||
|
DefaultLogLevel string `json:"default_log_level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const configPath = "/userdata/kvm_config.json"
|
const configPath = "/userdata/kvm_config.json"
|
||||||
|
|
||||||
var defaultConfig = &Config{
|
var defaultConfig = &Config{
|
||||||
CloudURL: "https://api.jetkvm.com",
|
CloudURL: "https://api.jetkvm.com",
|
||||||
CloudAppURL: "https://app.jetkvm.com",
|
CloudAppURL: "https://app.jetkvm.com",
|
||||||
AutoUpdateEnabled: true, // Set a default value
|
AutoUpdateEnabled: true, // Set a default value
|
||||||
ActiveExtension: "",
|
KeyboardLayout: "en-US",
|
||||||
KeyboardMacros: []KeyboardMacro{},
|
KeyboardMappingEnabled: false,
|
||||||
DisplayMaxBrightness: 64,
|
ActiveExtension: "",
|
||||||
DisplayDimAfterSec: 120, // 2 minutes
|
KeyboardMacros: []KeyboardMacro{},
|
||||||
DisplayOffAfterSec: 1800, // 30 minutes
|
DisplayMaxBrightness: 64,
|
||||||
TLSMode: "",
|
DisplayDimAfterSec: 120, // 2 minutes
|
||||||
|
DisplayOffAfterSec: 1800, // 30 minutes
|
||||||
|
TLSMode: "",
|
||||||
UsbConfig: &usbgadget.Config{
|
UsbConfig: &usbgadget.Config{
|
||||||
VendorId: "0x1d6b", //The Linux Foundation
|
VendorId: "0x1d6b", //The Linux Foundation
|
||||||
ProductId: "0x0104", //Multifunction Composite Gadget
|
ProductId: "0x0104", //Multifunction Composite Gadget
|
||||||
|
|
|
@ -83,6 +83,7 @@ export LD_LIBRARY_PATH=/oem/usr/lib:\$LD_LIBRARY_PATH
|
||||||
# Kill any existing instances of the application
|
# Kill any existing instances of the application
|
||||||
killall jetkvm_app || true
|
killall jetkvm_app || true
|
||||||
killall jetkvm_app_debug || true
|
killall jetkvm_app_debug || true
|
||||||
|
killall jetkvm_native || true
|
||||||
|
|
||||||
# Navigate to the directory where the binary will be stored
|
# Navigate to the directory where the binary will be stored
|
||||||
cd "${REMOTE_PATH}"
|
cd "${REMOTE_PATH}"
|
||||||
|
|
166
jsonrpc.go
166
jsonrpc.go
|
@ -164,6 +164,30 @@ func rpcGetDeviceID() (string, error) {
|
||||||
return GetDeviceID(), nil
|
return GetDeviceID(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rpcGetKeyboardLayout() (string, error) {
|
||||||
|
return config.KeyboardLayout, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcSetKeyboardLayout(KeyboardLayout string) (string, error) {
|
||||||
|
config.KeyboardLayout = KeyboardLayout
|
||||||
|
if err := SaveConfig(); err != nil {
|
||||||
|
return config.KeyboardLayout, fmt.Errorf("failed to save config: %w", err)
|
||||||
|
}
|
||||||
|
return KeyboardLayout, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcGetKeyboardMappingState() (bool, error) {
|
||||||
|
return config.KeyboardMappingEnabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcSetKeyboardMappingState(enabled bool) (bool, error) {
|
||||||
|
config.KeyboardMappingEnabled = enabled
|
||||||
|
if err := SaveConfig(); err != nil {
|
||||||
|
return config.KeyboardMappingEnabled, fmt.Errorf("failed to save config: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
func rpcReboot(force bool) error {
|
func rpcReboot(force bool) error {
|
||||||
logger.Info().Msg("Got reboot request from JSONRPC, rebooting...")
|
logger.Info().Msg("Got reboot request from JSONRPC, rebooting...")
|
||||||
|
|
||||||
|
@ -957,73 +981,77 @@ func setKeyboardMacros(params KeyboardMacrosParams) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcHandlers = map[string]RPCHandler{
|
var rpcHandlers = map[string]RPCHandler{
|
||||||
"ping": {Func: rpcPing},
|
"ping": {Func: rpcPing},
|
||||||
"reboot": {Func: rpcReboot, Params: []string{"force"}},
|
"reboot": {Func: rpcReboot, Params: []string{"force"}},
|
||||||
"getDeviceID": {Func: rpcGetDeviceID},
|
"getDeviceID": {Func: rpcGetDeviceID},
|
||||||
"deregisterDevice": {Func: rpcDeregisterDevice},
|
"deregisterDevice": {Func: rpcDeregisterDevice},
|
||||||
"getCloudState": {Func: rpcGetCloudState},
|
"getCloudState": {Func: rpcGetCloudState},
|
||||||
"keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
|
"keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
|
||||||
"absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
|
"absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
|
||||||
"relMouseReport": {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}},
|
"relMouseReport": {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}},
|
||||||
"wheelReport": {Func: rpcWheelReport, Params: []string{"wheelY"}},
|
"wheelReport": {Func: rpcWheelReport, Params: []string{"wheelY"}},
|
||||||
"getVideoState": {Func: rpcGetVideoState},
|
"getVideoState": {Func: rpcGetVideoState},
|
||||||
"getUSBState": {Func: rpcGetUSBState},
|
"getUSBState": {Func: rpcGetUSBState},
|
||||||
"unmountImage": {Func: rpcUnmountImage},
|
"unmountImage": {Func: rpcUnmountImage},
|
||||||
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
|
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
|
||||||
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
||||||
"getJigglerState": {Func: rpcGetJigglerState},
|
"getJigglerState": {Func: rpcGetJigglerState},
|
||||||
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
||||||
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
"getKeyboardLayout": {Func: rpcGetKeyboardLayout},
|
||||||
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"kbLayout"}},
|
||||||
"getAutoUpdateState": {Func: rpcGetAutoUpdateState},
|
"setKeyboardMappingState": {Func: rpcSetKeyboardMappingState, Params: []string{"enabled"}},
|
||||||
"setAutoUpdateState": {Func: rpcSetAutoUpdateState, Params: []string{"enabled"}},
|
"getKeyboardMappingState": {Func: rpcGetKeyboardMappingState},
|
||||||
"getEDID": {Func: rpcGetEDID},
|
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
||||||
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
||||||
"getDevChannelState": {Func: rpcGetDevChannelState},
|
"getAutoUpdateState": {Func: rpcGetAutoUpdateState},
|
||||||
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
"setAutoUpdateState": {Func: rpcSetAutoUpdateState, Params: []string{"enabled"}},
|
||||||
"getUpdateStatus": {Func: rpcGetUpdateStatus},
|
"getEDID": {Func: rpcGetEDID},
|
||||||
"tryUpdate": {Func: rpcTryUpdate},
|
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
||||||
"getDevModeState": {Func: rpcGetDevModeState},
|
"getDevChannelState": {Func: rpcGetDevChannelState},
|
||||||
"setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
|
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
||||||
"getSSHKeyState": {Func: rpcGetSSHKeyState},
|
"getUpdateStatus": {Func: rpcGetUpdateStatus},
|
||||||
"setSSHKeyState": {Func: rpcSetSSHKeyState, Params: []string{"sshKey"}},
|
"tryUpdate": {Func: rpcTryUpdate},
|
||||||
"getTLSState": {Func: rpcGetTLSState},
|
"getDevModeState": {Func: rpcGetDevModeState},
|
||||||
"setTLSState": {Func: rpcSetTLSState, Params: []string{"state"}},
|
"setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
|
||||||
"setMassStorageMode": {Func: rpcSetMassStorageMode, Params: []string{"mode"}},
|
"getSSHKeyState": {Func: rpcGetSSHKeyState},
|
||||||
"getMassStorageMode": {Func: rpcGetMassStorageMode},
|
"setSSHKeyState": {Func: rpcSetSSHKeyState, Params: []string{"sshKey"}},
|
||||||
"isUpdatePending": {Func: rpcIsUpdatePending},
|
"getTLSState": {Func: rpcGetTLSState},
|
||||||
"getUsbEmulationState": {Func: rpcGetUsbEmulationState},
|
"setTLSState": {Func: rpcSetTLSState, Params: []string{"state"}},
|
||||||
"setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}},
|
"setMassStorageMode": {Func: rpcSetMassStorageMode, Params: []string{"mode"}},
|
||||||
"getUsbConfig": {Func: rpcGetUsbConfig},
|
"getMassStorageMode": {Func: rpcGetMassStorageMode},
|
||||||
"setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}},
|
"isUpdatePending": {Func: rpcIsUpdatePending},
|
||||||
"checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}},
|
"getUsbEmulationState": {Func: rpcGetUsbEmulationState},
|
||||||
"getVirtualMediaState": {Func: rpcGetVirtualMediaState},
|
"setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}},
|
||||||
"getStorageSpace": {Func: rpcGetStorageSpace},
|
"getUsbConfig": {Func: rpcGetUsbConfig},
|
||||||
"mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}},
|
"setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}},
|
||||||
"mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}},
|
"checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}},
|
||||||
"mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}},
|
"getVirtualMediaState": {Func: rpcGetVirtualMediaState},
|
||||||
"listStorageFiles": {Func: rpcListStorageFiles},
|
"getStorageSpace": {Func: rpcGetStorageSpace},
|
||||||
"deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}},
|
"mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}},
|
||||||
"startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}},
|
"mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}},
|
||||||
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices},
|
"mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}},
|
||||||
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}},
|
"listStorageFiles": {Func: rpcListStorageFiles},
|
||||||
"resetConfig": {Func: rpcResetConfig},
|
"deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}},
|
||||||
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
|
"startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}},
|
||||||
"getBacklightSettings": {Func: rpcGetBacklightSettings},
|
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices},
|
||||||
"getDCPowerState": {Func: rpcGetDCPowerState},
|
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}},
|
||||||
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
|
"resetConfig": {Func: rpcResetConfig},
|
||||||
"getActiveExtension": {Func: rpcGetActiveExtension},
|
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
|
||||||
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
"getBacklightSettings": {Func: rpcGetBacklightSettings},
|
||||||
"getATXState": {Func: rpcGetATXState},
|
"getDCPowerState": {Func: rpcGetDCPowerState},
|
||||||
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
|
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
|
||||||
"getSerialSettings": {Func: rpcGetSerialSettings},
|
"getActiveExtension": {Func: rpcGetActiveExtension},
|
||||||
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
|
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
||||||
"getUsbDevices": {Func: rpcGetUsbDevices},
|
"getATXState": {Func: rpcGetATXState},
|
||||||
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
|
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
|
||||||
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
|
"getSerialSettings": {Func: rpcGetSerialSettings},
|
||||||
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
|
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
|
||||||
"getScrollSensitivity": {Func: rpcGetScrollSensitivity},
|
"getUsbDevices": {Func: rpcGetUsbDevices},
|
||||||
"setScrollSensitivity": {Func: rpcSetScrollSensitivity, Params: []string{"sensitivity"}},
|
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
|
||||||
"getKeyboardMacros": {Func: getKeyboardMacros},
|
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
|
||||||
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
|
||||||
|
"getScrollSensitivity": {Func: rpcGetScrollSensitivity},
|
||||||
|
"setScrollSensitivity": {Func: rpcSetScrollSensitivity, Params: []string{"sensitivity"}},
|
||||||
|
"getKeyboardMacros": {Func: getKeyboardMacros},
|
||||||
|
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
import {
|
import {
|
||||||
|
@ -7,10 +7,21 @@ import {
|
||||||
useRTCStore,
|
useRTCStore,
|
||||||
useSettingsStore,
|
useSettingsStore,
|
||||||
useVideoStore,
|
useVideoStore,
|
||||||
|
useKeyboardMappingsStore,
|
||||||
} from "@/hooks/stores";
|
} from "@/hooks/stores";
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
|
||||||
|
|
||||||
export default function InfoBar() {
|
export default function InfoBar() {
|
||||||
|
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||||
|
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||||
|
setKeys(useKeyboardMappingsStore.keys);
|
||||||
|
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||||
|
});
|
||||||
|
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||||
|
}, []);
|
||||||
|
|
||||||
const activeKeys = useHidStore(state => state.activeKeys);
|
const activeKeys = useHidStore(state => state.activeKeys);
|
||||||
const activeModifiers = useHidStore(state => state.activeModifiers);
|
const activeModifiers = useHidStore(state => state.activeModifiers);
|
||||||
const mouseX = useMouseStore(state => state.mouseX);
|
const mouseX = useMouseStore(state => state.mouseX);
|
||||||
|
|
|
@ -4,21 +4,22 @@ import { Button } from "@/components/Button";
|
||||||
import { Combobox } from "@/components/Combobox";
|
import { Combobox } from "@/components/Combobox";
|
||||||
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||||
import Card from "@/components/Card";
|
import Card from "@/components/Card";
|
||||||
import { keys, modifiers, keyDisplayMap } from "@/keyboardMappings";
|
import { keyDisplayMap } from "@/keyboardMappings/KeyboardLayouts";
|
||||||
|
import { keysUS, modifiersUS } from '../keyboardMappings/layouts/us';
|
||||||
import { MAX_KEYS_PER_STEP, DEFAULT_DELAY } from "@/constants/macros";
|
import { MAX_KEYS_PER_STEP, DEFAULT_DELAY } from "@/constants/macros";
|
||||||
import FieldLabel from "@/components/FieldLabel";
|
import FieldLabel from "@/components/FieldLabel";
|
||||||
|
|
||||||
// Filter out modifier keys since they're handled in the modifiers section
|
// Filter out modifier keys since they're handled in the modifiers section
|
||||||
const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta'];
|
const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta'];
|
||||||
|
|
||||||
const keyOptions = Object.keys(keys)
|
const keyOptions = Object.keys(keysUS)
|
||||||
.filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix)))
|
.filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix)))
|
||||||
.map(key => ({
|
.map(key => ({
|
||||||
value: key,
|
value: key,
|
||||||
label: keyDisplayMap[key] || key,
|
label: keyDisplayMap[key] || key,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const modifierOptions = Object.keys(modifiers).map(modifier => ({
|
const modifierOptions = Object.keys(modifiersUS).map(modifier => ({
|
||||||
value: modifier,
|
value: modifier,
|
||||||
label: modifier.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1 $2"),
|
label: modifier.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1 $2"),
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -27,6 +27,7 @@ type SelectMenuProps = Pick<
|
||||||
const sizes = {
|
const sizes = {
|
||||||
XS: "h-[24.5px] pl-3 pr-8 text-xs",
|
XS: "h-[24.5px] pl-3 pr-8 text-xs",
|
||||||
SM: "h-[32px] pl-3 pr-8 text-[13px]",
|
SM: "h-[32px] pl-3 pr-8 text-[13px]",
|
||||||
|
SM_Wide: "h-[32px] pl-3 pr-8 mr-5 text-[13px]",
|
||||||
MD: "h-[40px] pl-4 pr-10 text-sm",
|
MD: "h-[40px] pl-4 pr-10 text-sm",
|
||||||
LG: "h-[48px] pl-4 pr-10 px-5 text-base",
|
LG: "h-[48px] pl-4 pr-10 px-5 text-base",
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,9 +9,9 @@ import { Button } from "@components/Button";
|
||||||
|
|
||||||
import "react-simple-keyboard/build/css/index.css";
|
import "react-simple-keyboard/build/css/index.css";
|
||||||
|
|
||||||
import { useHidStore, useUiStore } from "@/hooks/stores";
|
import { useHidStore, useUiStore, useKeyboardMappingsStore } from "@/hooks/stores";
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
import { keys, modifiers, keyDisplayMap } from "@/keyboardMappings";
|
import { keyDisplayMap } from "@/keyboardMappings/KeyboardLayouts";
|
||||||
import useKeyboard from "@/hooks/useKeyboard";
|
import useKeyboard from "@/hooks/useKeyboard";
|
||||||
import DetachIconRaw from "@/assets/detach-icon.svg";
|
import DetachIconRaw from "@/assets/detach-icon.svg";
|
||||||
import AttachIconRaw from "@/assets/attach-icon.svg";
|
import AttachIconRaw from "@/assets/attach-icon.svg";
|
||||||
|
@ -25,7 +25,40 @@ const AttachIcon = ({ className }: { className?: string }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function KeyboardWrapper() {
|
function KeyboardWrapper() {
|
||||||
const [layoutName, setLayoutName] = useState("default");
|
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||||
|
const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
||||||
|
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||||
|
setKeys(useKeyboardMappingsStore.keys);
|
||||||
|
setChars(useKeyboardMappingsStore.chars);
|
||||||
|
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||||
|
setMappingsEnabled(useKeyboardMappingsStore.getMappingState());
|
||||||
|
});
|
||||||
|
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [layoutName, setLayoutName] = useState("default");
|
||||||
|
const [mappingsEnabled, setMappingsEnabled] = useState(useKeyboardMappingsStore.getMappingState());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mappingsEnabled) {
|
||||||
|
if (layoutName == "default" ) {
|
||||||
|
setLayoutName("mappedLower")
|
||||||
|
}
|
||||||
|
if (layoutName == "shift") {
|
||||||
|
setLayoutName("mappedUpper")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (layoutName == "mappedLower") {
|
||||||
|
setLayoutName("default")
|
||||||
|
}
|
||||||
|
if (layoutName == "mappedUpper") {
|
||||||
|
setLayoutName("shift")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [mappingsEnabled, layoutName]);
|
||||||
|
|
||||||
const keyboardRef = useRef<HTMLDivElement>(null);
|
const keyboardRef = useRef<HTMLDivElement>(null);
|
||||||
const showAttachedVirtualKeyboard = useUiStore(
|
const showAttachedVirtualKeyboard = useUiStore(
|
||||||
|
@ -112,16 +145,25 @@ function KeyboardWrapper() {
|
||||||
};
|
};
|
||||||
}, [endDrag, onDrag, startDrag]);
|
}, [endDrag, onDrag, startDrag]);
|
||||||
|
|
||||||
|
// TODO implement meta key and meta key modifer
|
||||||
|
// TODO implement hold functionality for key combos. (add a hold button, add all keys to an array, when released send as one)
|
||||||
const onKeyDown = useCallback(
|
const onKeyDown = useCallback(
|
||||||
(key: string) => {
|
(key: string) => {
|
||||||
|
const cleanKey = key.replace(/[()]/g, "");
|
||||||
|
// Mappings
|
||||||
|
const { key: mappedKey, shift, altLeft, altRight } = chars[cleanKey] ?? {};
|
||||||
|
|
||||||
const isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight";
|
const isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight";
|
||||||
const isKeyCaps = key === "CapsLock";
|
const isKeyCaps = key === "CapsLock";
|
||||||
const cleanKey = key.replace(/[()]/g, "");
|
const keyHasShiftModifier = (key.includes("(") && key !== "(") || shift;
|
||||||
const keyHasShiftModifier = key.includes("(");
|
|
||||||
|
|
||||||
// Handle toggle of layout for shift or caps lock
|
// Handle toggle of layout for shift or caps lock
|
||||||
const toggleLayout = () => {
|
const toggleLayout = () => {
|
||||||
setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default"));
|
if (mappingsEnabled) {
|
||||||
|
setLayoutName(prevLayout => (prevLayout === "mappedLower" ? "mappedUpper" : "mappedLower"));
|
||||||
|
} else {
|
||||||
|
setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default"));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (key === "CtrlAltDelete") {
|
if (key === "CtrlAltDelete") {
|
||||||
|
@ -143,10 +185,17 @@ function KeyboardWrapper() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKeyShift || isKeyCaps) {
|
if (isKeyShift || (!(layoutName == "shift" || layoutName == "mappedUpper") && isCapsLockActive)) {
|
||||||
toggleLayout();
|
toggleLayout();
|
||||||
|
}
|
||||||
|
|
||||||
if (isCapsLockActive) {
|
if (layoutName == "shift" || layoutName == "mappedUpper") {
|
||||||
|
if (!isCapsLockActive) {
|
||||||
|
toggleLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyCaps && isCapsLockActive) {
|
||||||
|
toggleLayout();
|
||||||
setIsCapsLockActive(false);
|
setIsCapsLockActive(false);
|
||||||
sendKeyboardEvent([keys["CapsLock"]], []);
|
sendKeyboardEvent([keys["CapsLock"]], []);
|
||||||
return;
|
return;
|
||||||
|
@ -155,25 +204,30 @@ function KeyboardWrapper() {
|
||||||
|
|
||||||
// Handle caps lock state change
|
// Handle caps lock state change
|
||||||
if (isKeyCaps) {
|
if (isKeyCaps) {
|
||||||
|
toggleLayout();
|
||||||
setIsCapsLockActive(!isCapsLockActive);
|
setIsCapsLockActive(!isCapsLockActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect new active keys and modifiers
|
// Collect new active keys and modifiers
|
||||||
const newKeys = keys[cleanKey] ? [keys[cleanKey]] : [];
|
const newKeys = keys[mappedKey ?? cleanKey] ? [keys[mappedKey ?? cleanKey]] : [];
|
||||||
const newModifiers =
|
const newModifiers =
|
||||||
keyHasShiftModifier && !isCapsLockActive ? [modifiers["ShiftLeft"]] : [];
|
[
|
||||||
|
((shift || isKeyShift)? modifiers['ShiftLeft'] : 0),
|
||||||
|
(altLeft? modifiers['AltLeft'] : 0),
|
||||||
|
(altRight? modifiers['AltRight'] : 0),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
// Update current keys and modifiers
|
// Update current keys and modifiers
|
||||||
sendKeyboardEvent(newKeys, newModifiers);
|
sendKeyboardEvent(newKeys, [...new Set(newModifiers)]);
|
||||||
|
|
||||||
// If shift was used as a modifier and caps lock is not active, revert to default layout
|
// If shift was used as a modifier and caps lock is not active, revert to default layout
|
||||||
if (keyHasShiftModifier && !isCapsLockActive) {
|
if (keyHasShiftModifier && !isCapsLockActive) {
|
||||||
setLayoutName("default");
|
setLayoutName(mappingsEnabled ? "mappedLower" : "default");
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(resetKeyboardState, 100);
|
setTimeout(resetKeyboardState, 100);
|
||||||
},
|
},
|
||||||
[isCapsLockActive, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive],
|
[isCapsLockActive, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive, mappingsEnabled, chars, keys, modifiers, layoutName],
|
||||||
);
|
);
|
||||||
|
|
||||||
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
|
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
|
||||||
|
@ -280,6 +334,25 @@ function KeyboardWrapper() {
|
||||||
"ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight",
|
"ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight",
|
||||||
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight",
|
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight",
|
||||||
],
|
],
|
||||||
|
mappedLower: [
|
||||||
|
"CtrlAltDelete AltMetaEscape",
|
||||||
|
"Escape F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12",
|
||||||
|
"` 1 2 3 4 5 6 7 8 9 0 - = Backspace",
|
||||||
|
"Tab q w e r t y u i o p [ ] \\",
|
||||||
|
"CapsLock a s d f g h j k l ; ' Enter",
|
||||||
|
"ShiftLeft z x c v b n m , . / ShiftRight",
|
||||||
|
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight"
|
||||||
|
],
|
||||||
|
|
||||||
|
mappedUpper: [
|
||||||
|
"CtrlAltDelete AltMetaEscape",
|
||||||
|
"Escape F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12",
|
||||||
|
"~ ! @ # $ % ^ & * ( ) _ + Backspace",
|
||||||
|
"Tab Q W E R T Y U I O P { } |",
|
||||||
|
"CapsLock A S D F G H J K L : \" Enter",
|
||||||
|
"ShiftLeft Z X C V B N M < > ? ShiftRight",
|
||||||
|
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight"
|
||||||
|
],
|
||||||
}}
|
}}
|
||||||
disableButtonHold={true}
|
disableButtonHold={true}
|
||||||
mergeDisplay={true}
|
mergeDisplay={true}
|
||||||
|
@ -332,4 +405,4 @@ function KeyboardWrapper() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KeyboardWrapper;
|
export default KeyboardWrapper;
|
|
@ -7,8 +7,8 @@ import {
|
||||||
useRTCStore,
|
useRTCStore,
|
||||||
useSettingsStore,
|
useSettingsStore,
|
||||||
useVideoStore,
|
useVideoStore,
|
||||||
|
useKeyboardMappingsStore,
|
||||||
} from "@/hooks/stores";
|
} from "@/hooks/stores";
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
|
||||||
import { useResizeObserver } from "@/hooks/useResizeObserver";
|
import { useResizeObserver } from "@/hooks/useResizeObserver";
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
import VirtualKeyboard from "@components/VirtualKeyboard";
|
import VirtualKeyboard from "@components/VirtualKeyboard";
|
||||||
|
@ -24,7 +24,30 @@ import {
|
||||||
NoAutoplayPermissionsOverlay,
|
NoAutoplayPermissionsOverlay,
|
||||||
} from "./VideoOverlay";
|
} from "./VideoOverlay";
|
||||||
|
|
||||||
|
// TODO Implement keyboard lock API to resolve #127
|
||||||
|
// https://developer.chrome.com/docs/capabilities/web-apis/keyboard-lock
|
||||||
|
// An appropriate error message will need to be displayed in order to alert users to browser compatibility issues.
|
||||||
|
// This requires TLS, waiting on TLS support.
|
||||||
|
|
||||||
|
|
||||||
|
// TODO Implement keyboard mapping setup in initial JetKVM setup
|
||||||
export default function WebRTCVideo() {
|
export default function WebRTCVideo() {
|
||||||
|
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||||
|
const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
||||||
|
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||||
|
|
||||||
|
// This map is used to maintain consistency between localised key mappings
|
||||||
|
const activeKeyState = useRef<Map<string, { mappedKey: string; modifiers: { shift: boolean, altLeft?: boolean, altRight?: boolean}; }>>(new Map());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||||
|
setKeys(useKeyboardMappingsStore.keys);
|
||||||
|
setChars(useKeyboardMappingsStore.chars);
|
||||||
|
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||||
|
});
|
||||||
|
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Video and stream related refs and states
|
// Video and stream related refs and states
|
||||||
const videoElm = useRef<HTMLVideoElement>(null);
|
const videoElm = useRef<HTMLVideoElement>(null);
|
||||||
const mediaStream = useRTCStore(state => state.mediaStream);
|
const mediaStream = useRTCStore(state => state.mediaStream);
|
||||||
|
@ -233,13 +256,13 @@ export default function WebRTCVideo() {
|
||||||
sendAbsMouseMovement(0, 0, 0);
|
sendAbsMouseMovement(0, 0, 0);
|
||||||
}, [sendAbsMouseMovement]);
|
}, [sendAbsMouseMovement]);
|
||||||
|
|
||||||
|
// TODO this needs reworked ot work with mappings
|
||||||
// Keyboard-related
|
// Keyboard-related
|
||||||
const handleModifierKeys = useCallback(
|
const handleModifierKeys = useCallback(
|
||||||
(e: KeyboardEvent, activeModifiers: number[]) => {
|
(e: KeyboardEvent, activeModifiers: number[], mappedKeyModifers: { shift: boolean; altLeft: boolean; altRight: boolean; }) => {
|
||||||
const { shiftKey, ctrlKey, altKey, metaKey } = e;
|
const { shiftKey, ctrlKey, altKey, metaKey } = e;
|
||||||
|
|
||||||
const filteredModifiers = activeModifiers.filter(Boolean);
|
const filteredModifiers = activeModifiers.filter(Boolean);
|
||||||
|
|
||||||
// Example: activeModifiers = [0x01, 0x02, 0x04, 0x08]
|
// Example: activeModifiers = [0x01, 0x02, 0x04, 0x08]
|
||||||
// Assuming 0x01 = ControlLeft, 0x02 = ShiftLeft, 0x04 = AltLeft, 0x08 = MetaLeft
|
// Assuming 0x01 = ControlLeft, 0x02 = ShiftLeft, 0x04 = AltLeft, 0x08 = MetaLeft
|
||||||
return (
|
return (
|
||||||
|
@ -250,6 +273,7 @@ export default function WebRTCVideo() {
|
||||||
.filter(
|
.filter(
|
||||||
modifier =>
|
modifier =>
|
||||||
shiftKey ||
|
shiftKey ||
|
||||||
|
mappedKeyModifers.shift ||
|
||||||
(modifier !== modifiers["ShiftLeft"] &&
|
(modifier !== modifiers["ShiftLeft"] &&
|
||||||
modifier !== modifiers["ShiftRight"]),
|
modifier !== modifiers["ShiftRight"]),
|
||||||
)
|
)
|
||||||
|
@ -268,7 +292,14 @@ export default function WebRTCVideo() {
|
||||||
.filter(
|
.filter(
|
||||||
modifier =>
|
modifier =>
|
||||||
altKey ||
|
altKey ||
|
||||||
(modifier !== modifiers["AltLeft"] && modifier !== modifiers["AltRight"]),
|
mappedKeyModifers.altLeft ||
|
||||||
|
(modifier !== modifiers["AltLeft"]),
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
modifier =>
|
||||||
|
altKey ||
|
||||||
|
mappedKeyModifers.altRight ||
|
||||||
|
(modifier !== modifiers["AltRight"])
|
||||||
)
|
)
|
||||||
// Meta: Keep if Meta is pressed or if the key isn't a Meta key
|
// Meta: Keep if Meta is pressed or if the key isn't a Meta key
|
||||||
// Example: If metaKey is true, keep all modifiers
|
// Example: If metaKey is true, keep all modifiers
|
||||||
|
@ -287,33 +318,45 @@ export default function WebRTCVideo() {
|
||||||
async (e: KeyboardEvent) => {
|
async (e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const prev = useHidStore.getState();
|
const prev = useHidStore.getState();
|
||||||
let code = e.code;
|
const code = e.code;
|
||||||
const key = e.key;
|
var localisedKey = settings.keyboardMappingEnabled ? e.key : code;
|
||||||
|
|
||||||
// if (document.activeElement?.id !== "videoFocusTrap") {
|
// if (document.activeElement?.id !== "videoFocusTrap") {hH
|
||||||
// console.log("KEYUP: Not focusing on the video", document.activeElement);
|
// console.log("KEYUP: Not focusing on the video", document.activeElement);
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
console.log(document.activeElement);
|
//
|
||||||
|
// console.log(document.activeElement);
|
||||||
|
|
||||||
setIsNumLockActive(e.getModifierState("NumLock"));
|
setIsNumLockActive(e.getModifierState("NumLock"));
|
||||||
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
||||||
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
||||||
|
|
||||||
if (code == "IntlBackslash" && ["`", "~"].includes(key)) {
|
/*if (code == "IntlBackslash" && ["`", "~"].includes(key)) {
|
||||||
code = "Backquote";
|
code = "Backquote";
|
||||||
} else if (code == "Backquote" && ["§", "±"].includes(key)) {
|
} else if (code == "Backquote" && ["§", "±"].includes(key)) {
|
||||||
code = "IntlBackslash";
|
code = "IntlBackslash";
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
const { key: mappedKey, shift, altLeft, altRight } = chars[localisedKey] ?? { key: code };
|
||||||
|
//if (!key) continue;
|
||||||
|
|
||||||
|
// Add the mapped key to keyState
|
||||||
|
activeKeyState.current.set(e.code, { mappedKey, modifiers: {shift, altLeft, altRight}});
|
||||||
|
|
||||||
// Add the key to the active keys
|
// Add the key to the active keys
|
||||||
const newKeys = [...prev.activeKeys, keys[code]].filter(Boolean);
|
const newKeys = [...prev.activeKeys, keys[mappedKey]].filter(Boolean);
|
||||||
|
|
||||||
|
// TODO I feel this may not be applying the modifiers correctly, specifically altRight
|
||||||
// Add the modifier to the active modifiers
|
// Add the modifier to the active modifiers
|
||||||
const newModifiers = handleModifierKeys(e, [
|
const newModifiers = handleModifierKeys(e, [
|
||||||
...prev.activeModifiers,
|
...prev.activeModifiers,
|
||||||
modifiers[code],
|
modifiers[code],
|
||||||
]);
|
(shift? modifiers['ShiftLeft'] : 0),
|
||||||
|
(altLeft? modifiers['AltLeft'] : 0),
|
||||||
|
(altRight? modifiers['AltRight'] : 0),],
|
||||||
|
{shift: shift, altLeft: altLeft? true : false, altRight: altRight ? true : false}
|
||||||
|
);
|
||||||
|
|
||||||
// When pressing the meta key + another key, the key will never trigger a keyup
|
// When pressing the meta key + another key, the key will never trigger a keyup
|
||||||
// event, so we need to clear the keys after a short delay
|
// event, so we need to clear the keys after a short delay
|
||||||
|
@ -323,6 +366,8 @@ export default function WebRTCVideo() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const prev = useHidStore.getState();
|
const prev = useHidStore.getState();
|
||||||
sendKeyboardEvent([], newModifiers || prev.activeModifiers);
|
sendKeyboardEvent([], newModifiers || prev.activeModifiers);
|
||||||
|
activeKeyState.current.delete("MetaLeft");
|
||||||
|
activeKeyState.current.delete("MetaRight");
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,6 +379,10 @@ export default function WebRTCVideo() {
|
||||||
setIsScrollLockActive,
|
setIsScrollLockActive,
|
||||||
handleModifierKeys,
|
handleModifierKeys,
|
||||||
sendKeyboardEvent,
|
sendKeyboardEvent,
|
||||||
|
chars,
|
||||||
|
keys,
|
||||||
|
modifiers,
|
||||||
|
settings,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -346,14 +395,125 @@ export default function WebRTCVideo() {
|
||||||
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
||||||
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
||||||
|
|
||||||
|
// Check if the released key is a modifier (e.g., Shift, Alt, Control)
|
||||||
|
const isModifierKey =
|
||||||
|
e.code === "ShiftLeft" ||
|
||||||
|
e.code === "ShiftRight" ||
|
||||||
|
e.code === "AltLeft" ||
|
||||||
|
e.code === "AltRight" ||
|
||||||
|
e.code === "ControlLeft" ||
|
||||||
|
e.code === "ControlRight";
|
||||||
|
|
||||||
|
var newKeys = prev.activeKeys;
|
||||||
|
|
||||||
|
// Handle modifier release
|
||||||
|
if (isModifierKey) {
|
||||||
|
// Update all affected keys when this modifier is released
|
||||||
|
activeKeyState.current.forEach((value, code) => {
|
||||||
|
const { mappedKey, modifiers: mappedModifiers} = value;
|
||||||
|
|
||||||
|
// Remove the released modifier from the modifier bitmask
|
||||||
|
//const updatedModifiers = modifiers & ~modifiers[e.code];
|
||||||
|
|
||||||
|
// Recalculate the remapped key based on the updated modifiers
|
||||||
|
//const updatedMappedKey = chars[originalKey]?.key || originalKey;
|
||||||
|
|
||||||
|
var removeCurrentKey = false;
|
||||||
|
|
||||||
|
// Shift Handling
|
||||||
|
if (mappedModifiers.shift && (e.code === "ShiftLeft" || e.code === "ShiftRight")) {
|
||||||
|
activeKeyState.current.delete(code);
|
||||||
|
removeCurrentKey = true;
|
||||||
|
};
|
||||||
|
// Left Alt handling
|
||||||
|
if (mappedModifiers.altLeft && e.code === "AltLeft") {
|
||||||
|
activeKeyState.current.delete(code);
|
||||||
|
removeCurrentKey = true;
|
||||||
|
};
|
||||||
|
// Right Alt handling
|
||||||
|
if (mappedModifiers.altRight && e.code === "AltRight") {
|
||||||
|
activeKeyState.current.delete(code);
|
||||||
|
removeCurrentKey = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (removeCurrentKey) {
|
||||||
|
newKeys = newKeys
|
||||||
|
.filter(k => k !== keys[mappedKey]) // Remove the previously mapped key
|
||||||
|
//.concat(keys[updatedMappedKey]) // Add the new remapped key, don't need to do this.
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const newModifiers = handleModifierKeys(
|
||||||
|
e,
|
||||||
|
prev.activeModifiers.filter(k => k !== modifiers[e.code]),
|
||||||
|
{shift: false, altLeft: false, altRight: false}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the keyState
|
||||||
|
/*activeKeyState.current.delete(code);/*.set(code, {
|
||||||
|
mappedKey: updatedMappedKey,
|
||||||
|
modifiers: updatedModifiers,
|
||||||
|
originalKey,
|
||||||
|
});*/
|
||||||
|
|
||||||
|
// Remove the modifer key from keyState
|
||||||
|
activeKeyState.current.delete(e.code);
|
||||||
|
|
||||||
|
// This is required to filter out the alt keys as well as the modifier.
|
||||||
|
newKeys = newKeys
|
||||||
|
.filter(k => k !== keys[e.code]) // Remove the previously mapped key
|
||||||
|
//.concat(keys[updatedMappedKey]) // Add the new remapped key, don't need to do this.
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// Send the updated HID payload
|
||||||
|
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||||
|
|
||||||
|
return; // Exit as we've already handled the modifier release
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the mapped key and modifiers from keyState
|
||||||
|
const keyInfo = activeKeyState.current.get(e.code);
|
||||||
|
if (!keyInfo) return; // Ignore if no record exists
|
||||||
|
|
||||||
|
const { mappedKey, modifiers: modifier } = keyInfo;
|
||||||
|
|
||||||
|
// Remove the key from keyState
|
||||||
|
activeKeyState.current.delete(e.code);
|
||||||
|
|
||||||
|
// Filter out the key that was just released
|
||||||
|
newKeys = newKeys.filter(k => k !== keys[mappedKey]).filter(Boolean);
|
||||||
|
|
||||||
|
// Filter out the associated modifier
|
||||||
|
//const newModifiers = prev.activeModifiers.filter(k => k !== modifier).filter(Boolean);
|
||||||
|
const newModifiers = handleModifierKeys(
|
||||||
|
e,
|
||||||
|
prev.activeModifiers.filter(k => {
|
||||||
|
if (modifier.shift && k == modifiers["ShiftLeft"]) return false;
|
||||||
|
if (modifier.altLeft && k == modifiers["AltLeft"]) return false;
|
||||||
|
if (modifier.altRight && k == modifiers["AltRight"]) return false;
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
{shift: modifier.shift, altLeft: modifier.altLeft? true : false, altRight: modifier.altRight ? true : false}
|
||||||
|
);
|
||||||
|
/*
|
||||||
|
const { key: mappedKey/*, shift, altLeft, altRight*//* } = chars[e.key] ?? { key: e.code };
|
||||||
|
//if (!key) continue;
|
||||||
|
console.log("Mapped Key: " + mappedKey)
|
||||||
|
// Build the modifier bitmask
|
||||||
|
/*const modifier =
|
||||||
|
(shift ? modifiers["ShiftLeft"] : 0) |
|
||||||
|
(altLeft ? modifiers["AltLeft"] : 0) |
|
||||||
|
(altRight ? modifiers["AltRight"] : 0); // This is important for a lot of keyboard layouts, right and left alt have different functions*//*
|
||||||
|
|
||||||
// Filtering out the key that was just released (keys[e.code])
|
// Filtering out the key that was just released (keys[e.code])
|
||||||
const newKeys = prev.activeKeys.filter(k => k !== keys[e.code]).filter(Boolean);
|
const newKeys = prev.activeKeys.filter(k => k !== keys[mappedKey]).filter(Boolean);
|
||||||
|
|
||||||
// Filter out the modifier that was just released
|
// Filter out the modifier that was just released
|
||||||
const newModifiers = handleModifierKeys(
|
const newModifiers = handleModifierKeys(
|
||||||
e,
|
e,
|
||||||
prev.activeModifiers.filter(k => k !== modifiers[e.code]),
|
prev.activeModifiers.filter(k => k !== modifiers[e.code]),
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||||
},
|
},
|
||||||
|
@ -363,6 +523,9 @@ export default function WebRTCVideo() {
|
||||||
setIsScrollLockActive,
|
setIsScrollLockActive,
|
||||||
handleModifierKeys,
|
handleModifierKeys,
|
||||||
sendKeyboardEvent,
|
sendKeyboardEvent,
|
||||||
|
chars,
|
||||||
|
keys,
|
||||||
|
modifiers,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -444,6 +607,7 @@ export default function WebRTCVideo() {
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
|
activeKeyState.current.clear();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[keyDownHandler, keyUpHandler, resetKeyboardState, sendKeyboardEvent],
|
[keyDownHandler, keyUpHandler, resetKeyboardState, sendKeyboardEvent],
|
||||||
|
|
|
@ -8,8 +8,7 @@ import { GridCard } from "@components/Card";
|
||||||
import { TextAreaWithLabel } from "@components/TextArea";
|
import { TextAreaWithLabel } from "@components/TextArea";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { useHidStore, useRTCStore, useUiStore } from "@/hooks/stores";
|
import { useHidStore, useRTCStore, useUiStore, useKeyboardMappingsStore } from "@/hooks/stores";
|
||||||
import { chars, keys, modifiers } from "@/keyboardMappings";
|
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
||||||
const hidKeyboardPayload = (keys: number[], modifier: number) => {
|
const hidKeyboardPayload = (keys: number[], modifier: number) => {
|
||||||
|
@ -17,6 +16,19 @@ const hidKeyboardPayload = (keys: number[], modifier: number) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PasteModal() {
|
export default function PasteModal() {
|
||||||
|
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||||
|
const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
||||||
|
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||||
|
setKeys(useKeyboardMappingsStore.keys);
|
||||||
|
setChars(useKeyboardMappingsStore.chars);
|
||||||
|
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||||
|
});
|
||||||
|
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||||
|
}, []);
|
||||||
|
|
||||||
const TextAreaRef = useRef<HTMLTextAreaElement>(null);
|
const TextAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const setPasteMode = useHidStore(state => state.setPasteModeEnabled);
|
const setPasteMode = useHidStore(state => state.setPasteModeEnabled);
|
||||||
const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap);
|
const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap);
|
||||||
|
@ -42,13 +54,19 @@ export default function PasteModal() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const char of text) {
|
for (const char of text) {
|
||||||
const { key, shift } = chars[char] ?? {};
|
const { key, shift, altLeft, altRight } = chars[char] ?? {};
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
|
|
||||||
|
// Build the modifier bitmask
|
||||||
|
const modifier =
|
||||||
|
(shift ? modifiers["ShiftLeft"] : 0) |
|
||||||
|
(altLeft ? modifiers["AltLeft"] : 0) |
|
||||||
|
(altRight ? modifiers["AltRight"] : 0); // This is important for a lot of keyboard layouts, right and left alt have different functions
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
send(
|
send(
|
||||||
"keyboardReport",
|
"keyboardReport",
|
||||||
hidKeyboardPayload([keys[key]], shift ? modifiers["ShiftLeft"] : 0),
|
hidKeyboardPayload([keys[key]], modifier),
|
||||||
params => {
|
params => {
|
||||||
if ("error" in params) return reject(params.error);
|
if ("error" in params) return reject(params.error);
|
||||||
send("keyboardReport", hidKeyboardPayload([], 0), params => {
|
send("keyboardReport", hidKeyboardPayload([], 0), params => {
|
||||||
|
@ -63,7 +81,7 @@ export default function PasteModal() {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
notifications.error("Failed to paste text");
|
notifications.error("Failed to paste text");
|
||||||
}
|
}
|
||||||
}, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode]);
|
}, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, chars, keys, modifiers]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (TextAreaRef.current) {
|
if (TextAreaRef.current) {
|
||||||
|
@ -125,9 +143,12 @@ export default function PasteModal() {
|
||||||
<div className="mt-2 flex items-center gap-x-2">
|
<div className="mt-2 flex items-center gap-x-2">
|
||||||
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
|
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
|
||||||
<span className="text-xs text-red-500 dark:text-red-400">
|
<span className="text-xs text-red-500 dark:text-red-400">
|
||||||
The following characters won't be pasted:{" "}
|
The following characters won't be pasted as the current keyboard layout does not contain a valid mapping:{" "}
|
||||||
{invalidChars.join(", ")}
|
{invalidChars.join(", ")}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-xs text-red-500 dark:text-red-400">
|
||||||
|
Tip: You can set your desired keyboard layout in settings, and remember to enable keyboard mapping.
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { createJSONStorage, persist } from "zustand/middleware";
|
import { createJSONStorage, persist } from "zustand/middleware";
|
||||||
|
import { getKeyboardMappings } from "@/keyboardMappings/KeyboardLayouts";
|
||||||
import { MAX_STEPS_PER_MACRO, MAX_TOTAL_MACROS, MAX_KEYS_PER_STEP } from "@/constants/macros";
|
import { MAX_STEPS_PER_MACRO, MAX_TOTAL_MACROS, MAX_KEYS_PER_STEP } from "@/constants/macros";
|
||||||
|
|
||||||
// Define the JsonRpc types for better type checking
|
// Define the JsonRpc types for better type checking
|
||||||
|
@ -285,6 +286,9 @@ interface SettingsState {
|
||||||
mouseMode: string;
|
mouseMode: string;
|
||||||
setMouseMode: (mode: string) => void;
|
setMouseMode: (mode: string) => void;
|
||||||
|
|
||||||
|
keyboardMappingEnabled: boolean;
|
||||||
|
setkeyboardMappingEnabled: (enabled: boolean) => void;
|
||||||
|
|
||||||
debugMode: boolean;
|
debugMode: boolean;
|
||||||
setDebugMode: (enabled: boolean) => void;
|
setDebugMode: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
@ -299,6 +303,9 @@ interface SettingsState {
|
||||||
export const useSettingsStore = create(
|
export const useSettingsStore = create(
|
||||||
persist<SettingsState>(
|
persist<SettingsState>(
|
||||||
set => ({
|
set => ({
|
||||||
|
keyboardMappingEnabled: false,
|
||||||
|
setkeyboardMappingEnabled: enabled => set({keyboardMappingEnabled: enabled}),
|
||||||
|
|
||||||
isCursorHidden: false,
|
isCursorHidden: false,
|
||||||
setCursorVisibility: enabled => set({ isCursorHidden: enabled }),
|
setCursorVisibility: enabled => set({ isCursorHidden: enabled }),
|
||||||
|
|
||||||
|
@ -631,6 +638,69 @@ export const useUsbConfigModalStore = create<UsbConfigModalState>(set => ({
|
||||||
setErrorMessage: message => set({ errorMessage: message }),
|
setErrorMessage: message => set({ errorMessage: message }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
class KeyboardMappingsStore {
|
||||||
|
private _layout: string = 'en-US';
|
||||||
|
private _subscribers: (() => void)[] = [];
|
||||||
|
private _mappingsEnabled: boolean = false;
|
||||||
|
|
||||||
|
public keys = getKeyboardMappings(this._layout).keys;
|
||||||
|
public chars = getKeyboardMappings(this._layout).chars;
|
||||||
|
public modifiers = getKeyboardMappings(this._layout).modifiers;
|
||||||
|
|
||||||
|
private mappedKeys = getKeyboardMappings(this._layout).keys;
|
||||||
|
private mappedChars = getKeyboardMappings(this._layout).chars;
|
||||||
|
private mappedModifiers = getKeyboardMappings(this._layout).modifiers;
|
||||||
|
|
||||||
|
setLayout(newLayout: string) {
|
||||||
|
if (this._layout === newLayout) return;
|
||||||
|
this._layout = newLayout;
|
||||||
|
const updatedMappings = getKeyboardMappings(newLayout);
|
||||||
|
this.mappedKeys = updatedMappings.keys;
|
||||||
|
this.mappedChars = updatedMappings.chars;
|
||||||
|
this.mappedModifiers = updatedMappings.modifiers;
|
||||||
|
if (this._mappingsEnabled) {
|
||||||
|
this.keys = this.mappedKeys;
|
||||||
|
this.chars = this.mappedChars;
|
||||||
|
this.modifiers = this.mappedModifiers;
|
||||||
|
this._notifySubscribers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMappingsState(enabled: boolean) {
|
||||||
|
this._mappingsEnabled = enabled;
|
||||||
|
if (this._mappingsEnabled) {
|
||||||
|
this.keys = this.mappedKeys;
|
||||||
|
this.chars = this.mappedChars;
|
||||||
|
this.modifiers = this.mappedModifiers;
|
||||||
|
} else {
|
||||||
|
this.keys = getKeyboardMappings('us').keys;
|
||||||
|
this.chars = getKeyboardMappings('us').chars;
|
||||||
|
this.modifiers = getKeyboardMappings('us').modifiers;
|
||||||
|
}
|
||||||
|
this._notifySubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
getMappingState() {
|
||||||
|
return this._mappingsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayout() {
|
||||||
|
return this._layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(callback: () => void) {
|
||||||
|
this._subscribers.push(callback);
|
||||||
|
return () => {
|
||||||
|
this._subscribers = this._subscribers.filter(sub => sub !== callback); // Cleanup
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _notifySubscribers() {
|
||||||
|
this._subscribers.forEach(callback => callback());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useKeyboardMappingsStore = new KeyboardMappingsStore();
|
||||||
interface LocalAuthModalState {
|
interface LocalAuthModalState {
|
||||||
modalView:
|
modalView:
|
||||||
| "createPassword"
|
| "createPassword"
|
||||||
|
@ -804,4 +874,4 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
|
||||||
set({ loading: false });
|
set({ loading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useCallback } from "react";
|
import { useCallback, useState, useEffect } from "react";
|
||||||
|
|
||||||
import { useHidStore, useRTCStore } from "@/hooks/stores";
|
import { useHidStore, useRTCStore } from "@/hooks/stores";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
import { useKeyboardMappingsStore } from "@/hooks/stores";
|
||||||
|
|
||||||
export default function useKeyboard() {
|
export default function useKeyboard() {
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
|
@ -12,6 +12,17 @@ export default function useKeyboard() {
|
||||||
state => state.updateActiveKeysAndModifiers,
|
state => state.updateActiveKeysAndModifiers,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||||
|
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||||
|
setKeys(useKeyboardMappingsStore.keys);
|
||||||
|
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||||
|
});
|
||||||
|
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||||
|
}, []);
|
||||||
|
|
||||||
const sendKeyboardEvent = useCallback(
|
const sendKeyboardEvent = useCallback(
|
||||||
(keys: number[], modifiers: number[]) => {
|
(keys: number[], modifiers: number[]) => {
|
||||||
if (rpcDataChannel?.readyState !== "open") return;
|
if (rpcDataChannel?.readyState !== "open") return;
|
||||||
|
|
|
@ -1,291 +0,0 @@
|
||||||
export const keys = {
|
|
||||||
AltLeft: 0xe2,
|
|
||||||
AltRight: 0xe6,
|
|
||||||
ArrowDown: 0x51,
|
|
||||||
ArrowLeft: 0x50,
|
|
||||||
ArrowRight: 0x4f,
|
|
||||||
ArrowUp: 0x52,
|
|
||||||
Backquote: 0x35,
|
|
||||||
Backslash: 0x31,
|
|
||||||
Backspace: 0x2a,
|
|
||||||
BracketLeft: 0x2f,
|
|
||||||
BracketRight: 0x30,
|
|
||||||
CapsLock: 0x39,
|
|
||||||
Comma: 0x36,
|
|
||||||
ContextMenu: 0,
|
|
||||||
Delete: 0x4c,
|
|
||||||
Digit0: 0x27,
|
|
||||||
Digit1: 0x1e,
|
|
||||||
Digit2: 0x1f,
|
|
||||||
Digit3: 0x20,
|
|
||||||
Digit4: 0x21,
|
|
||||||
Digit5: 0x22,
|
|
||||||
Digit6: 0x23,
|
|
||||||
Digit7: 0x24,
|
|
||||||
Digit8: 0x25,
|
|
||||||
Digit9: 0x26,
|
|
||||||
End: 0x4d,
|
|
||||||
Enter: 0x28,
|
|
||||||
Equal: 0x2e,
|
|
||||||
Escape: 0x29,
|
|
||||||
F1: 0x3a,
|
|
||||||
F2: 0x3b,
|
|
||||||
F3: 0x3c,
|
|
||||||
F4: 0x3d,
|
|
||||||
F5: 0x3e,
|
|
||||||
F6: 0x3f,
|
|
||||||
F7: 0x40,
|
|
||||||
F8: 0x41,
|
|
||||||
F9: 0x42,
|
|
||||||
F10: 0x43,
|
|
||||||
F11: 0x44,
|
|
||||||
F12: 0x45,
|
|
||||||
F13: 0x68,
|
|
||||||
Home: 0x4a,
|
|
||||||
Insert: 0x49,
|
|
||||||
IntlBackslash: 0x31,
|
|
||||||
KeyA: 0x04,
|
|
||||||
KeyB: 0x05,
|
|
||||||
KeyC: 0x06,
|
|
||||||
KeyD: 0x07,
|
|
||||||
KeyE: 0x08,
|
|
||||||
KeyF: 0x09,
|
|
||||||
KeyG: 0x0a,
|
|
||||||
KeyH: 0x0b,
|
|
||||||
KeyI: 0x0c,
|
|
||||||
KeyJ: 0x0d,
|
|
||||||
KeyK: 0x0e,
|
|
||||||
KeyL: 0x0f,
|
|
||||||
KeyM: 0x10,
|
|
||||||
KeyN: 0x11,
|
|
||||||
KeyO: 0x12,
|
|
||||||
KeyP: 0x13,
|
|
||||||
KeyQ: 0x14,
|
|
||||||
KeyR: 0x15,
|
|
||||||
KeyS: 0x16,
|
|
||||||
KeyT: 0x17,
|
|
||||||
KeyU: 0x18,
|
|
||||||
KeyV: 0x19,
|
|
||||||
KeyW: 0x1a,
|
|
||||||
KeyX: 0x1b,
|
|
||||||
KeyY: 0x1c,
|
|
||||||
KeyZ: 0x1d,
|
|
||||||
KeypadExclamation: 0xcf,
|
|
||||||
Minus: 0x2d,
|
|
||||||
NumLock: 0x53,
|
|
||||||
Numpad0: 0x62,
|
|
||||||
Numpad1: 0x59,
|
|
||||||
Numpad2: 0x5a,
|
|
||||||
Numpad3: 0x5b,
|
|
||||||
Numpad4: 0x5c,
|
|
||||||
Numpad5: 0x5d,
|
|
||||||
Numpad6: 0x5e,
|
|
||||||
Numpad7: 0x5f,
|
|
||||||
Numpad8: 0x60,
|
|
||||||
Numpad9: 0x61,
|
|
||||||
NumpadAdd: 0x57,
|
|
||||||
NumpadDivide: 0x54,
|
|
||||||
NumpadEnter: 0x58,
|
|
||||||
NumpadMultiply: 0x55,
|
|
||||||
NumpadSubtract: 0x56,
|
|
||||||
NumpadDecimal: 0x63,
|
|
||||||
PageDown: 0x4e,
|
|
||||||
PageUp: 0x4b,
|
|
||||||
Period: 0x37,
|
|
||||||
Quote: 0x34,
|
|
||||||
Semicolon: 0x33,
|
|
||||||
Slash: 0x38,
|
|
||||||
Space: 0x2c,
|
|
||||||
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,
|
|
||||||
ShiftLeft: 0x02,
|
|
||||||
ShiftRight: 0x20,
|
|
||||||
AltLeft: 0x04,
|
|
||||||
AltRight: 0x40,
|
|
||||||
MetaLeft: 0x08,
|
|
||||||
MetaRight: 0x80,
|
|
||||||
} as Record<string, number>;
|
|
||||||
|
|
||||||
export const modifierDisplayMap: Record<string, string> = {
|
|
||||||
ControlLeft: "Left Ctrl",
|
|
||||||
ControlRight: "Right Ctrl",
|
|
||||||
ShiftLeft: "Left Shift",
|
|
||||||
ShiftRight: "Right Shift",
|
|
||||||
AltLeft: "Left Alt",
|
|
||||||
AltRight: "Right Alt",
|
|
||||||
MetaLeft: "Left Meta",
|
|
||||||
MetaRight: "Right Meta",
|
|
||||||
} as Record<string, string>;
|
|
||||||
|
|
||||||
export const keyDisplayMap: Record<string, string> = {
|
|
||||||
CtrlAltDelete: "Ctrl + Alt + Delete",
|
|
||||||
AltMetaEscape: "Alt + Meta + Escape",
|
|
||||||
Escape: "esc",
|
|
||||||
Tab: "tab",
|
|
||||||
Backspace: "backspace",
|
|
||||||
Enter: "enter",
|
|
||||||
CapsLock: "caps lock",
|
|
||||||
ShiftLeft: "shift",
|
|
||||||
ShiftRight: "shift",
|
|
||||||
ControlLeft: "ctrl",
|
|
||||||
AltLeft: "alt",
|
|
||||||
AltRight: "alt",
|
|
||||||
MetaLeft: "meta",
|
|
||||||
MetaRight: "meta",
|
|
||||||
Space: " ",
|
|
||||||
Home: "home",
|
|
||||||
PageUp: "pageup",
|
|
||||||
Delete: "delete",
|
|
||||||
End: "end",
|
|
||||||
PageDown: "pagedown",
|
|
||||||
ArrowLeft: "←",
|
|
||||||
ArrowRight: "→",
|
|
||||||
ArrowUp: "↑",
|
|
||||||
ArrowDown: "↓",
|
|
||||||
|
|
||||||
// Letters
|
|
||||||
KeyA: "a", KeyB: "b", KeyC: "c", KeyD: "d", KeyE: "e",
|
|
||||||
KeyF: "f", KeyG: "g", KeyH: "h", KeyI: "i", KeyJ: "j",
|
|
||||||
KeyK: "k", KeyL: "l", KeyM: "m", KeyN: "n", KeyO: "o",
|
|
||||||
KeyP: "p", KeyQ: "q", KeyR: "r", KeyS: "s", KeyT: "t",
|
|
||||||
KeyU: "u", KeyV: "v", KeyW: "w", KeyX: "x", KeyY: "y",
|
|
||||||
KeyZ: "z",
|
|
||||||
|
|
||||||
// Numbers
|
|
||||||
Digit1: "1", Digit2: "2", Digit3: "3", Digit4: "4", Digit5: "5",
|
|
||||||
Digit6: "6", Digit7: "7", Digit8: "8", Digit9: "9", Digit0: "0",
|
|
||||||
|
|
||||||
// Symbols
|
|
||||||
Minus: "-",
|
|
||||||
Equal: "=",
|
|
||||||
BracketLeft: "[",
|
|
||||||
BracketRight: "]",
|
|
||||||
Backslash: "\\",
|
|
||||||
Semicolon: ";",
|
|
||||||
Quote: "'",
|
|
||||||
Comma: ",",
|
|
||||||
Period: ".",
|
|
||||||
Slash: "/",
|
|
||||||
Backquote: "`",
|
|
||||||
IntlBackslash: "\\",
|
|
||||||
|
|
||||||
// Function keys
|
|
||||||
F1: "F1", F2: "F2", F3: "F3", F4: "F4",
|
|
||||||
F5: "F5", F6: "F6", F7: "F7", F8: "F8",
|
|
||||||
F9: "F9", F10: "F10", F11: "F11", F12: "F12",
|
|
||||||
|
|
||||||
// Numpad
|
|
||||||
Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2",
|
|
||||||
Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5",
|
|
||||||
Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8",
|
|
||||||
Numpad9: "Num 9", NumpadAdd: "Num +", NumpadSubtract: "Num -",
|
|
||||||
NumpadMultiply: "Num *", NumpadDivide: "Num /", NumpadDecimal: "Num .",
|
|
||||||
NumpadEnter: "Num Enter"
|
|
||||||
};
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
import { keysUKApple, charsUKApple, modifiersUKApple } from './layouts/uk_apple';
|
||||||
|
import { keysUK, charsUK, modifiersUK } from './layouts/uk';
|
||||||
|
import { keysUS, charsUS, modifiersUS } from './layouts/us';
|
||||||
|
import { keysDE_T1, charsDE_T1, modifiersDE_T1 } from './layouts/de_t1';
|
||||||
|
|
||||||
|
export function getKeyboardMappings(layout: string) {
|
||||||
|
switch (layout) {
|
||||||
|
case "en-GB_apple":
|
||||||
|
return {
|
||||||
|
keys: keysUKApple,
|
||||||
|
chars: charsUKApple,
|
||||||
|
modifiers: modifiersUKApple,
|
||||||
|
};
|
||||||
|
case "en-GB":
|
||||||
|
return {
|
||||||
|
keys: keysUK,
|
||||||
|
chars: charsUK,
|
||||||
|
modifiers: modifiersUK,
|
||||||
|
};
|
||||||
|
case "de-DE":
|
||||||
|
return {
|
||||||
|
keys: keysDE_T1,
|
||||||
|
chars: charsDE_T1,
|
||||||
|
modifiers: modifiersDE_T1,
|
||||||
|
};
|
||||||
|
case "en-US":
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
keys: keysUS,
|
||||||
|
chars: charsUS,
|
||||||
|
modifiers: modifiersUS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const modifierDisplayMap: Record<string, string> = {
|
||||||
|
ControlLeft: "Left Ctrl",
|
||||||
|
ControlRight: "Right Ctrl",
|
||||||
|
ShiftLeft: "Left Shift",
|
||||||
|
ShiftRight: "Right Shift",
|
||||||
|
AltLeft: "Left Alt",
|
||||||
|
AltRight: "Right Alt",
|
||||||
|
MetaLeft: "Left Meta",
|
||||||
|
MetaRight: "Right Meta",
|
||||||
|
} as Record<string, string>;
|
||||||
|
|
||||||
|
export const keyDisplayMap: Record<string, string> = {
|
||||||
|
CtrlAltDelete: "Ctrl + Alt + Delete",
|
||||||
|
AltMetaEscape: "Alt + Meta + Escape",
|
||||||
|
Escape: "esc",
|
||||||
|
Tab: "tab",
|
||||||
|
Backspace: "backspace",
|
||||||
|
Enter: "enter",
|
||||||
|
CapsLock: "caps lock",
|
||||||
|
ShiftLeft: "shift",
|
||||||
|
ShiftRight: "shift",
|
||||||
|
ControlLeft: "ctrl",
|
||||||
|
AltLeft: "alt",
|
||||||
|
AltRight: "alt",
|
||||||
|
MetaLeft: "meta",
|
||||||
|
MetaRight: "meta",
|
||||||
|
Space: " ",
|
||||||
|
Home: "home",
|
||||||
|
PageUp: "pageup",
|
||||||
|
Delete: "delete",
|
||||||
|
End: "end",
|
||||||
|
PageDown: "pagedown",
|
||||||
|
ArrowLeft: "←",
|
||||||
|
ArrowRight: "→",
|
||||||
|
ArrowUp: "↑",
|
||||||
|
ArrowDown: "↓",
|
||||||
|
|
||||||
|
// Letters
|
||||||
|
KeyA: "a", KeyB: "b", KeyC: "c", KeyD: "d", KeyE: "e",
|
||||||
|
KeyF: "f", KeyG: "g", KeyH: "h", KeyI: "i", KeyJ: "j",
|
||||||
|
KeyK: "k", KeyL: "l", KeyM: "m", KeyN: "n", KeyO: "o",
|
||||||
|
KeyP: "p", KeyQ: "q", KeyR: "r", KeyS: "s", KeyT: "t",
|
||||||
|
KeyU: "u", KeyV: "v", KeyW: "w", KeyX: "x", KeyY: "y",
|
||||||
|
KeyZ: "z",
|
||||||
|
|
||||||
|
// Numbers
|
||||||
|
Digit1: "1", Digit2: "2", Digit3: "3", Digit4: "4", Digit5: "5",
|
||||||
|
Digit6: "6", Digit7: "7", Digit8: "8", Digit9: "9", Digit0: "0",
|
||||||
|
|
||||||
|
// Symbols
|
||||||
|
Minus: "-",
|
||||||
|
Equal: "=",
|
||||||
|
BracketLeft: "[",
|
||||||
|
BracketRight: "]",
|
||||||
|
Backslash: "\\",
|
||||||
|
Semicolon: ";",
|
||||||
|
Quote: "'",
|
||||||
|
Comma: ",",
|
||||||
|
Period: ".",
|
||||||
|
Slash: "/",
|
||||||
|
Backquote: "`",
|
||||||
|
IntlBackslash: "\\",
|
||||||
|
|
||||||
|
// Function keys
|
||||||
|
F1: "F1", F2: "F2", F3: "F3", F4: "F4",
|
||||||
|
F5: "F5", F6: "F6", F7: "F7", F8: "F8",
|
||||||
|
F9: "F9", F10: "F10", F11: "F11", F12: "F12",
|
||||||
|
|
||||||
|
// Numpad
|
||||||
|
Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2",
|
||||||
|
Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5",
|
||||||
|
Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8",
|
||||||
|
Numpad9: "Num 9", NumpadAdd: "Num +", NumpadSubtract: "Num -",
|
||||||
|
NumpadMultiply: "Num *", NumpadDivide: "Num /", NumpadDecimal: "Num .",
|
||||||
|
NumpadEnter: "Num Enter",
|
||||||
|
|
||||||
|
// Mappings for Keyboard Layout Mapping
|
||||||
|
"q": "q",
|
||||||
|
"w": "w",
|
||||||
|
"e": "e",
|
||||||
|
"r": "r",
|
||||||
|
"t": "t",
|
||||||
|
"y": "y",
|
||||||
|
"u": "u",
|
||||||
|
"i": "i",
|
||||||
|
"o": "o",
|
||||||
|
"p": "p",
|
||||||
|
"a": "a",
|
||||||
|
"s": "s",
|
||||||
|
"d": "d",
|
||||||
|
"f": "f",
|
||||||
|
"g": "g",
|
||||||
|
"h": "h",
|
||||||
|
"j": "j",
|
||||||
|
"k": "k",
|
||||||
|
"l": "l",
|
||||||
|
"z": "z",
|
||||||
|
"x": "x",
|
||||||
|
"c": "c",
|
||||||
|
"v": "v",
|
||||||
|
"b": "b",
|
||||||
|
"n": "n",
|
||||||
|
"m": "m",
|
||||||
|
|
||||||
|
"Q": "Q",
|
||||||
|
"W": "W",
|
||||||
|
"E": "E",
|
||||||
|
"R": "R",
|
||||||
|
"T": "T",
|
||||||
|
"Y": "Y",
|
||||||
|
"U": "U",
|
||||||
|
"I": "I",
|
||||||
|
"O": "O",
|
||||||
|
"P": "P",
|
||||||
|
"A": "A",
|
||||||
|
"S": "S",
|
||||||
|
"D": "D",
|
||||||
|
"F": "F",
|
||||||
|
"G": "G",
|
||||||
|
"H": "H",
|
||||||
|
"J": "J",
|
||||||
|
"K": "K",
|
||||||
|
"L": "L",
|
||||||
|
"Z": "Z",
|
||||||
|
"X": "X",
|
||||||
|
"C": "C",
|
||||||
|
"V": "V",
|
||||||
|
"B": "B",
|
||||||
|
"N": "N",
|
||||||
|
"M": "M",
|
||||||
|
|
||||||
|
"1": "1",
|
||||||
|
"2": "2",
|
||||||
|
"3": "3",
|
||||||
|
"4": "4",
|
||||||
|
"5": "5",
|
||||||
|
"6": "6",
|
||||||
|
"7": "7",
|
||||||
|
"8": "8",
|
||||||
|
"9": "9",
|
||||||
|
"0": "0",
|
||||||
|
|
||||||
|
"!": "!",
|
||||||
|
"@": "@",
|
||||||
|
"#": "#",
|
||||||
|
"$": "$",
|
||||||
|
"%": "%",
|
||||||
|
"^": "^",
|
||||||
|
"&": "&",
|
||||||
|
"*": "*",
|
||||||
|
"(": "(",
|
||||||
|
")": ")",
|
||||||
|
|
||||||
|
"-": "-",
|
||||||
|
"_": "_",
|
||||||
|
|
||||||
|
"[": "[",
|
||||||
|
"]": "]",
|
||||||
|
"{": "{",
|
||||||
|
"}": "}",
|
||||||
|
|
||||||
|
"|": "|",
|
||||||
|
|
||||||
|
";": ";",
|
||||||
|
":": ":",
|
||||||
|
|
||||||
|
"'": "'",
|
||||||
|
"\"": "\"",
|
||||||
|
|
||||||
|
",": ",",
|
||||||
|
"<": "<",
|
||||||
|
|
||||||
|
".": ".",
|
||||||
|
">": ">",
|
||||||
|
|
||||||
|
"/": "/",
|
||||||
|
"?": "?",
|
||||||
|
|
||||||
|
"`": "`",
|
||||||
|
"~": "~",
|
||||||
|
|
||||||
|
"\\": "\\"
|
||||||
|
};
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { charsUS, keysUS, modifiersUS } from "./us";
|
||||||
|
|
||||||
|
export const keysDE_T1 = {
|
||||||
|
...keysUS,
|
||||||
|
} as Record<string, number>;
|
||||||
|
|
||||||
|
export const charsDE_T1 = {
|
||||||
|
...charsUS,
|
||||||
|
|
||||||
|
"y": { key: "KeyZ", shift: false },
|
||||||
|
"Y": { key: "KeyZ", shift: true },
|
||||||
|
"z": { key: "KeyY", shift: false },
|
||||||
|
"Z": { key: "KeyY", shift: true },
|
||||||
|
|
||||||
|
"ä": { key: "Quote", shift: false },
|
||||||
|
"Ä": { key: "Quote", shift: true },
|
||||||
|
"ö": { key: "Semicolon", shift: false },
|
||||||
|
"Ö": { key: "Semicolon", shift: true },
|
||||||
|
"ü": { key: "BracketLeft", shift: false },
|
||||||
|
"Ü": { key: "BracketLeft", shift: true },
|
||||||
|
"ß": { key: "Minus", shift: false },
|
||||||
|
"?": { key: "Minus", shift: true },
|
||||||
|
|
||||||
|
"§": { key: "Digit3", shift: true },
|
||||||
|
"°": { key: "Backquote", shift: true },
|
||||||
|
|
||||||
|
"@": { key: "KeyQ", shift: false, altRight: true },
|
||||||
|
"\"": { key: "Digit2", shift: true },
|
||||||
|
|
||||||
|
"#": { key: "Backslash", shift: false },
|
||||||
|
"'": { key: "Backslash", shift: true },
|
||||||
|
|
||||||
|
".": { key: "Period", shift: false },
|
||||||
|
":": { key: "Period", shift: true },
|
||||||
|
",": { key: "Comma", shift: false },
|
||||||
|
";": { key: "Comma", shift: true },
|
||||||
|
|
||||||
|
"-": { key: "Slash", shift: false },
|
||||||
|
"_": { key: "Slash", shift: true },
|
||||||
|
|
||||||
|
"*": { key: "BracketRight", shift: true },
|
||||||
|
"+": { key: "BracketRight", shift: false },
|
||||||
|
"=": { key: "Digit0", shift: true },
|
||||||
|
"~": { key: "BracketRight", shift: false, altRight: true },
|
||||||
|
"{": { key: "Digit7", shift: false, altRight: true },
|
||||||
|
"}": { key: "Digit0", shift: false, altRight: true },
|
||||||
|
"[": { key: "Digit8", shift: false, altRight: true },
|
||||||
|
"]": { key: "Digit9", shift: false, altRight: true },
|
||||||
|
|
||||||
|
"\\": { key: "Minus", shift: false, altRight: true },
|
||||||
|
"|": { key: "IntlBackslash", shift: true, altRight: true },
|
||||||
|
|
||||||
|
"<": { key: "IntlBackslash", shift: false },
|
||||||
|
">": { key: "IntlBackslash", shift: true },
|
||||||
|
|
||||||
|
"^": {key: "Backquote", shift: false},
|
||||||
|
|
||||||
|
"€": { key: "KeyE", shift: false, altRight: true },
|
||||||
|
|
||||||
|
"²": {key: "Digit2", shift: false, altRight: true },
|
||||||
|
"³": {key: "Digit3", shift: false, altRight: true },
|
||||||
|
|
||||||
|
"μ": {key: "KeyM", shift: false, altRight: true },
|
||||||
|
|
||||||
|
} as Record<string, { key: string; shift: boolean; altLeft?: boolean; altRight?: boolean }>;
|
||||||
|
|
||||||
|
export const modifiersDE_T1 = {
|
||||||
|
...modifiersUS,
|
||||||
|
} as Record<string, number>;
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { charsUS, keysUS, modifiersUS } from "./us";
|
||||||
|
|
||||||
|
export const keysUK = {
|
||||||
|
...keysUS,
|
||||||
|
} as Record<string, number>;
|
||||||
|
|
||||||
|
export const charsUK = {
|
||||||
|
...charsUS,
|
||||||
|
"`": { key: "Backquote", shift: false },
|
||||||
|
"~": { key: "Backslash", shift: true },
|
||||||
|
"\\": { key: "IntlBacklash", shift: false },
|
||||||
|
"|": { key: "IntlBacklash", shift: true },
|
||||||
|
"#": { key: "Backslash", shift: false },
|
||||||
|
"£": { key: "Digit3", shift: true },
|
||||||
|
"@": { key: "Quote", shift: true },
|
||||||
|
"\"": { key: "Digit2", shift: true },
|
||||||
|
"¬": { key: "Backquote", shift: true },
|
||||||
|
"¦": { key: "Backquote", shift: false, altRight: true },
|
||||||
|
"€": { key: "Digit4", shift: false, altRight: true },
|
||||||
|
} as Record<string, { key: string; shift: boolean; altLeft?: boolean; altRight?: boolean; }>;
|
||||||
|
|
||||||
|
export const modifiersUK = {
|
||||||
|
...modifiersUS,
|
||||||
|
} as Record<string, number>;
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { charsUS, keysUS, modifiersUS } from "./us";
|
||||||
|
|
||||||
|
// Extend US Keys with UK Apple-specific changes
|
||||||
|
export const keysUKApple = {
|
||||||
|
...keysUS,
|
||||||
|
} as Record<string, number>;
|
||||||
|
|
||||||
|
// Extend US Chars with UK Apple-specific changes
|
||||||
|
export const charsUKApple = {
|
||||||
|
...charsUS,
|
||||||
|
"`": { key: "Backquote", shift: false },
|
||||||
|
"~": { key: "Backquote", shift: true },
|
||||||
|
"\\" : { key: "Backslash", shift: false },
|
||||||
|
"|": { key: "Backslash", shift: true },
|
||||||
|
"#": { key: "Digit3", shift: false, altLeft: true },
|
||||||
|
"£": { key: "Digit3", shift: true },
|
||||||
|
"@": { key: "Digit2", shift: true },
|
||||||
|
"\"": { key: "Quote", shift: true },
|
||||||
|
"¬": { key: "KeyL", shift: false, altLeft: true},
|
||||||
|
} as Record<string, { key: string; shift: boolean; altLeft?: boolean; altRight?: boolean; }>;
|
||||||
|
|
||||||
|
// Modifiers are typically the same between UK and US layouts
|
||||||
|
export const modifiersUKApple = {
|
||||||
|
...modifiersUS,
|
||||||
|
} as Record<string, number>;
|
|
@ -0,0 +1,215 @@
|
||||||
|
export const keysUS = {
|
||||||
|
AltLeft: 0xe2,
|
||||||
|
AltRight: 0xe6,
|
||||||
|
ArrowDown: 0x51,
|
||||||
|
ArrowLeft: 0x50,
|
||||||
|
ArrowRight: 0x4f,
|
||||||
|
ArrowUp: 0x52,
|
||||||
|
Backquote: 0x35,
|
||||||
|
Backslash: 0x31,
|
||||||
|
Backspace: 0x2a,
|
||||||
|
BracketLeft: 0x2f,
|
||||||
|
BracketRight: 0x30,
|
||||||
|
CapsLock: 0x39,
|
||||||
|
Comma: 0x36,
|
||||||
|
ContextMenu: 0,
|
||||||
|
Delete: 0x4c,
|
||||||
|
Digit0: 0x27,
|
||||||
|
Digit1: 0x1e,
|
||||||
|
Digit2: 0x1f,
|
||||||
|
Digit3: 0x20,
|
||||||
|
Digit4: 0x21,
|
||||||
|
Digit5: 0x22,
|
||||||
|
Digit6: 0x23,
|
||||||
|
Digit7: 0x24,
|
||||||
|
Digit8: 0x25,
|
||||||
|
Digit9: 0x26,
|
||||||
|
End: 0x4d,
|
||||||
|
Enter: 0x28,
|
||||||
|
Equal: 0x2e,
|
||||||
|
Escape: 0x29,
|
||||||
|
F1: 0x3a,
|
||||||
|
F2: 0x3b,
|
||||||
|
F3: 0x3c,
|
||||||
|
F4: 0x3d,
|
||||||
|
F5: 0x3e,
|
||||||
|
F6: 0x3f,
|
||||||
|
F7: 0x40,
|
||||||
|
F8: 0x41,
|
||||||
|
F9: 0x42,
|
||||||
|
F10: 0x43,
|
||||||
|
F11: 0x44,
|
||||||
|
F12: 0x45,
|
||||||
|
F13: 0x68,
|
||||||
|
Home: 0x4a,
|
||||||
|
Insert: 0x49,
|
||||||
|
IntlBackslash: 0x31,
|
||||||
|
KeyA: 0x04,
|
||||||
|
KeyB: 0x05,
|
||||||
|
KeyC: 0x06,
|
||||||
|
KeyD: 0x07,
|
||||||
|
KeyE: 0x08,
|
||||||
|
KeyF: 0x09,
|
||||||
|
KeyG: 0x0a,
|
||||||
|
KeyH: 0x0b,
|
||||||
|
KeyI: 0x0c,
|
||||||
|
KeyJ: 0x0d,
|
||||||
|
KeyK: 0x0e,
|
||||||
|
KeyL: 0x0f,
|
||||||
|
KeyM: 0x10,
|
||||||
|
KeyN: 0x11,
|
||||||
|
KeyO: 0x12,
|
||||||
|
KeyP: 0x13,
|
||||||
|
KeyQ: 0x14,
|
||||||
|
KeyR: 0x15,
|
||||||
|
KeyS: 0x16,
|
||||||
|
KeyT: 0x17,
|
||||||
|
KeyU: 0x18,
|
||||||
|
KeyV: 0x19,
|
||||||
|
KeyW: 0x1a,
|
||||||
|
KeyX: 0x1b,
|
||||||
|
KeyY: 0x1c,
|
||||||
|
KeyZ: 0x1d,
|
||||||
|
KeypadExclamation: 0xcf,
|
||||||
|
Minus: 0x2d,
|
||||||
|
NumLock: 0x53,
|
||||||
|
Numpad0: 0x62,
|
||||||
|
Numpad1: 0x59,
|
||||||
|
Numpad2: 0x5a,
|
||||||
|
Numpad3: 0x5b,
|
||||||
|
Numpad4: 0x5c,
|
||||||
|
Numpad5: 0x5d,
|
||||||
|
Numpad6: 0x5e,
|
||||||
|
Numpad7: 0x5f,
|
||||||
|
Numpad8: 0x60,
|
||||||
|
Numpad9: 0x61,
|
||||||
|
NumpadAdd: 0x57,
|
||||||
|
NumpadDivide: 0x54,
|
||||||
|
NumpadEnter: 0x58,
|
||||||
|
NumpadMultiply: 0x55,
|
||||||
|
NumpadSubtract: 0x56,
|
||||||
|
NumpadDecimal: 0x63,
|
||||||
|
PageDown: 0x4e,
|
||||||
|
PageUp: 0x4b,
|
||||||
|
Period: 0x37,
|
||||||
|
Quote: 0x34,
|
||||||
|
Semicolon: 0x33,
|
||||||
|
Slash: 0x38,
|
||||||
|
Space: 0x2c,
|
||||||
|
Tab: 0x2b,
|
||||||
|
} as Record<string, number>;
|
||||||
|
|
||||||
|
export const charsUS = {
|
||||||
|
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; shift: boolean; altLeft?: boolean; altRight?: boolean; }>;
|
||||||
|
|
||||||
|
export const modifiersUS = {
|
||||||
|
ControlLeft: 0x01,
|
||||||
|
ControlRight: 0x10,
|
||||||
|
ShiftLeft: 0x02,
|
||||||
|
ShiftRight: 0x20,
|
||||||
|
AltLeft: 0x04,
|
||||||
|
AltRight: 0x40,
|
||||||
|
MetaLeft: 0x08,
|
||||||
|
MetaRight: 0x80,
|
||||||
|
} as Record<string, number>;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Button } from "@/components/Button";
|
||||||
import EmptyCard from "@/components/EmptyCard";
|
import EmptyCard from "@/components/EmptyCard";
|
||||||
import Card from "@/components/Card";
|
import Card from "@/components/Card";
|
||||||
import { MAX_TOTAL_MACROS, COPY_SUFFIX, DEFAULT_DELAY } from "@/constants/macros";
|
import { MAX_TOTAL_MACROS, COPY_SUFFIX, DEFAULT_DELAY } from "@/constants/macros";
|
||||||
import { keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings";
|
import { keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings/KeyboardLayouts";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||||
|
@ -27,6 +27,7 @@ export default function SettingsMacrosRoute() {
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const [macroToDelete, setMacroToDelete] = useState<KeySequence | null>(null);
|
const [macroToDelete, setMacroToDelete] = useState<KeySequence | null>(null);
|
||||||
|
|
||||||
|
|
||||||
const isMaxMacrosReached = useMemo(() =>
|
const isMaxMacrosReached = useMemo(() =>
|
||||||
macros.length >= MAX_TOTAL_MACROS,
|
macros.length >= MAX_TOTAL_MACROS,
|
||||||
[macros.length]
|
[macros.length]
|
||||||
|
|
|
@ -5,7 +5,7 @@ import MouseIcon from "@/assets/mouse-icon.svg";
|
||||||
import PointingFinger from "@/assets/pointing-finger.svg";
|
import PointingFinger from "@/assets/pointing-finger.svg";
|
||||||
import { GridCard } from "@/components/Card";
|
import { GridCard } from "@/components/Card";
|
||||||
import { Checkbox } from "@/components/Checkbox";
|
import { Checkbox } from "@/components/Checkbox";
|
||||||
import { useDeviceSettingsStore, useSettingsStore } from "@/hooks/stores";
|
import { useDeviceSettingsStore, useSettingsStore, useKeyboardMappingsStore } 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";
|
||||||
|
@ -36,6 +36,39 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
|
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
|
|
||||||
|
const [keyboardLayout, setKeyboardLayout] = useState("en-US");
|
||||||
|
const [kbMappingEnabled, setKeyboardMapping] = useState(false);
|
||||||
|
|
||||||
|
const keyboardMappingEnabled = useSettingsStore(state => state.keyboardMappingEnabled);
|
||||||
|
const setkeyboardMappingEnabled = useSettingsStore(state => state.setkeyboardMappingEnabled);
|
||||||
|
|
||||||
|
const handleKeyboardLayoutChange = (keyboardLayout: string) => {
|
||||||
|
send("setKeyboardLayout", { kbLayout: keyboardLayout }, resp => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
notifications.error(
|
||||||
|
`Failed to set keyboard layout: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
useKeyboardMappingsStore.setLayout(keyboardLayout)
|
||||||
|
setKeyboardLayout(keyboardLayout);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyboardMappingChange = (enabled: boolean) => {
|
||||||
|
send("setKeyboardMappingState", { enabled }, resp => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
notifications.error(
|
||||||
|
`Failed to set keyboard maping state state: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setkeyboardMappingEnabled(enabled);
|
||||||
|
useKeyboardMappingsStore.setMappingsState(enabled);
|
||||||
|
setKeyboardMapping(enabled);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
send("getJigglerState", {}, resp => {
|
send("getJigglerState", {}, resp => {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
|
@ -48,7 +81,21 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
setScrollSensitivity(resp.result as ScrollSensitivity);
|
setScrollSensitivity(resp.result as ScrollSensitivity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isScrollSensitivityEnabled, send, setScrollSensitivity]);
|
|
||||||
|
send("getKeyboardLayout", {}, resp => {
|
||||||
|
if ("error" in resp) return;
|
||||||
|
setKeyboardLayout(String(resp.result));
|
||||||
|
useKeyboardMappingsStore.setLayout(String(resp.result))
|
||||||
|
});
|
||||||
|
|
||||||
|
send("getKeyboardMappingState", {}, resp => {
|
||||||
|
if ("error" in resp) return;
|
||||||
|
setKeyboardMapping(resp.result as boolean);
|
||||||
|
setkeyboardMappingEnabled(resp.result as boolean);
|
||||||
|
useKeyboardMappingsStore.setMappingsState(resp.result as boolean);
|
||||||
|
});
|
||||||
|
|
||||||
|
}, [isScrollSensitivityEnabled, send, setScrollSensitivity, setkeyboardMappingEnabled, keyboardMappingEnabled, keyboardLayout, setKeyboardLayout]);
|
||||||
|
|
||||||
const handleJigglerChange = (enabled: boolean) => {
|
const handleJigglerChange = (enabled: boolean) => {
|
||||||
send("setJigglerState", { enabled }, resp => {
|
send("setJigglerState", { enabled }, resp => {
|
||||||
|
@ -78,6 +125,7 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
[send, setScrollSensitivity],
|
[send, setScrollSensitivity],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
|
@ -183,6 +231,44 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<SettingsPageHeader
|
||||||
|
title="Keyboard"
|
||||||
|
description="Customize keyboard behaviour"
|
||||||
|
/>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<SettingsItem
|
||||||
|
title="Enable Keyboard Mapping"
|
||||||
|
description="Enables mapping of keys from your native layout to the layout of the target device"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={kbMappingEnabled}
|
||||||
|
onChange={e => {
|
||||||
|
handleKeyboardMappingChange(e.target.checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
<SettingsItem
|
||||||
|
title="Keyboard Layout"
|
||||||
|
description="Set keyboard layout (this should match the target machine)"
|
||||||
|
>
|
||||||
|
<SelectMenuBasic
|
||||||
|
size="SM_Wide"
|
||||||
|
label=""
|
||||||
|
// TODO figure out how to make this selector wider like the EDID one?, (done but not sure if in desired way.)
|
||||||
|
//fullWidth
|
||||||
|
value={keyboardLayout}
|
||||||
|
options={[
|
||||||
|
{ value: "en-US", label: "US" },
|
||||||
|
{ value: "en-GB", label: "UK" },
|
||||||
|
{ value: "en-GB_apple", label: "UK (Apple)" },
|
||||||
|
{ value: "de_DE", label: "German (T1)" },
|
||||||
|
]}
|
||||||
|
onChange={e => handleKeyboardLayoutChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ export default function SettingsRoute() {
|
||||||
>
|
>
|
||||||
<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">
|
<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" />
|
<LuKeyboard className="h-4 w-4 shrink-0" />
|
||||||
<h1>Mouse</h1>
|
<h1>Mouse & Keyboard</h1>
|
||||||
</div>
|
</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue