# SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_hid.keyboard_layout_base.KeyboardLayoutBase`
=======================================================
* Author(s): Dan Halbert, AngainorDev, Neradoc
"""
try:
from typing import Tuple
from .keyboard import Keyboard
except ImportError:
pass
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HID.git"
[docs]class KeyboardLayoutBase:
"""Base class for keyboard layouts. Uses the tables defined in the subclass
to map UTF-8 characters to appropriate keypresses.
Non-supported characters and most control characters will raise an exception.
"""
SHIFT_FLAG = 0x80
"""Bit set in any keycode byte if the shift key is required for the character."""
ALTGR_FLAG = 0x80
"""Bit set in the combined keys table if altgr is required for the first key."""
SHIFT_CODE = 0xE1
"""The SHIFT keycode, to avoid dependency to the Keycode class."""
RIGHT_ALT_CODE = 0xE6
"""The ALTGR keycode, to avoid dependency to the Keycode class."""
ASCII_TO_KEYCODE = ()
"""Bytes string of keycodes for low ASCII characters, indexed by the ASCII value.
Keycodes use the `SHIFT_FLAG` if needed.
Dead keys are excluded by assigning the keycode 0."""
HIGHER_ASCII = {}
"""Dictionary that associates the ord() int value of high ascii and utf8 characters
to their keycode. Keycodes use the `SHIFT_FLAG` if needed."""
NEED_ALTGR = ""
"""Characters in `ASCII_TO_KEYCODE` and `HIGHER_ASCII` that need
the ALTGR key pressed to type."""
COMBINED_KEYS = {}
"""
Dictionary of characters (indexed by ord() value) that can be accessed by typing first
a dead key followed by a regular key, like ``ñ`` as ``~ + n``. The value is a 2-bytes int:
the high byte is the dead-key keycode (including SHIFT_FLAG), the low byte is the ascii code
of the second character, with ALTGR_FLAG set if the dead key (the first key) needs ALTGR.
The combined-key codes bits are: ``0b SDDD DDDD AKKK KKKK``:
``S`` is the shift flag for the **first** key,
``DDD DDDD`` is the keycode for the **first** key,
``A`` is the altgr flag for the **first** key,
``KKK KKKK`` is the (low) ASCII code for the second character.
"""
def __init__(self, keyboard: Keyboard) -> None:
"""Specify the layout for the given keyboard.
:param keyboard: a Keyboard object. Write characters to this keyboard when requested.
Example::
kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayout(kbd)
"""
self.keyboard = keyboard
def _write(self, keycode: int, altgr: bool = False) -> None:
"""Type a key combination based on shift bit and altgr bool
:param keycode: int value of the keycode, with the shift bit.
:param altgr: bool indicating if the altgr key should be pressed too.
"""
# Add altgr modifier if needed
if altgr:
self.keyboard.press(self.RIGHT_ALT_CODE)
# If this is a shifted char, clear the SHIFT flag and press the SHIFT key.
if keycode & self.SHIFT_FLAG:
keycode &= ~self.SHIFT_FLAG
self.keyboard.press(self.SHIFT_CODE)
self.keyboard.press(keycode)
self.keyboard.release_all()
[docs] def write(self, string: str) -> None:
"""Type the string by pressing and releasing keys on my keyboard.
:param string: A string of UTF-8 characters to convert to key presses and send.
:raises ValueError: if any of the characters has no keycode
(such as some control characters).
Example::
# Write abc followed by Enter to the keyboard
layout.write('abc\\n')
"""
for char in string:
# find easy ones first
keycode = self._char_to_keycode(char)
if keycode > 0:
self._write(keycode, char in self.NEED_ALTGR)
# find combined keys
elif ord(char) in self.COMBINED_KEYS:
# first key (including shift bit)
cchar = self.COMBINED_KEYS[ord(char)]
self._write(cchar >> 8, cchar & self.ALTGR_FLAG)
# second key (removing the altgr bit)
char = chr(cchar & 0xFF & (~self.ALTGR_FLAG))
keycode = self._char_to_keycode(char)
# assume no altgr needed for second key
self._write(keycode, False)
else:
raise ValueError(
"No keycode available for character {letter} ({num}/0x{num:02x}).".format(
letter=repr(char), num=ord(char)
)
)
[docs] def keycodes(self, char: str) -> Tuple[int, ...]:
"""Return a tuple of keycodes needed to type the given character.
:param char: A single UTF8 character in a string.
:type char: str of length one.
:returns: tuple of Keycode keycodes.
:raises ValueError: if there is no keycode for ``char``.
Examples::
# Returns (Keycode.TAB,)
keycodes('\t')
# Returns (Keycode.A,)
keycode('a')
# Returns (Keycode.SHIFT, Keycode.A)
keycode('A')
# Raises ValueError with a US layout because it's an unknown character
keycode('é')
"""
keycode = self._char_to_keycode(char)
if keycode == 0:
raise ValueError(
"No keycode available for character {letter} ({num}/0x{num:02x}).".format(
letter=repr(char), num=ord(char)
)
)
codes = []
if char in self.NEED_ALTGR:
codes.append(self.RIGHT_ALT_CODE)
if keycode & self.SHIFT_FLAG:
codes.extend((self.SHIFT_CODE, keycode & ~self.SHIFT_FLAG))
else:
codes.append(keycode)
return codes
def _above128char_to_keycode(self, char: str) -> int:
"""Return keycode for above 128 utf8 codes.
A character can be indexed by the char itself or its int ord() value.
:param char_val: char value
:return: keycode, with modifiers if needed
"""
if ord(char) in self.HIGHER_ASCII:
return self.HIGHER_ASCII[ord(char)]
if char in self.HIGHER_ASCII:
return self.HIGHER_ASCII[char]
return 0
def _char_to_keycode(self, char: str) -> int:
"""Return the HID keycode for the given character, with the SHIFT_FLAG possibly set.
If the character requires pressing the Shift key, the SHIFT_FLAG bit is set.
You must clear this bit before passing the keycode in a USB report.
"""
char_val = ord(char)
if char_val > len(self.ASCII_TO_KEYCODE):
return self._above128char_to_keycode(char)
keycode = self.ASCII_TO_KEYCODE[char_val]
return keycode