Skip to content

Commit

Permalink
Support German (de-DE) keyboard layouts when pasting (#1842)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
DustyDiamond and jotaen authored Sep 12, 2024
1 parent b3be4fa commit a287d8e
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 3 deletions.
7 changes: 7 additions & 0 deletions app/hid/keycodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
MODIFIER_RIGHT_SHIFT = 1 << 5
MODIFIER_RIGHT_ALT = 1 << 6
MODIFIER_RIGHT_META = 1 << 7
# The "Alt Gr" key is a special modifier key that is present on e.g. German
# keyboards. There are multiple ways to emulate this key, e.g. by (a) just
# using the "Right Alt" modifier, or (b) by using "Left Alt"+"Left Ctrl". In
# our tests, we found that (a) seems to be the most compatible option, as it
# appears to work on both Windows and Linux systems, whereas (b) appears to
# only work reliably on Windows systems.
MODIFIER_ALT_GR = MODIFIER_RIGHT_ALT

KEYCODE_NONE = 0
KEYCODE_A = 0x04
Expand Down
5 changes: 3 additions & 2 deletions app/templates/custom-elements/paste-dialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ <h3>Paste Text</h3>
spellcheck="false"
></textarea>
<div class="hint">
The target system must have an <span class="monospace">en-US</span> or
<span class="monospace">en-GB</span> keyboard layout.
The target system must have an <span class="monospace">en-US</span>,
<span class="monospace">en-GB</span> or
<span class="monospace">de-DE</span> keyboard layout.
</div>
<button id="confirm-btn" class="btn-success">Paste</button>
<button id="cancel-btn">Close</button>
Expand Down
109 changes: 108 additions & 1 deletion app/text_to_hid.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,112 @@ class UnsupportedCharacterError(Error):
modifier=hid.MODIFIER_LEFT_SHIFT),
}

_DE_CHAR_TO_HID_MAP = _COMMON_CHAR_TO_HID_MAP | {
'y':
hid.Keystroke(keycode=hid.KEYCODE_Z),
'z':
hid.Keystroke(keycode=hid.KEYCODE_Y),
'Y':
hid.Keystroke(keycode=hid.KEYCODE_Z, modifier=hid.MODIFIER_LEFT_SHIFT),
'Z':
hid.Keystroke(keycode=hid.KEYCODE_Y, modifier=hid.MODIFIER_LEFT_SHIFT),
'"':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_2,
modifier=hid.MODIFIER_LEFT_SHIFT),
'§':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_3,
modifier=hid.MODIFIER_LEFT_SHIFT),
'&':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_6,
modifier=hid.MODIFIER_LEFT_SHIFT),
'/':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_7,
modifier=hid.MODIFIER_LEFT_SHIFT),
'(':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_8,
modifier=hid.MODIFIER_LEFT_SHIFT),
')':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_9,
modifier=hid.MODIFIER_LEFT_SHIFT),
'=':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_0,
modifier=hid.MODIFIER_LEFT_SHIFT),
'<':
hid.Keystroke(keycode=hid.KEYCODE_102ND),
'>':
hid.Keystroke(keycode=hid.KEYCODE_102ND,
modifier=hid.MODIFIER_LEFT_SHIFT),
'#':
hid.Keystroke(keycode=hid.KEYCODE_HASH),
';':
hid.Keystroke(keycode=hid.KEYCODE_COMMA,
modifier=hid.MODIFIER_LEFT_SHIFT),
':':
hid.Keystroke(keycode=hid.KEYCODE_PERIOD,
modifier=hid.MODIFIER_LEFT_SHIFT),
'-':
hid.Keystroke(keycode=hid.KEYCODE_FORWARD_SLASH),
'_':
hid.Keystroke(keycode=hid.KEYCODE_FORWARD_SLASH,
modifier=hid.MODIFIER_LEFT_SHIFT),
'ä':
hid.Keystroke(keycode=hid.KEYCODE_SINGLE_QUOTE),
'Ä':
hid.Keystroke(keycode=hid.KEYCODE_SINGLE_QUOTE,
modifier=hid.MODIFIER_LEFT_SHIFT),
'ö':
hid.Keystroke(keycode=hid.KEYCODE_SEMICOLON),
'Ö':
hid.Keystroke(keycode=hid.KEYCODE_SEMICOLON,
modifier=hid.MODIFIER_LEFT_SHIFT),
'ü':
hid.Keystroke(keycode=hid.KEYCODE_LEFT_BRACKET),
'Ü':
hid.Keystroke(keycode=hid.KEYCODE_LEFT_BRACKET,
modifier=hid.MODIFIER_LEFT_SHIFT),
'+':
hid.Keystroke(keycode=hid.KEYCODE_RIGHT_BRACKET),
'*':
hid.Keystroke(keycode=hid.KEYCODE_RIGHT_BRACKET,
modifier=hid.MODIFIER_LEFT_SHIFT),
'~':
hid.Keystroke(keycode=hid.KEYCODE_RIGHT_BRACKET,
modifier=hid.MODIFIER_ALT_GR),
'|':
hid.Keystroke(keycode=hid.KEYCODE_102ND, modifier=hid.MODIFIER_ALT_GR),
'@':
hid.Keystroke(keycode=hid.KEYCODE_Q, modifier=hid.MODIFIER_ALT_GR),
'€':
hid.Keystroke(keycode=hid.KEYCODE_E, modifier=hid.MODIFIER_ALT_GR),
'{':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_7,
modifier=hid.MODIFIER_ALT_GR),
'[':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_8,
modifier=hid.MODIFIER_ALT_GR),
']':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_9,
modifier=hid.MODIFIER_ALT_GR),
'}':
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_0,
modifier=hid.MODIFIER_ALT_GR),
'\'':
hid.Keystroke(keycode=hid.KEYCODE_BACKSLASH,
modifier=hid.MODIFIER_LEFT_SHIFT),
'?':
hid.Keystroke(keycode=hid.KEYCODE_MINUS,
modifier=hid.MODIFIER_LEFT_SHIFT),
'ß':
hid.Keystroke(keycode=hid.KEYCODE_MINUS),
'\\':
hid.Keystroke(keycode=hid.KEYCODE_MINUS, modifier=hid.MODIFIER_ALT_GR),
'´':
hid.Keystroke(keycode=hid.KEYCODE_EQUAL_SIGN),
'`':
hid.Keystroke(keycode=hid.KEYCODE_EQUAL_SIGN,
modifier=hid.MODIFIER_LEFT_SHIFT),
}


def convert(char, language):
"""Converts a language character into a HID Keystroke object.
Expand All @@ -279,7 +385,8 @@ def convert(char, language):
try:
language_map = {
'en-GB': _GB_CHAR_TO_HID_MAP,
'en-US': _US_CHAR_TO_HID_MAP
'en-US': _US_CHAR_TO_HID_MAP,
'de-DE': _DE_CHAR_TO_HID_MAP,
}[language]
except KeyError:
# Default to en-US if no other language matches.
Expand Down
2 changes: 2 additions & 0 deletions app/text_to_hid_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def test_language_mapping(self):
self.assertEqual(
hid.Keystroke(hid.KEYCODE_SINGLE_QUOTE, hid.MODIFIER_LEFT_SHIFT),
text_to_hid.convert('@', 'en-GB'))
self.assertEqual(hid.Keystroke(hid.KEYCODE_Q, hid.MODIFIER_ALT_GR),
text_to_hid.convert('@', 'de-DE'))

def test_defaults_to_us_english_language_mapping(self):
self.assertEqual(
Expand Down

0 comments on commit a287d8e

Please sign in to comment.