mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
Merge pull request #631 from Alexander-Ger-Reich/main
Adds a German keyboard and more
This commit is contained in:
@@ -16,6 +16,75 @@ type Char struct {
|
||||
|
||||
type PasteReq struct {
|
||||
Content string `form:"content" validate:"required"`
|
||||
Langue string `form:"langue"`
|
||||
}
|
||||
|
||||
func LangueSwitch(base map[rune]Char, lang string) map[rune]Char {
|
||||
// wenn kein lang angegeben → Standardmap zurück
|
||||
if lang == "" {
|
||||
return base
|
||||
}
|
||||
|
||||
// immer Kopie erstellen
|
||||
m := copyMap(base)
|
||||
|
||||
switch lang {
|
||||
case "de":
|
||||
// Y tauschen
|
||||
m['y'] = Char{0, 29}
|
||||
m['Y'] = Char{2, 29}
|
||||
|
||||
// Z tauschen
|
||||
m['z'] = Char{0, 28}
|
||||
m['Z'] = Char{2, 28}
|
||||
|
||||
// deutsche Sonderzeichen hinzufügen oder remappen
|
||||
m['\u00E4'] = Char{0, 52} // ä
|
||||
m['\u00C4'] = Char{2, 52} // Ä
|
||||
m['\u00F6'] = Char{0, 51} // ö
|
||||
m['\u00D6'] = Char{2, 51} // Ö
|
||||
m['\u00FC'] = Char{0, 47} // ü
|
||||
m['\u00DC'] = Char{2, 47} // Ü
|
||||
m['\u00DF'] = Char{0, 45} // ß
|
||||
|
||||
//Tauschen
|
||||
m['^'] = Char{0, 53} // muss doppelt sein
|
||||
m['/'] = Char{2, 36} // Shift + 7
|
||||
m['('] = Char{2, 37} // Shift + 8
|
||||
m['&'] = Char{2, 35} // Shift + 6
|
||||
m[')'] = Char{2, 38} // Shift + 9
|
||||
m['`'] = Char{2, 46} // Grave Accent / Backtick
|
||||
m['"'] = Char{2, 31} // Shift + 2
|
||||
m['?'] = Char{2, 45} // Shift + ß
|
||||
m['{'] = Char{0x40, 36} // ALt Gr + 7
|
||||
m['['] = Char{0x40, 37} // ALt Gr + 8
|
||||
m[']'] = Char{0x40, 38} // ALt Gr + 6
|
||||
m['}'] = Char{0x40, 39} // ALt Gr + 0
|
||||
m['\\'] = Char{0x40, 45} // ALt Gr + ß
|
||||
m['@'] = Char{0x40, 20} // ALt Gr + q
|
||||
m['+'] = Char{0, 48} // Shift + +
|
||||
m['*'] = Char{2, 48} // Shift + +
|
||||
m['~'] = Char{0x40, 48} // Shift + +
|
||||
m['#'] = Char{0, 49} // Shift + #
|
||||
m['\''] = Char{2, 49} // Shift + #
|
||||
m['<'] = Char{0, 100} // Shift + <
|
||||
m['>'] = Char{2, 100} // Shift + <
|
||||
m['|'] = Char{0x40, 100} // ALt Gr + <
|
||||
m[';'] = Char{2, 54} // Shift + ,
|
||||
m[':'] = Char{2, 55} // Shift + .
|
||||
m['-'] = Char{0, 56} // Shift + -
|
||||
m['_'] = Char{2, 56} // Shift + -
|
||||
|
||||
//neu
|
||||
m['\u00B4'] = Char{0, 46} // ´
|
||||
m['\u00B0'] = Char{2, 53} // °
|
||||
m['\u00A7'] = Char{2, 32} // §
|
||||
m['\u20AC'] = Char{0x40, 8} // €
|
||||
m['\u00B2'] = Char{0x40, 31} // ²
|
||||
m['\u00B3'] = Char{0x40, 32} // ³
|
||||
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *Service) Paste(c *gin.Context) {
|
||||
@@ -25,6 +94,7 @@ func (s *Service) Paste(c *gin.Context) {
|
||||
rsp.ErrRsp(c, -1, "invalid arguments")
|
||||
return
|
||||
}
|
||||
charMapLocal := LangueSwitch(charMap, req.Langue)
|
||||
if len(req.Content) > 1024 {
|
||||
rsp.ErrRsp(c, -2, "content too long")
|
||||
return
|
||||
@@ -34,7 +104,7 @@ func (s *Service) Paste(c *gin.Context) {
|
||||
keyUp := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
for _, char := range req.Content {
|
||||
key, ok := charMap[char]
|
||||
key, ok := charMapLocal[char]
|
||||
if !ok {
|
||||
log.Debugf("unknown key '%c' (rune: %d)", char, char)
|
||||
continue
|
||||
@@ -48,6 +118,15 @@ func (s *Service) Paste(c *gin.Context) {
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("hid paste success, total %d characters processed", len(req.Content))
|
||||
}
|
||||
|
||||
func copyMap(src map[rune]Char) map[rune]Char {
|
||||
dst := make(map[rune]Char, len(src))
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
var charMap = map[rune]Char{
|
||||
// Lowercase letters
|
||||
'a': {0, 4}, 'b': {0, 5}, 'c': {0, 6}, 'd': {0, 7}, 'e': {0, 8},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { http } from '@/lib/http.ts';
|
||||
|
||||
// paste
|
||||
export function paste(content: string) {
|
||||
return http.post('/api/hid/paste', { content });
|
||||
export function paste(content: string, langue: string) {
|
||||
return http.post('/api/hid/paste', { content, langue });
|
||||
}
|
||||
|
||||
// reset hid
|
||||
|
||||
@@ -68,11 +68,13 @@ const de = {
|
||||
keyboard: {
|
||||
title: 'Tastatur',
|
||||
paste: 'Einfügen',
|
||||
tips: 'Nur Standard-Tastaturbuchstaben und Symbole werden unterstützt',
|
||||
tips: 'Server Tastaturbelegung',
|
||||
placeholder: 'Bitte eingeben',
|
||||
submit: 'Senden',
|
||||
virtual: 'Tastatur',
|
||||
ctrlaltdel: 'Ctrl+Alt+Del'
|
||||
ctrlaltdel: 'Ctrl+Alt+Del',
|
||||
dropdownEnglish: 'Englisch',
|
||||
dropdownGerman: 'Deutsch'
|
||||
},
|
||||
mouse: {
|
||||
title: 'Maus',
|
||||
|
||||
@@ -69,11 +69,13 @@ const en = {
|
||||
keyboard: {
|
||||
title: 'Keyboard',
|
||||
paste: 'Paste',
|
||||
tips: 'Only standard keyboard letters and symbols are supported',
|
||||
tips: 'Server keyboard layout',
|
||||
placeholder: 'Please input',
|
||||
submit: 'Submit',
|
||||
virtual: 'Keyboard',
|
||||
ctrlaltdel: 'Ctrl+Alt+Del'
|
||||
ctrlaltdel: 'Ctrl+Alt+Del',
|
||||
dropdownEnglish: 'English',
|
||||
dropdownGerman: 'German'
|
||||
},
|
||||
mouse: {
|
||||
title: 'Mouse',
|
||||
|
||||
@@ -85,6 +85,7 @@ export const KeyboardCodes: Map<string, number> = new Map([
|
||||
['Backslash', 49],
|
||||
['IntlBackslash', 49],
|
||||
['Backquote_azerty', 100],
|
||||
['IntlBackslash_qwertz', 100],
|
||||
|
||||
['Semicolon', 51],
|
||||
['Quote', 52],
|
||||
|
||||
@@ -46,6 +46,7 @@ export const VirtualKeyboard = () => {
|
||||
const languages = [
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'fr', label: 'French' },
|
||||
{ value: 'de', label: 'Deutsch' },
|
||||
{ value: 'ru', label: 'Russian' }
|
||||
];
|
||||
|
||||
@@ -65,6 +66,7 @@ export const VirtualKeyboard = () => {
|
||||
const layoutMap = new Map([
|
||||
['en', 'default'],
|
||||
['ru', 'rus'],
|
||||
['de', 'qwertz'],
|
||||
['fr', 'azerty']
|
||||
]);
|
||||
|
||||
@@ -126,6 +128,17 @@ export const VirtualKeyboard = () => {
|
||||
// all other labels use their own code
|
||||
return KeyboardCodes.get(base);
|
||||
}
|
||||
if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) {
|
||||
const base = key.replace('_qwertz', '');
|
||||
// Tausch
|
||||
if (base === 'KeyZ') return KeyboardCodes.get('KeyY');
|
||||
if (base === 'KeyY') return KeyboardCodes.get('KeyZ');
|
||||
|
||||
if (base === 'IntlBackslash') return KeyboardCodes.get('IntlBackslash_qwertz');
|
||||
|
||||
// all other labels use their own code
|
||||
return KeyboardCodes.get(base);
|
||||
}
|
||||
|
||||
const specialKey = specialKeyMap.get(key);
|
||||
if (specialKey) {
|
||||
|
||||
@@ -29,6 +29,14 @@ export const keyboardOptions = {
|
||||
'{shiftleft} RusZ RusX RusC RusV RusB RusN RusM RusComma RusPeriod RusSlash {shiftright}',
|
||||
'{controlleft} {winleft} {altleft} {space} {altright} {winright} {menu} {controlright}'
|
||||
],
|
||||
qwertz: [
|
||||
'{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12',
|
||||
'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Minus_qwertz Equal_qwertz {backspace}',
|
||||
'{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP BracketLeft_qwertz BracketRight_qwertz',
|
||||
'{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon_qwertz Quote_qwertz Backslash_qwertz {enter}',
|
||||
'{shiftleft} IntlBackslash_qwertz KeyY_qwertz KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Period_qwertz Slash_qwertz {shiftright}',
|
||||
'{controlleft_qwertz} {winleft} {altleft} {space} {altright_qwertz} {winright} {menu} {controlright_qwertz}'
|
||||
],
|
||||
azerty: [
|
||||
'{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12',
|
||||
'Backquote_azerty Digit1_azerty Digit2_azerty Digit3_azerty Digit4_azerty Digit5_azerty Digit6_azerty Digit7_azerty Digit8_azerty Digit9_azerty Digit0_azerty Minus_azerty Equal_azerty {backspace}',
|
||||
@@ -55,6 +63,39 @@ export const keyboardOptions = {
|
||||
Equal: '+<br/>=',
|
||||
'{backspace}': 'Backspace',
|
||||
|
||||
Backquote_qwertz: '^<br/>°',
|
||||
Digit1_qwertz: '1<br/>!',
|
||||
Digit2_qwertz: '2<br/>"',
|
||||
Digit3_qwertz: '3<br/>§',
|
||||
Digit4_qwertz: '4<br/>$',
|
||||
Digit5_qwertz: '5<br/>%',
|
||||
Digit6_qwertz: '6<br/>&',
|
||||
Digit7_qwertz: '7{/',
|
||||
Digit8_qwertz: '8[(',
|
||||
Digit9_qwertz: '9])',
|
||||
Digit0_qwertz: '0}=',
|
||||
Minus_qwertz: 'ß\\?',
|
||||
Equal_qwertz: '´<br/>`',
|
||||
|
||||
KeyY_qwertz: 'Y',
|
||||
KeyZ_qwertz: 'Z',
|
||||
|
||||
KeyQ_qwertz: 'Q<br/>@',
|
||||
KeyE_qwertz: 'E<br/>€',
|
||||
BracketRight_qwertz: '+~*',
|
||||
Backslash_qwertz: '#<br/>\'',
|
||||
IntlBackslash_qwertz: '<|>',
|
||||
Comma_qwertz: ',<br/>;',
|
||||
Period_qwertz: '.<br/>:',
|
||||
Slash_qwertz: '-<br/>_',
|
||||
'{controlleft_qwertz}': 'STRG',
|
||||
'{altright_qwertz}': 'ALT GR',
|
||||
'{controlright_qwertz}': 'STRG',
|
||||
|
||||
BracketLeft_qwertz: 'Ü',
|
||||
Semicolon_qwertz: 'Ö',
|
||||
Quote_qwertz: 'Ä',
|
||||
|
||||
'{tab}': 'Tab',
|
||||
KeyQ: 'Q',
|
||||
KeyW: 'W',
|
||||
@@ -275,7 +316,12 @@ export const specialKeyMap = new Map([
|
||||
['{arrowright}', 'ArrowRight'],
|
||||
['{arrowleft}', 'ArrowLeft'],
|
||||
['{arrowdown}', 'ArrowDown'],
|
||||
['{arrowup}', 'ArrowUp']
|
||||
['{arrowup}', 'ArrowUp'],
|
||||
|
||||
//DE
|
||||
['{controlleft_qwertz}', 'ControlLeft'],
|
||||
['{controlright_qwertz}', 'ControlRight'],
|
||||
['{altright_qwertz}', 'AltRight']
|
||||
]);
|
||||
|
||||
// modifier keys
|
||||
@@ -289,7 +335,12 @@ export const modifierKeys = [
|
||||
'{controlright}',
|
||||
'{altright}',
|
||||
'{metaright}',
|
||||
'{winright}'
|
||||
'{winright}',
|
||||
|
||||
// DE-spezifische Modifier-Tokens (für qwertz-Layout)
|
||||
'{controlleft_qwertz}',
|
||||
'{controlright_qwertz}',
|
||||
'{altright_qwertz}'
|
||||
];
|
||||
|
||||
// double line display buttons
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import { Button, Input, Modal, type InputRef } from 'antd';
|
||||
import { Button, Input, Modal, Select, type InputRef } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { ClipboardIcon } from 'lucide-react';
|
||||
@@ -8,6 +8,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { paste } from '@/api/hid';
|
||||
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export const Paste = () => {
|
||||
@@ -19,6 +21,7 @@ export const Paste = () => {
|
||||
const [status, setStatus] = useState<'' | 'error'>('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errMsg, setErrMsg] = useState('');
|
||||
const [langue, setLangue] = useState('en');
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
|
||||
@@ -32,7 +35,7 @@ export const Paste = () => {
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
|
||||
paste(inputValue)
|
||||
paste(inputValue, langue)
|
||||
.then((rsp) => {
|
||||
if (rsp.code !== 0) {
|
||||
setErrMsg(rsp.msg);
|
||||
@@ -88,6 +91,14 @@ export const Paste = () => {
|
||||
>
|
||||
<div className="pb-3 text-xs text-neutral-500">{t('keyboard.tips')}</div>
|
||||
|
||||
<Select
|
||||
value={langue}
|
||||
onChange={(value) => setLangue(value)}
|
||||
style={{ width: '100%', marginBottom: '12px' }} >
|
||||
<Option value="en">{t('keyboard.dropdownEnglish')}</Option>
|
||||
<Option value="de">{t('keyboard.dropdownGerman')}</Option>
|
||||
</Select>
|
||||
|
||||
<TextArea
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
|
||||
Reference in New Issue
Block a user