|
| 1 | +# SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | + |
| 5 | +""" |
| 6 | +`adafruit_hid.keyboard_layout_base.KeyboardLayoutBase` |
| 7 | +======================================================= |
| 8 | +
|
| 9 | +* Author(s): Dan Halbert, AngainorDev, Neradoc |
| 10 | +""" |
| 11 | + |
| 12 | + |
| 13 | +try: |
| 14 | + from typing import Tuple |
| 15 | + from .keyboard import Keyboard |
| 16 | +except ImportError: |
| 17 | + pass |
| 18 | + |
| 19 | + |
| 20 | +__version__ = "0.0.0-auto.0" |
| 21 | +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HID.git" |
| 22 | + |
| 23 | + |
| 24 | +class KeyboardLayoutBase: |
| 25 | + """Base class for keyboard layouts. Uses the tables defined in the subclass |
| 26 | + to map UTF-8 characters to appropriate keypresses. |
| 27 | +
|
| 28 | + Non-supported characters and most control characters will raise an exception. |
| 29 | + """ |
| 30 | + |
| 31 | + SHIFT_FLAG = 0x80 |
| 32 | + """Bit set in any keycode byte if the shift key is required for the character.""" |
| 33 | + ALTGR_FLAG = 0x80 |
| 34 | + """Bit set in the combined keys table if altgr is required for the first key.""" |
| 35 | + SHIFT_CODE = 0xE1 |
| 36 | + """The SHIFT keycode, to avoid dependency to the Keycode class.""" |
| 37 | + RIGHT_ALT_CODE = 0xE6 |
| 38 | + """The ALTGR keycode, to avoid dependency to the Keycode class.""" |
| 39 | + ASCII_TO_KEYCODE = () |
| 40 | + """Bytes string of keycodes for low ASCII characters, indexed by the ASCII value. |
| 41 | + Keycodes use the `SHIFT_FLAG` if needed. |
| 42 | + Dead keys are excluded by assigning the keycode 0.""" |
| 43 | + HIGHER_ASCII = {} |
| 44 | + """Dictionary that associates the ord() int value of high ascii and utf8 characters |
| 45 | + to their keycode. Keycodes use the `SHIFT_FLAG` if needed.""" |
| 46 | + NEED_ALTGR = "" |
| 47 | + """Characters in `ASCII_TO_KEYCODE` and `HIGHER_ASCII` that need |
| 48 | + the ALTGR key pressed to type.""" |
| 49 | + COMBINED_KEYS = {} |
| 50 | + """ |
| 51 | + Dictionary of characters (indexed by ord() value) that can be accessed by typing first |
| 52 | + a dead key followed by a regular key, like ``ñ`` as ``~ + n``. The value is a 2-bytes int: |
| 53 | + the high byte is the dead-key keycode (including SHIFT_FLAG), the low byte is the ascii code |
| 54 | + of the second character, with ALTGR_FLAG set if the dead key (the first key) needs ALTGR. |
| 55 | +
|
| 56 | + The combined-key codes bits are: ``0b SDDD DDDD AKKK KKKK``: |
| 57 | + ``S`` is the shift flag for the **first** key, |
| 58 | + ``DDD DDDD`` is the keycode for the **first** key, |
| 59 | + ``A`` is the altgr flag for the **first** key, |
| 60 | + ``KKK KKKK`` is the (low) ASCII code for the second character. |
| 61 | + """ |
| 62 | + |
| 63 | + def __init__(self, keyboard: Keyboard) -> None: |
| 64 | + """Specify the layout for the given keyboard. |
| 65 | +
|
| 66 | + :param keyboard: a Keyboard object. Write characters to this keyboard when requested. |
| 67 | +
|
| 68 | + Example:: |
| 69 | +
|
| 70 | + kbd = Keyboard(usb_hid.devices) |
| 71 | + layout = KeyboardLayout(kbd) |
| 72 | + """ |
| 73 | + self.keyboard = keyboard |
| 74 | + |
| 75 | + def _write(self, keycode: int, altgr: bool = False) -> None: |
| 76 | + """Type a key combination based on shift bit and altgr bool |
| 77 | +
|
| 78 | + :param keycode: int value of the keycode, with the shift bit. |
| 79 | + :param altgr: bool indicating if the altgr key should be pressed too. |
| 80 | + """ |
| 81 | + # Add altgr modifier if needed |
| 82 | + if altgr: |
| 83 | + self.keyboard.press(self.RIGHT_ALT_CODE) |
| 84 | + # If this is a shifted char, clear the SHIFT flag and press the SHIFT key. |
| 85 | + if keycode & self.SHIFT_FLAG: |
| 86 | + keycode &= ~self.SHIFT_FLAG |
| 87 | + self.keyboard.press(self.SHIFT_CODE) |
| 88 | + self.keyboard.press(keycode) |
| 89 | + self.keyboard.release_all() |
| 90 | + |
| 91 | + def write(self, string: str) -> None: |
| 92 | + """Type the string by pressing and releasing keys on my keyboard. |
| 93 | +
|
| 94 | + :param string: A string of UTF-8 characters to convert to key presses and send. |
| 95 | + :raises ValueError: if any of the characters has no keycode |
| 96 | + (such as some control characters). |
| 97 | +
|
| 98 | + Example:: |
| 99 | +
|
| 100 | + # Write abc followed by Enter to the keyboard |
| 101 | + layout.write('abc\\n') |
| 102 | + """ |
| 103 | + for char in string: |
| 104 | + # find easy ones first |
| 105 | + keycode = self._char_to_keycode(char) |
| 106 | + if keycode > 0: |
| 107 | + self._write(keycode, char in self.NEED_ALTGR) |
| 108 | + # find combined keys |
| 109 | + elif ord(char) in self.COMBINED_KEYS: |
| 110 | + # first key (including shift bit) |
| 111 | + cchar = self.COMBINED_KEYS[ord(char)] |
| 112 | + self._write(cchar >> 8, cchar & self.ALTGR_FLAG) |
| 113 | + # second key (removing the altgr bit) |
| 114 | + char = chr(cchar & 0xFF & (~self.ALTGR_FLAG)) |
| 115 | + keycode = self._char_to_keycode(char) |
| 116 | + # assume no altgr needed for second key |
| 117 | + self._write(keycode, False) |
| 118 | + else: |
| 119 | + raise ValueError( |
| 120 | + "No keycode available for character {letter} ({num}/0x{num:02x}).".format( |
| 121 | + letter=repr(char), num=ord(char) |
| 122 | + ) |
| 123 | + ) |
| 124 | + |
| 125 | + def keycodes(self, char: str) -> Tuple[int, ...]: |
| 126 | + """Return a tuple of keycodes needed to type the given character. |
| 127 | +
|
| 128 | + :param char: A single UTF8 character in a string. |
| 129 | + :type char: str of length one. |
| 130 | + :returns: tuple of Keycode keycodes. |
| 131 | + :raises ValueError: if there is no keycode for ``char``. |
| 132 | +
|
| 133 | + Examples:: |
| 134 | +
|
| 135 | + # Returns (Keycode.TAB,) |
| 136 | + keycodes('\t') |
| 137 | + # Returns (Keycode.A,) |
| 138 | + keycode('a') |
| 139 | + # Returns (Keycode.SHIFT, Keycode.A) |
| 140 | + keycode('A') |
| 141 | + # Raises ValueError with a US layout because it's an unknown character |
| 142 | + keycode('é') |
| 143 | + """ |
| 144 | + keycode = self._char_to_keycode(char) |
| 145 | + if keycode == 0: |
| 146 | + raise ValueError( |
| 147 | + "No keycode available for character {letter} ({num}/0x{num:02x}).".format( |
| 148 | + letter=repr(char), num=ord(char) |
| 149 | + ) |
| 150 | + ) |
| 151 | + |
| 152 | + codes = [] |
| 153 | + if char in self.NEED_ALTGR: |
| 154 | + codes.append(self.RIGHT_ALT_CODE) |
| 155 | + if keycode & self.SHIFT_FLAG: |
| 156 | + codes.extend((self.SHIFT_CODE, keycode & ~self.SHIFT_FLAG)) |
| 157 | + else: |
| 158 | + codes.append(keycode) |
| 159 | + |
| 160 | + return codes |
| 161 | + |
| 162 | + def _above128char_to_keycode(self, char: str) -> int: |
| 163 | + """Return keycode for above 128 utf8 codes. |
| 164 | +
|
| 165 | + A character can be indexed by the char itself or its int ord() value. |
| 166 | +
|
| 167 | + :param char_val: char value |
| 168 | + :return: keycode, with modifiers if needed |
| 169 | + """ |
| 170 | + if ord(char) in self.HIGHER_ASCII: |
| 171 | + return self.HIGHER_ASCII[ord(char)] |
| 172 | + if char in self.HIGHER_ASCII: |
| 173 | + return self.HIGHER_ASCII[char] |
| 174 | + return 0 |
| 175 | + |
| 176 | + def _char_to_keycode(self, char: str) -> int: |
| 177 | + """Return the HID keycode for the given character, with the SHIFT_FLAG possibly set. |
| 178 | +
|
| 179 | + If the character requires pressing the Shift key, the SHIFT_FLAG bit is set. |
| 180 | + You must clear this bit before passing the keycode in a USB report. |
| 181 | + """ |
| 182 | + char_val = ord(char) |
| 183 | + if char_val > len(self.ASCII_TO_KEYCODE): |
| 184 | + return self._above128char_to_keycode(char) |
| 185 | + keycode = self.ASCII_TO_KEYCODE[char_val] |
| 186 | + return keycode |
0 commit comments