Skip to content

Commit a287d8e

Browse files
DustyDiamondjotaen
andauthored
Support German (de-DE) keyboard layouts when pasting (#1842)
This PR adds a detection mechanism for the German `de-DE` locale in the paste feature. In this case, it uses the correct mapping, and ensures that all characters come through correctly. ## Original PR description Hi, I added the translation table for German Special Characters. The Manual definition of `ALT_GR` is necessary since on german layouts the `AltGr` Key doesnt behave like the `MODIFIER_RIGHT_ALT`. In Tinypilot, pressing `AltGr` is interpreted like `Ctrl Left + Alt Right` which translates to hex `0x41`, but it should be `Ctrl Left + Alt Left` (Hex `0x05`) to work for German Special Characters. Closes #1830 For an Quick overview for anyone later reading this, this would support the following special Characters: ``` !"§$%&(){}[]/+*#'-_.:,;äöüÄÖÜ<>|€?ß\~`´ ``` Greetings from Germany :) <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1842"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a> --------- Co-authored-by: Jan Heuermann <[email protected]>
1 parent b3be4fa commit a287d8e

File tree

4 files changed

+120
-3
lines changed

4 files changed

+120
-3
lines changed

app/hid/keycodes.py

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
MODIFIER_RIGHT_SHIFT = 1 << 5
1515
MODIFIER_RIGHT_ALT = 1 << 6
1616
MODIFIER_RIGHT_META = 1 << 7
17+
# The "Alt Gr" key is a special modifier key that is present on e.g. German
18+
# keyboards. There are multiple ways to emulate this key, e.g. by (a) just
19+
# using the "Right Alt" modifier, or (b) by using "Left Alt"+"Left Ctrl". In
20+
# our tests, we found that (a) seems to be the most compatible option, as it
21+
# appears to work on both Windows and Linux systems, whereas (b) appears to
22+
# only work reliably on Windows systems.
23+
MODIFIER_ALT_GR = MODIFIER_RIGHT_ALT
1724

1825
KEYCODE_NONE = 0
1926
KEYCODE_A = 0x04

app/templates/custom-elements/paste-dialog.html

+3-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ <h3>Paste Text</h3>
6565
spellcheck="false"
6666
></textarea>
6767
<div class="hint">
68-
The target system must have an <span class="monospace">en-US</span> or
69-
<span class="monospace">en-GB</span> keyboard layout.
68+
The target system must have an <span class="monospace">en-US</span>,
69+
<span class="monospace">en-GB</span> or
70+
<span class="monospace">de-DE</span> keyboard layout.
7071
</div>
7172
<button id="confirm-btn" class="btn-success">Paste</button>
7273
<button id="cancel-btn">Close</button>

app/text_to_hid.py

+108-1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,112 @@ class UnsupportedCharacterError(Error):
262262
modifier=hid.MODIFIER_LEFT_SHIFT),
263263
}
264264

265+
_DE_CHAR_TO_HID_MAP = _COMMON_CHAR_TO_HID_MAP | {
266+
'y':
267+
hid.Keystroke(keycode=hid.KEYCODE_Z),
268+
'z':
269+
hid.Keystroke(keycode=hid.KEYCODE_Y),
270+
'Y':
271+
hid.Keystroke(keycode=hid.KEYCODE_Z, modifier=hid.MODIFIER_LEFT_SHIFT),
272+
'Z':
273+
hid.Keystroke(keycode=hid.KEYCODE_Y, modifier=hid.MODIFIER_LEFT_SHIFT),
274+
'"':
275+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_2,
276+
modifier=hid.MODIFIER_LEFT_SHIFT),
277+
'§':
278+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_3,
279+
modifier=hid.MODIFIER_LEFT_SHIFT),
280+
'&':
281+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_6,
282+
modifier=hid.MODIFIER_LEFT_SHIFT),
283+
'/':
284+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_7,
285+
modifier=hid.MODIFIER_LEFT_SHIFT),
286+
'(':
287+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_8,
288+
modifier=hid.MODIFIER_LEFT_SHIFT),
289+
')':
290+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_9,
291+
modifier=hid.MODIFIER_LEFT_SHIFT),
292+
'=':
293+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_0,
294+
modifier=hid.MODIFIER_LEFT_SHIFT),
295+
'<':
296+
hid.Keystroke(keycode=hid.KEYCODE_102ND),
297+
'>':
298+
hid.Keystroke(keycode=hid.KEYCODE_102ND,
299+
modifier=hid.MODIFIER_LEFT_SHIFT),
300+
'#':
301+
hid.Keystroke(keycode=hid.KEYCODE_HASH),
302+
';':
303+
hid.Keystroke(keycode=hid.KEYCODE_COMMA,
304+
modifier=hid.MODIFIER_LEFT_SHIFT),
305+
':':
306+
hid.Keystroke(keycode=hid.KEYCODE_PERIOD,
307+
modifier=hid.MODIFIER_LEFT_SHIFT),
308+
'-':
309+
hid.Keystroke(keycode=hid.KEYCODE_FORWARD_SLASH),
310+
'_':
311+
hid.Keystroke(keycode=hid.KEYCODE_FORWARD_SLASH,
312+
modifier=hid.MODIFIER_LEFT_SHIFT),
313+
'ä':
314+
hid.Keystroke(keycode=hid.KEYCODE_SINGLE_QUOTE),
315+
'Ä':
316+
hid.Keystroke(keycode=hid.KEYCODE_SINGLE_QUOTE,
317+
modifier=hid.MODIFIER_LEFT_SHIFT),
318+
'ö':
319+
hid.Keystroke(keycode=hid.KEYCODE_SEMICOLON),
320+
'Ö':
321+
hid.Keystroke(keycode=hid.KEYCODE_SEMICOLON,
322+
modifier=hid.MODIFIER_LEFT_SHIFT),
323+
'ü':
324+
hid.Keystroke(keycode=hid.KEYCODE_LEFT_BRACKET),
325+
'Ü':
326+
hid.Keystroke(keycode=hid.KEYCODE_LEFT_BRACKET,
327+
modifier=hid.MODIFIER_LEFT_SHIFT),
328+
'+':
329+
hid.Keystroke(keycode=hid.KEYCODE_RIGHT_BRACKET),
330+
'*':
331+
hid.Keystroke(keycode=hid.KEYCODE_RIGHT_BRACKET,
332+
modifier=hid.MODIFIER_LEFT_SHIFT),
333+
'~':
334+
hid.Keystroke(keycode=hid.KEYCODE_RIGHT_BRACKET,
335+
modifier=hid.MODIFIER_ALT_GR),
336+
'|':
337+
hid.Keystroke(keycode=hid.KEYCODE_102ND, modifier=hid.MODIFIER_ALT_GR),
338+
'@':
339+
hid.Keystroke(keycode=hid.KEYCODE_Q, modifier=hid.MODIFIER_ALT_GR),
340+
'€':
341+
hid.Keystroke(keycode=hid.KEYCODE_E, modifier=hid.MODIFIER_ALT_GR),
342+
'{':
343+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_7,
344+
modifier=hid.MODIFIER_ALT_GR),
345+
'[':
346+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_8,
347+
modifier=hid.MODIFIER_ALT_GR),
348+
']':
349+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_9,
350+
modifier=hid.MODIFIER_ALT_GR),
351+
'}':
352+
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_0,
353+
modifier=hid.MODIFIER_ALT_GR),
354+
'\'':
355+
hid.Keystroke(keycode=hid.KEYCODE_BACKSLASH,
356+
modifier=hid.MODIFIER_LEFT_SHIFT),
357+
'?':
358+
hid.Keystroke(keycode=hid.KEYCODE_MINUS,
359+
modifier=hid.MODIFIER_LEFT_SHIFT),
360+
'ß':
361+
hid.Keystroke(keycode=hid.KEYCODE_MINUS),
362+
'\\':
363+
hid.Keystroke(keycode=hid.KEYCODE_MINUS, modifier=hid.MODIFIER_ALT_GR),
364+
'´':
365+
hid.Keystroke(keycode=hid.KEYCODE_EQUAL_SIGN),
366+
'`':
367+
hid.Keystroke(keycode=hid.KEYCODE_EQUAL_SIGN,
368+
modifier=hid.MODIFIER_LEFT_SHIFT),
369+
}
370+
265371

266372
def convert(char, language):
267373
"""Converts a language character into a HID Keystroke object.
@@ -279,7 +385,8 @@ def convert(char, language):
279385
try:
280386
language_map = {
281387
'en-GB': _GB_CHAR_TO_HID_MAP,
282-
'en-US': _US_CHAR_TO_HID_MAP
388+
'en-US': _US_CHAR_TO_HID_MAP,
389+
'de-DE': _DE_CHAR_TO_HID_MAP,
283390
}[language]
284391
except KeyError:
285392
# Default to en-US if no other language matches.

app/text_to_hid_test.py

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def test_language_mapping(self):
2323
self.assertEqual(
2424
hid.Keystroke(hid.KEYCODE_SINGLE_QUOTE, hid.MODIFIER_LEFT_SHIFT),
2525
text_to_hid.convert('@', 'en-GB'))
26+
self.assertEqual(hid.Keystroke(hid.KEYCODE_Q, hid.MODIFIER_ALT_GR),
27+
text_to_hid.convert('@', 'de-DE'))
2628

2729
def test_defaults_to_us_english_language_mapping(self):
2830
self.assertEqual(

0 commit comments

Comments
 (0)